design

v2026.09.24

Design and edit anything in Subframe — pages, components, snippets, design documents, the theme. Use when the user explicitly wants a resource in the Subframe design tool created or changed, asks to explore visual directions in Subframe, or invokes /subframe:design. Do not use for implementation-only work in an existing codebase. Always load this skill when taking any action through the Subframe MCP server. This includes building or iterating on UI, evolving the design system, capturing design intent in writing, or cleaning up a project. Don't write UI code directly — design first, then implement with /subframe:develop.

GitHub
Install command
npx skhub add subframeapp/design
Markdown
SKILL.md

The Subframe MCP server exposes tools for the full design surface — pages, components, snippets, design documents, theme — and this skill teaches you when to reach for each one. Each tool returns a URL the user can open to see the result. The heaviest ones (design_page, design_component, edit_component) run as background AI jobs and also return a jobId — pass it to wait_for_jobs if you need to ensure completion.

Treat a Subframe design as an explicit output, not a prerequisite for UI coding. When the user requests a Subframe design artifact or meaningful design exploration, design in Subframe first and then implement with /subframe:develop if requested. For implementation-only requests, edit the code directly.

When to use this skill

Use this skill when the user wants to:

  • Create a page or screen in Subframe, materially redesign one, or explore visual directions
  • Add or edit a reusable Subframe component or snippet
  • Capture design intent or component usage guidance as a Subframe design document
  • Update the Subframe project's visual theme (colors, fonts, corners, shadows, typography)
  • Remove Subframe pages, components, snippets, or canvases that are no longer needed

The key value: /subframe:design and /subframe:develop bridge coding and design. They work in both directions — create designs while coding and then ensure your code exactly reflects your design.

Picking the right tool

IntentTool
Find out what already exists in the projectlist_components, list_pages, list_snippets, list_canvases, get_project_info
Build a screen the user navigates todesign_page (new) / edit_page (targeted change)
Change only the styles of existing page or snippet elementsupdate_node_styles
Build a reusable building block (Button, Card, ListItem) used inside pagesdesign_component (new) / edit_component (targeted change)
Build a small example used inside a design document (e.g. a Button-variants demo)design_snippet (new) / edit_snippet (targeted change)
Write or update written design / usage documentationwrite_design_document
Change project-wide colors, fonts, corners, shadows, typographyedit_theme
Remove a page, canvas, component, or snippetdelete_page / delete_canvas / delete_component / delete_snippet

MCP access

Discover the Subframe MCP tools first if your client loads them on demand. If no Subframe MCP tools are available, help the user install, enable, or authenticate the server using the MCP setup guide. In Claude Code or Codex, /mcp shows the server's connection and authentication status.

If read tools work but design or editing tools are unavailable, check the user's project permissions. Viewers have read-only MCP access, including screenshots and search; designing or editing requires an editor seat. Explain that permission requirement when it is the cause instead of asking the user to authenticate again.

Find the projectId

Every design tool takes a projectId. Resolve it like this:

  1. Check .subframe/sync.json if it exists locally.
  2. If no projectId is found, call list_projects. Each project includes a projectId, name, teamId, and teamName.
    • One project: Use it automatically.
    • Multiple projects: Always ask the user which project to use. Present each project with its teamName to disambiguate. If the user already mentioned a specific team or project name, match it against the teamName and name fields — but still confirm before proceeding. Never silently pick a project when multiple exist.

Audit what already exists

Before working on any design, get a picture of the project's current state. On any project where you don't already have explicit knowledge of what's been built, call:

  • list_components — see which components already exist. Some projects may have pre-existing components, some may not have any components yet.
  • get_theme — see the project's theme tokens (colors, fonts, corners, shadows, typography).
  • get_project_info — see project-level design documents.

This audit is cheap and critical to proper project management.

Verify theme alignment before designing

The first time you're about to call design_page / design_component / edit_page / edit_component / update_node_styles against a given project in this conversation, follow this process:

  1. If the project has codebase context (working in a repo, recreating a page, importing a design), locate and read the codebase's theme source — tailwind.config.*, theme CSS variables, a tokens module. Also read the codebase's most canonical filled component (typically Button) to see which token is used where — names and values can match while roles diverge.

  2. Write the comparison — a brief alignment summary covering colors, fonts, corners, shadows, typography, and role (which token is used for what, on both sides). Flag mismatches even when names/values look the same. Watch specifically for:

    • Role divergence — e.g., the codebase's brand token paints focus rings only while a separate primary token paints filled-button backgrounds; the Subframe project's convention may have brand-primary painting the filled-button background instead. If you need to verify Subframe-side roles, call get_component_info on the relevant component to see which tokens it actually references.
    • Dual-token systems — shadcn-style primary / primary-foreground, destructive / destructive-foreground, etc., where each role pairs a surface token with a content token. The Subframe theme may not have direct equivalents.
  3. If anything mismatches, or get_theme is empty, stop and ask the user if they would like to set up the theme via edit_theme first since any design call is at risk of using hardcoded values and breaking the design system source-of-truth promise. Treat this like destructive-deletion confirmation: required even under autonomy / no-clarifying-questions directives.

  4. If the codebase uses hardcoded values instead of token, the user may be hoping to start using tokens with Subframe. Ask the user if they want to create a theme in Subframe from their hardcoded values or if they would prefer to stick with hardcoded values.

If the project has no codebase context, only the empty-theme check applies — skip the comparison.

When roles diverge, ask the user how they would like to proceed as far as keeping the Subframe roles versus matching their codebase ones. Based on their answer, follow the process outlined in Risk-classify before calling and, if necessary, safe consolidation via alias bridging.

Grounding design calls in real code

Subframe's agent is far more accurate when the call carries raw code than when it carries paraphrase. "Make the primary darker" leaves a guess; pasting --color-primary: oklch(0.55 0.18 250) doesn't. Wherever the codebase already has the source — a component implementation, theme tokens, a similar page — pass it into the call instead of describing it.

design_page, design_component, and edit_component take grounding through the references parameter — a list of { source, usage } entries where source is one of:

  • { kind: "subframe", id } — an existing Subframe page, component, or snippet (by ID or name; resolved server-side, no need to inline its code)
  • { kind: "code", content } — raw code from the codebase (the surface to recreate, data types, usage patterns)
  • { kind: "image", url } — a Subframe-hosted upload URL (e.g. an image the user pasted from Subframe). Uploads only happen inside the Subframe app — there is no MCP or CLI upload, so skip this kind unless you were given an upload URL. Local files or arbitrary URLs are rejected; up to 5 images are used per call, each applied per its own usage note.

usage says how the generator should use THAT reference — e.g. "match this page's layout and header" or "recreate this mockup exactly". Always write it: the generator sees only the description plus the references, not your reasoning. (edit_page / edit_snippet are node-targeted and take no grounding.)

Default to pasting full files when they exist. Under-including is generally worse than over-including. Head each content with its path:

// src/components/Button.tsx
<full file content>

Group related files as separate code references (component + stories + CSS module), each with its own usage note.

Don't paste what the agent already has. For edit_component, the current Subframe code is already on the server — don't echo it back. Read it with get_component_info so your description can target exactly what differs. Prefer the most efficient form: plain language when the change doesn't depend on any code the agent hasn't seen ("change padding from 4 to 6, add a hover state"); otherwise pass reference code scaled to what's needed — a targeted diff or code snippet when the Subframe code already resembles the target, a full file when handing over a wholesale target, a sibling pattern, or related types — as code references with usage notes. (edit_page and edit_snippet use a different, node-targeted model — see their own sections.)

Soft cap on very large files (~500 LOC combined). When trimming, keep verbatim:

  • Prop / type / interface definitions
  • JSX structure with className strings (and any cva / variant maps) intact
  • All styling rules — never paraphrase styles ("subtle shadow with rounded corners" → paste className="rounded-md shadow-sm")

Safe to trim:

  • Business handlers, event wiring, data fetching, side effects
  • Imports unrelated to structure/styling
  • Test- or dev-only blocks
  • Comments that aren't load-bearing

If the codebase has no source for what you're designing, describe the design from scratch rather than fabricating a reference.

For component design (design_component, edit_component)

Include in the description:

  1. The canonical component source file if it exists in the codebase (e.g. src/components/Button.tsx).
  2. The stories file (e.g. Button.stories.tsx / .mdx) — exact variants, sizes, states, and the props that produce each.
  3. CSS modules or scoped CSS (e.g. Button.module.css) if styling lives outside Tailwind classes.
  4. Prop-type files if types are defined separately (e.g. a types.ts).
  5. Theme tokens the component references — pull the relevant rows from the codebase theme (tailwind.config.*, CSS variables) and from get_theme so the agent keeps roles aligned.

For snippet design (design_snippet)

design_snippet takes the same references input as the other design tools — ground it the same way (the codebase implementation the snippet should mirror as code, existing Subframe entities as subframe). edit_snippet takes no grounding context — it's a node-targeted edit (see its section below).

For page design (design_page)

See Preparing references below for the page-specific rule about leaving Subframe component references as-is and inlining everything else. The general grounding rules above (default to full files, paste styles verbatim, soft cap with trimming) layer on top. (edit_page is a node-targeted edit, not a reference-grounded design call — see its section below.)

For theme edits (edit_theme)

See Preparing the edit_theme description under the Theme section. The codebase theme source files belong in the description verbatim — don't paraphrase token values.

Background jobs and wait_for_jobs

design_page, design_component, and edit_component return a jobId alongside their URL. The job runs in the background — the URL is live immediately and allows the user to watch the design populate.

Surface job status to the user. When you kick off a design, tell them you've started ("Designing your settings page in Subframe…") and present the URL. When the job finishes, tell them a relevant message like "✓ Variations are ready to review.". The user already sees live progress in the editor, but they should not have to go to the editor to know when the design is done.

Present the URL verbatim as a clickable link. Opening the URL returned by design_page, design_component, or edit_component shows the design in Subframe, where the user can watch it generate and keep iterating with the agent in agent chat.

When to call wait_for_jobs:

  • Before reading back the generated content with get_page_info, get_component_info, get_snippet_info, or get_canvas_info. The read returns empty/stale state until the job finishes.
  • Before a downstream design call that needs the new resource as context (e.g., designing a page that should reference a component you just created).
  • Before handing off to /subframe:develop if the user immediately asks to implement.

You don't need wait_for_jobs when you're only presenting the URL to the user and stopping there.

wait_for_jobs accepts multiple jobIds at once — batch them when you've kicked off multiple designs. Each result is running, done, error, or not_found. Call in a loop until every job reads done or error. For design_page, the summary reports how many requested pages were actually applied: partial success is done with the count, while zero applied pages is error. Inspect the canvas and decide whether retrying missing variations is useful. A job that stops reporting eventually becomes error; never treat an unverifiable job as completed work.

Pages

Before designing a page

When the user asks you to design, recreate, or redesign a page that uses non-trivial UI components (see What belongs as a Subframe component for what counts):

  1. Run the project audit — list_components (and get_project_info if you haven't yet).

  2. Inventory the components the page renders with their status and decision. Output the list verbatim, even when the conclusion seems obvious

    • Missing entirely → design_component
    • Exists but visually doesn't match the source/spec → edit_component (existing components keep their identity; existing usages are updated)
    • Exists and matches → reference directly in the page design

    Example:

    Button: missing → design_component
    Alert: missing → design_component
    SettingsCard: missing → design_component
    ProfileMenu: exists, matches → reference
    
  3. Write the dependency list before any design_component/edit_component calls. For each new or edited component in the batch, list the other components in the batch that it visually embeds. Output it verbatim, even when the list is short. For example:

    Button: deps=[]
    Alert: deps=[]
    SettingsCard: deps=[Button]  // footer holds a Save button
    

    Many components embed other components — a Card with an action footer holds a Button, a Form holds Text Fields, a ListItem holds an Avatar. If you skip writing the list, you will miss these.

  4. Group into waves from the dependency list, then run waves sequentially. A component goes in Wave N if all its deps are in waves < N or already ready as-is. Kick off everything in a wave in parallel, wait_for_jobs on the whole wave, then start the next wave. wait_for_jobs on the final wave before kicking off the page, so the page design sees up-to-date components.

    Example: page needs a new Button, a new Alert, and a new SettingsCard (whose footer renders a Save Button).

    • Wave 1: Button + Alert in parallel → wait_for_jobs both.
    • Wave 2: SettingsCard → wait_for_jobs.
    • Then design_page.

Default to handling all the components the page renders — not just the domain-specific ones. This includes standard components like Button, Input, Alert, Card, Badge, Tabs, Toggle, etc. design_page does NOT have a default component library to fall back on. list_components is the complete list available in the project. If design_page needs a component that doesn't exist, it falls back to inline markup, which doesn't create a reusable component.

If you would need to create/edit more than 3 components in Subframe to design the page, ask the user if they would prefer you to handle all the components, design the page without them, or somewhere in between. When designing any number of components, always run the dependency listing in step 3.

design_page — new pages and redesigns

Use design_page when:

  • Creating a new page from scratch
  • Redesigning or rethinking existing UI — if there's an existing implementation in code, use design_page when the user wants to explore new design directions or add new features
  • Recreating an existing UI from code exactly as a starting point to design in Subframe
  • The user wants options to choose from (multiple variations)

Always pass a canvasName (required); variations land as pages on that canvas. Returns canvasId, canvasUrl, and jobId.

Context and variations

How much context to gather and how many variations to generate depends on the task:

TaskContextVariations
New page (open-ended)Data types (code reference)4 — explore the design space
New page (with reference pages)Reference pages (subframe reference if in Subframe, code if not), data types (code)1-2 — stay close to the reference pages
Redesigning existing UIThe current page (subframe reference if in Subframe, code if not; the usage note says what to keep vs change)2-4 — depending on how open-ended
Recreating an existing UIThe current page's exact markup and styles (code reference, usage "recreate this exactly")1 — recreate the UI from code exactly

Always include when available:

  • The existing page being discussed and similar existing pages (the single most valuable context) — pass them as subframe references by ID or name (e.g. a page ID from a pasted MCP link, or a variation page ID the user referenced). A subframe reference resolves any Subframe page, snippet, or component; use a code reference for code that only exists in the codebase.
  • Components or patterns the user refers to or explicitly mentions — subframe references for components already in the Subframe project, code references for patterns or code not yet in Subframe
  • Data types/interfaces for what the page will display (as a code reference)
  • A mockup or screenshot the user supplied through Subframe — as an image reference (Subframe-hosted upload URLs only)

Preparing references

The general "paste real code, soft cap with verbatim styling" rules in Grounding design calls in real code apply here too. The page-specific addition below is the Subframe-vs-non-Subframe component handling rule.

When including code in a code reference, distinguish between components in the Subframe project vs not (list_components is the source of truth):

  • References to components that already exist in the project — leave as-is. Subframe will resolve them from the project.
  • References to components that don't exist in the project yet — either design them first with design_component (see Before designing a page), or inline their rendered JSX + Tailwind classes into the reference content.
  • References to non-component application code — always inline. See What belongs as a Subframe component for the distinction.

When you inline a component, expand it into its JSX markup (inputs, buttons, layout, Tailwind classes). If that expanded markup has more component references, evaluate those using the same process.

Variations

Each variation is an object with a name (short name) and description (a few sentence prompt describing the design direction).

When you have reference pages (references), use fewer variations (1-2) and keep them grounded in the reference. The variations should refine or extend the existing design, not diverge from it. For example:

  • { "name": "Adapted layout", "description": "Follow the same layout as the reference page but adapted for [new content]" }
  • { "name": "Compact data-dense", "description": "Same structure as the reference but with a more compact, data-dense layout." }

When starting from scratch (no references), use more variations (4) to explore the design space:

  • { "name": "Data table", "description": "Compact data table with inline actions and bulk operations." }
  • { "name": "Card grid", "description": "Card-based layout with visual hierarchy and quick filters." }
  • { "name": "Minimal single-column", "description": "Minimal single-column design focused on the primary action." }
  • { "name": "Split panel", "description": "Split-panel layout with sidebar navigation and detail view." }

More variations = more exploration. Fewer = more focused. Default to fewer when strong context exists.

Multi-page requests

When designing multiple related pages (user flows, CRUD, etc.):

  1. Design the primary page first with more variations to establish the direction.
  2. After the user has reviewed the variations on the canvas, design each remaining page passing the chosen page's ID as sourcePageId — the generation starts from that page's EXACT structure and changes only what the description asks, so shared header, nav, and layout carry over by construction. Have the user paste an MCP link to the variation they want, or use get_canvas_info with the canvasId to enumerate the pages on the canvas and ask which to use. Add references for anything else the new screen should draw on.
  3. Use the same canvasName so related pages land on the same canvas. Don't pack multiple screens into variations — variations are independent alternatives of the SAME screen.

update_node_styles — restyle elements on a page or snippet

Prefer update_node_styles when only the Tailwind styles of existing page or snippet elements need to change. It updates classes in place while preserving content, structure, instance props, and node IDs. Untargeted descendants are unaffected. To change a reusable component definition, use edit_component; page layouts must be edited in the Subframe editor.

  1. Call get_page_info or get_snippet_info with includeNodeIds: true to read the current classes and each element's data-node-id.
  2. Identify the page or snippet with id, name, or url, and pass its projectId.
  3. Pass nodeIds (an array of 1–100 node IDs) and the complete new className. Use a one-item array for a single element.

The class list is replaced in full. Include every class that should remain, including sizing, layout, responsive, and state classes. For example, changing the padding of flex w-full flex-col gap-4 p-4 requires flex w-full flex-col gap-4 p-6, not just p-6.

Batch elements from the same page or snippet only when they should share the entire new class list. If their final classes differ, use separate calls even when the requested change is the same.

The edit applies immediately and returns url plus results, one entry per unique target in request order. Check each entry's nodeId, appliedClassName, and optional warnings for dropped or unsupported styling. Canonical classes may be normalized; compare the intended styles, not just the exact string.

edit_page — targeted edits to an existing page

Use edit_page for targeted changes to an existing page element's content, structure, styles, or a combination of these. It takes a structured edit:

  1. Call get_page_info with includeNodeIds: true to get the page JSX with a data-node-id on every element.
  2. Pick the nodeId you want to change.
  3. Choose an operation: replace (swap the node and its subtree), insert-above / insert-below (add your code as a new sibling), or delete (remove it).
  4. Pass the new or replacement subtree as code — a JSX fragment with a single root element. Don't include data-node-id attributes (new nodes are assigned ids automatically); leave code empty for delete.

replace removes the target and its entire subtree. Include every descendant that should remain in the replacement JSX. Never send an empty replacement wrapper just to change styling.

Identify the page with id, name, or url (call list_pages first if you need to find it). The edit applies immediately and returns pageUrl, appliedCode (the canonical code after parsing — compare it to what you sent to confirm nothing was dropped or normalized), and any parser warnings. appliedCode carries each element's data-node-id, so target those nodes in follow-up edits directly instead of re-reading the page. The user can undo via page version history in the Subframe editor.

code must be valid Subframe JSX: a single root element and static markup only — no .map(), hooks, state, or conditional rendering (these are rejected, not silently dropped), and no <html>/<body> wrappers. Style with Tailwind classNames; inline style={{…}}, event handlers, and most data-* attributes are dropped. The includeNodeIds: true output is already in this shape — use it as your template.

When to use edit_page vs design_page

  • edit_page: Targeted changes to an existing Subframe page.
  • design_page: New pages, redesigns, or exploring multiple design directions.

When NOT to use edit_page: If the user has existing UI in their codebase but no corresponding Subframe page, or if they want to explore multiple design options, use design_page instead.

After a page design

For design_page, present the returned canvasUrl as a clickable markdown link. The canvas opens immediately; each variation appears as a new page on the canvas as it finishes generating. The user reviews them side-by-side on the canvas and may keep multiple, edit them, delete some, or just leave them all there — there's no formal "pick one" step.

From there, the user may continue refining in Subframe or return here and ask you to implement the design in code. Do NOT ask the user which variation they prefer or present variation options as a multiple choice in chat. Simply present the canvas URL and let them know they can ask you to implement once they're ready.

If you need to enumerate the variation pages programmatically (e.g., to reference one in references or sourcePageId, or to read its current code with get_page_info), call wait_for_jobs with the jobId first, check its applied-page count, then call get_canvas_info with the canvasId. Reading too early may return only the variations that have finished by that moment.

Internally track the canvasId returned by design_page. Don't surface it to the user. Use it with get_canvas_info for follow-up canvas-level operations, or pass the same canvasName on subsequent design_page calls so new variations land on the same canvas.

When referencing, implementing, or editing a page, use specific page IDs the user has referenced (via pasted MCP link or while iterating in the editor), or call get_canvas_info to look them up by name — design_page itself doesn't return individual page IDs since successful variations land as separate pages on the canvas.

Components

Components are reusable UI building blocks (Button, Card, ListItem, Toggle, etc.) that get used inside pages. They live at the project level and sync into the codebase via npx @subframe/cli sync. Designing a component creates a new entry in the project's component library. Editing one updates every page that uses it.

What belongs as a Subframe component

Subframe components are visual/presentational primitives — the reusable UI building blocks that get composed into pages. Be deliberate about what gets promoted to a component vs. what stays inline in a page.

Make it a component if it:

  • Renders pure UI: buttons, inputs, cards, modals, badges, alerts, list items, layout primitives (containers, stacks, grids), etc.
  • Will be reused across multiple pages, or has variants worth defining once

Keep it inline (in the page, not a component) if it:

  • Fetches data, calls APIs, or manages application state
  • Wires together business logic (form submission handlers, validation flows, page-level orchestration)
  • Is a one-off composition specific to a single page
  • Is utility code (hooks, helpers, non-visual modules) — those don't belong in Subframe at all

When unsure, quickly read the source. If it imports data-fetching libraries, stores, or API clients, it's application code — keep it in the page.

design_component — add a new component

Use design_component to create something that should be a Subframe component (see What belongs as a Subframe component). Pass:

  • description — what the component is and how it should look/behave.
  • references (optional) — the grounding material with per-reference usage notes. Pass real code, don't paraphrase — see Grounding design calls in real code for what to include (canonical source, stories, CSS modules, prop types, relevant theme tokens) and verbosity rules, plus a mockup/screenshot as an image reference when the user supplied one through Subframe. Apply the Subframe-vs-application rule from Preparing references: pass components that already exist in this Subframe project as subframe references, inline anything else as code.
  • name — the component name (PascalCase, e.g., "PrivacyToggle")
  • projectId — usually inferred from .subframe/sync.json

Returns componentId (immediately referenceable in other tools), componentUrl (open this in the editor to watch the design happen), and jobId (pass to wait_for_jobs before reading back via get_component_info or referencing in another design call).

edit_component — change an existing component

Use edit_component for targeted changes to a component already in the project. Call get_component_info first so your description can target exactly what differs. The agent already has the current Subframe code — only paste outside reference code when the change depends on something the agent can't see (a codebase implementation to match, a sibling component, a design spec). See Grounding design calls in real code for what to include and how to trim.

Pass one of id, name, or url plus a description; optionally references to point the agent at related Subframe pages/snippets/components, outside code, or an image to match. Returns componentUrl and jobId. Edits propagate to every page using the component, so confirm with the user before making structural changes.

The same component cannot be edited by two agents simultaneously — if another conversation is already working on it, the tool returns the in-progress URL and you should wait or ask the user.

Note: Agent editing is not supported for page layouts. To modify a layout, the user must open it in the Subframe editor directly.

Use edit_component to align existing components with a codebase or spec. If a project component doesn't match the user's source code or design references, that's an edit_component job — don't design a parallel one. If it's unclear whether the edits apply cleanly to existing usages, confirm with the user before editing.

Snippets

Snippets are small, standalone bits of UI typically embedded inside design documents as live examples — for instance, a "Button variants" snippet showing every Button state side-by-side, embedded in the Button's usage doc. They can be inserted into any design but are detached whenever inserted, so changes to a snippet do not propagate to other designs. They're not synced into code as components.

design_snippet — create a new snippet

Use design_snippet when the user wants to illustrate something in a design document, or wants a small standalone composition that doesn't need to be represented as a component. Pass:

  • description — what to show
  • name — optional; defaults to "AI Generated Snippet"
  • references (optional) — the same grounding input as the other design tools: outside code as code entries (the codebase implementation the snippet should mirror, related types, the usage example it illustrates), existing Subframe pages/snippets/components as subframe entries (resolved server-side, no need to inline their code), plus an image when the user supplied one through Subframe — each with a usage note.

Returns snippetId and snippetUrl. Embed the snippet in a design document with <div data-type="component-example" data-component-id="<snippetId>"></div> (see the design documents section).

edit_snippet — change an existing snippet

Same node-targeted model, JSX constraints, and replacement rules as edit_page, but for snippets. Call get_snippet_info with includeNodeIds: true to see each element's data-node-id, then pass nodeId, an operation (replace / insert-above / insert-below / delete), and a single-root code fragment. The edit applies immediately and returns snippetUrl, appliedCode (with each element's data-node-id for follow-up edits), and any warnings. Use when the embedded example needs to evolve alongside the component it documents.

Design documents

Design documents are markdown files that convey how to work within your design system — brand voice, design principles, component usage rules, accessibility requirements, do/don't examples. They're read by you (and other coding agents) when designing or implementing. There are two kinds:

  • Project-scoped docs — cover broad guidance like design principles, project-wide conventions, onboarding notes. A project can have many.
  • Component-scoped docs — attached directly to a specific component; cover specifics for that component like "when to use this" and do/don't examples. A component can have at most one design document.

Use design documents when:

  • The user explicitly asks for one ("write a design doc for the Toggle component")
  • There is complexity in using the design system that needs to be documented for future designs
  • The user's repo has existing design guidelines that should be migrated to Subframe
  • A component needs usage guidance, accessibility notes, or do/don't examples

Read existing docs first via get_project_info (returns project-level docs) or get_component_info (returns the component's designDocuments). If a component already has a doc, you must update it (don't try to create a second one); pass the existing id to write_design_document.

What belongs in a design document

Design documents are for design judgment that Subframe's structured data can't carry. They should be concise and contain information that is unobvious to a consumer of the design system. Subframe's agent already has access to:

  • Component code (props, JSX, styles)
  • The theme (colors, fonts, corners, shadows, typography tokens)

Restating any of that in a doc is wasted space. Reach instead for the layer above: when to use what, how to compose, what it should say, what to avoid.

Belongs in a design doc:

  • Usage hierarchy and taxonomy
  • Variant meaning — what each variant communicates to the user
  • Sizing rules
  • Labeling rules
  • Composition patterns and accessibility notes
  • Theme conventions — when to use the existing theme tokens, e.g. use brand color here but neutral there
  • Do's and don'ts framed as design judgment

Does NOT belong in a design doc:

  • Prop tables or API documentation — the agent reads the component code already
  • Theme token values (hex codes, pixel spacing, shadow definitions, radius values) — the theme already holds these. If the project's theme is wrong or empty, fix the theme via edit_theme; don't paper over it in a doc.
  • Inline JSX or Tailwind class examples — these should be embedded snippets instead
  • Common design standards — if any designer would follow a pattern by default, don't restate it. Only document practices unique to this design system. When a common standard does hold particular importance, write the project-specific angle ("Confirm before destructive actions — restate the noun in the dialog, e.g. 'Delete survey' not 'Are you sure?'"), not the standard itself.
  • Anything trivially derivable from the component source or the theme

write_design_document

Inputs:

  • content — markdown
  • id (optional) — if editing an existing doc; omit to create a new one
  • componentId (optional) — if creating a new component-scoped doc
  • title (optional) — for project-scoped docs only; ignored for component-scoped
  • mode (optional) — replace (default) overwrites, append adds the new content after existing

Embed snippet examples with HTML:

<div data-type="component-example" data-component-id="<snippetId>"></div>

Preserve these tags verbatim when round-tripping through mode: replace — losing them removes the embed.

Theme

Use edit_theme to update the project's visual theme — colors, fonts, corners, shadows, typography tokens. Applies immediately to the whole project; there's no preview step. Always use get_theme first to see the current state before formulating changes.

What edit_theme can do

  • Add new tokens
  • Tweak values on existing tokens ("make the primary darker," "increase border radius")
  • Rename tokens — safe. Components and pages reference tokens by id, so the alias survives the rename.
  • Delete tokens — destructive. Every page/component that referenced the deleted token gets its alias replaced with the token's concrete value at deletion time. Those usages are now detached (hardcoded), not tracking any token. Theme version history restores the token, but the detached references stay detached — full recovery requires project version history.

Preparing the edit_theme description

When the project has codebase context, the description should carry the actual theme source files verbatim, not paraphrased token values. Include every token — do not drop tokens that seem unrelated to the immediate task. The theme is project-wide; what you are about to design isn't the only consumer. Look for:

  • tailwind.config.* (often tailwind.config.ts or .js)
  • Global CSS files with @theme blocks (Tailwind v4) or :root { --color-... } declarations — globals.css, app.css, index.css
  • Token modules (tokens.ts, tokens.json, theme.ts, Style Dictionary exports)
  • Font config (next/font, CSS @font-face, font import URLs)

Paste each file in a fenced block headed by its path (// tailwind.config.ts). Don't summarize token values — the agent's accuracy on color, spacing, and typography depends on the exact strings.

Only let the agent invent tokens when there is genuinely no codebase theme source. In that case, say so explicitly in the description.

Risk-classify before calling

Read the user's prompt and classify before invoking:

RiskOperationsAction
LowAdding tokens, tweaking valuesCall directly; mention what changed after
MediumRenaming tokens, broad multi-token tweaksBriefly note the scope (whole project) and call
HighAnything that deletes tokens, replaces the palette wholesale, or implies "remove / strip / replace existing X"Confirm with the user before calling. Spell out what will be deleted and that pages/components using those tokens will be updated to use hardcoded values (which would then require project version history to undo).

When in doubt about the risk of a prompt, assume High risk and confirm.

Safe consolidation via alias bridging

When the user wants to consolidate tokens (e.g., brand-50 through brand-900 → brand-primary / brand-secondary / brand-tertiary), use alias bridging to avoid the destructive-delete cascade for tokens that map cleanly to the new structure. The process takes two sequential edit_theme calls (can't be combined into one):

  1. First call — create the new tokens AND re-point each mappable old token as an alias of its matching new token. Example: create brand-primary with the desired color, and update brand-200 to alias brand-primary in the same call.
  2. Second call — delete the old tokens. Because references in pages/components resolve through the alias to the new tokens, they stay attached — no hardcoded fallback.

When alias bridging doesn't apply: some old tokens won't map cleanly to any new one (e.g., brand-50 is too pale to belong to any of primary/secondary/tertiary). For those, the standard destructive-delete cascade applies — references become hardcoded values. Make a per-token call: bridge what you can, accept detachment (and confirm with the user) for what you can't.

When NOT to use edit_theme

  • Styling individual pages or snippets — edit those resources directly; theme token changes apply project-wide.

Deletion

Four tools, one per resource type. Always confirm with the user before calling any delete tool — these are irreversible from MCP (the Subframe editor retains version history for restore, but recovery is manual and may require reverting changes that occurred after).

  • delete_page({ id|name|url, projectId, force? }) — deletes a page, removing it from its canvas and stripping prototype actions referencing it. Refuses by default if referenced in other pages. Use force: true to delete anyway. Page layouts can't be deleted with this tool — use delete_component (it cascades to clear pageOptions.layout on every page using the layout).
  • delete_component({ id|name|url, projectId, force? }) — deletes a component or page layout. Detaches instances or clears layouts. Refuses by default if in use. Use force: true to delete anyway.
  • delete_snippet({ id|name|url, projectId }) — deletes a snippet. Any design document embeds are removed automatically.
  • delete_canvas({ id|name|url, projectId, deleteChildPages? }) — deletes a canvas. Refuses if it has pages on it. Use deleteChildPages: true to delete the canvas plus every page on it.

When a delete tool refuses because of references, surface what it would affect to the user before retrying with force: true / deleteChildPages: true. Don't auto-escalate to force-mode without confirmation.

Iterating

The user reviews and refines designs in the Subframe editor, not in code. When they come back asking to combine ideas, refine a specific direction, or iterate further:

  • They reference a specific variation (by pasted MCP link, by name, or by describing it). If you need to find the variation's pageId, call get_canvas_info with the canvasId from the original design_page response — it returns the pages on the canvas with names and IDs. Make targeted edits to that page, or call design_page with the page as sourcePageId (to evolve its exact structure) or as a subframe reference (for a fresh set of options grounded in that direction).
  • They want to mix variations ("I like the layout from variation 1 but the colors from variation 3"). Ask them to paste the MCP links of the variations they want to combine (or use get_canvas_info to look up page IDs by name), then call design_page with those pages as subframe references whose usage notes say what to take from each, and a description of the combination.
  • They want to start over ("none of these are right"). Call design_page again with a refined description and any reference pages as subframe references. Use the same canvasName to keep related work on the same canvas.
  • They want to iterate on a component or snippet. Use edit_component / edit_snippet for targeted changes; the resource keeps its identity and existing usages stay wired up.

You don't have to read the generated code by default — Subframe renders the designs and the user reviews them visually in the editor, so summarizing them in chat usually isn't useful. When reading the code would genuinely help (the user asks what was generated, you're picking which design to extend, etc.), call wait_for_jobs if necessary and then the required get_*_info calls.

Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

Not specified

Source path

plugins/claude/subframe/skills/design

Default branch

main

Latest commit

942ed40

Tree SHA

0f72aab