plugins/claude/subframe/skills/design/SKILL.md
Design and edit anything in Subframe — pages, components, snippets, design documents, the theme. Also handles deletion of those resources except theme. 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.
npx skillsauth add subframeapp/subframe designInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
Don't write UI code directly. Subframe generates production-ready React/Tailwind code that matches the design system. Design in Subframe first, then implement with /subframe:develop.
The user wants to:
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.
| Intent | Tool |
| --- | --- |
| Find out what already exists in the project | list_components, list_pages, list_snippets, list_flows, get_project_info |
| Build a screen the user navigates to | design_page (new) / edit_page (targeted change) |
| Build a reusable building block (Button, Card, ListItem) used inside pages | design_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 documentation | write_design_document |
| Change project-wide colors, fonts, corners, shadows, typography | edit_theme |
| Remove a page, flow, component, or snippet | delete_page / delete_flow / delete_component / delete_snippet |
If you cannot find the design tools (or any Subframe MCP tools), the MCP server likely needs to be authenticated. Ask the user to authenticate the Subframe MCP server. If the user is using Claude Code or Codex, instruct them to run /mcp to view and authenticate their MCP servers, and then say "done" when they're finished.
Every design tool takes a projectId. Resolve it like this:
.subframe/sync.json if it exists locally.list_projects. Each project includes a projectId, name, teamId, and teamName.
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.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.
The first time you're about to call design_page / design_component / edit_page / edit_component against a given project in this conversation, follow this process:
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.
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:
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.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.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.
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.
Subframe's design AI 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 — paste it into the call instead of describing it.
Default to pasting full files when they exist. Under-including is generally worse than over-including. Use the format:
// src/components/Button.tsx
<full file content>
Group related files in adjacent blocks (component + stories + CSS module).
Don't paste what the AI already has. For edit_component, edit_page, and edit_snippet, the current Subframe code is already on the server — don't echo it back. Read it with get_component_info / get_page_info / get_snippet_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 AI hasn't seen ("change padding from 4 to 6, add a hover state"); otherwise 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.
Soft cap on very large files (~500 LOC combined). When trimming, keep verbatim:
className strings (and any cva / variant maps) intactclassName="rounded-md shadow-sm")Safe to trim:
If the codebase has no source for what you're designing, describe the design from scratch rather than fabricating a reference.
design_component, edit_component)Include in the description:
src/components/Button.tsx).Button.stories.tsx / .mdx) — exact variants, sizes, states, and the props that produce each.Button.module.css) if styling lives outside Tailwind classes.types.ts).tailwind.config.*, CSS variables) and from get_theme so the AI keeps roles aligned.design_snippet, edit_snippet)See the snippet tool sections below for codeContext / references parameter use. edit_snippet has no codeContext parameter — paste outside reference code into description.
design_page, edit_page)See Preparing codeContext 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_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.
wait_for_jobsdesign_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 — don't strip query parameters. The URLs returned by design_page, design_component, and edit_component (and the inline-AI tools like edit_page) embed a conversation ID that opens the AI chat panel preloaded with the conversation that produced the design. That gives the user reasoning, intermediate steps, and a place to keep iterating with the AI directly — far more useful context than the bare resource URL.
When to call wait_for_jobs:
get_page_info, get_component_info, get_snippet_info, or get_flow_info. The read returns empty/stale state until the job finishes./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 (with an optional summary), or not_found. Call in a loop until every job reads done. The server treats jobs that stall longer than ~10 minutes as done so the loop never hangs.
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):
Run the project audit — list_components (and get_project_info if you haven't yet).
Inventory the components the page renders with their status and decision. Output the list verbatim, even when the conclusion seems obvious
design_componentedit_component (existing components keep their identity; existing usages are updated)Example:
Button: missing → design_component
Alert: missing → design_component
SettingsCard: missing → design_component
ProfileMenu: exists, matches → reference
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.
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).
Button + Alert in parallel → wait_for_jobs both.SettingsCard → wait_for_jobs.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 redesignsUse design_page when:
design_page when the user wants to explore new design directions or add new featuresHow much context to gather and how many variations to generate depends on the task:
| Task | Context | Variations |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
| New page (open-ended) | Data types (codeContext) | 4 — explore the design space |
| New page (with reference pages) | Reference pages (additionalPages if in Subframe, codeContext if not), data types (codeContext) | 1-2 — stay close to the reference pages |
| Redesigning existing UI | The current page (additionalPages if in Subframe, codeContext if not; note what to keep vs change in the description) | 2-4 — depending on how open-ended |
| Recreating an existing UI | The current page's exact markup and styles (codeContext) | 1 — recreate the UI from code exactly |
Always include when available:
additionalPages for Subframe pages — pass the page ID from a pasted MCP link, or a specific variation page ID the user has referenced. Use codeContext for pages that only exist in the codebase.codeContext)codeContext)codeContextThe 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 codeContext, distinguish between components in the Subframe project vs not (list_components is the source of truth):
design_component (see Before designing a page), or inline their rendered JSX + Tailwind classes into codeContext.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.
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 (additionalPages), 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 additionalPages), 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.
When designing multiple related pages (flows, CRUD, etc.):
additionalPages. Have the user paste an MCP link to the variation they want as reference, or use get_flow_info with the flowId to enumerate the pages in the flow and ask which to use.flowName to group related pages together.edit_page — targeted edits to an existing pageUse edit_page for targeted changes to a specific Subframe page. Pass id, name, or url (call list_pages first if you need to find it) plus a description of the change. Follow the Grounding rules — the AI already has the current page code, so only paste outside reference code when the change depends on something the AI can't see. The edit applies immediately; present the returned pageUrl to the user.
edit_page vs design_pageedit_page: Targeted changes to an existing Subframe page. Fast and precise.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.
For design_page, present the returned flowUrl as a clickable markdown link. The flow 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 flow 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 flow 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 additionalPages or to read its current code with get_page_info), call wait_for_jobs with the jobId first, then get_flow_info with the flowId. Reading too early may return only the variations that have finished by that moment.
Internally track the flowId returned by design_page. Don't surface it to the user. Use it with get_flow_info for follow-up flow-level operations, or pass the same flowName on subsequent design_page calls to keep new variations grouped in the same flow.
For /subframe:develop, additionalPages, or edit_page, use specific page IDs the user has referenced (via pasted MCP link or while iterating in the editor), or call get_flow_info to look them up by name — design_page itself doesn't return individual page IDs since all variations land as separate pages on the canvas.
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.
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:
Keep it inline (in the page, not a component) if it:
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 componentUse 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. Paste 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. Apply the Subframe-vs-application rule from Preparing codeContext: leave references to components that already exist in this Subframe project as-is, inline anything else.name — the component name (PascalCase, e.g., "PrivacyToggle")projectId — usually inferred from .subframe/sync.jsonReturns 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 componentUse 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 design AI already has the current Subframe code — only paste outside reference code when the change depends on something the AI 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. 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: AI 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 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 snippetUse 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 showname — optional; defaults to "AI Generated Snippet"codeContext (optional) — raw outside code that grounds the snippet (the codebase implementation it should mirror, related types, the specific usage example it illustrates).references (optional) — IDs or names of existing Subframe components, pages, or snippets to use as design context (resolved server-side, no need to inline their code).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 snippetSame shape as edit_component but for snippets. Use when the embedded example needs to evolve alongside the component it 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 AI agents) when designing or implementing. There are two kinds:
Use design documents when:
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.
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. The Subframe design AI already has access to:
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:
Does NOT belong in a design doc:
edit_theme; don't paper over it in a doc.write_design_documentInputs:
content — markdownid (optional) — if editing an existing doc; omit to create a new onecomponentId (optional) — if creating a new component-scoped doctitle (optional) — for project-scoped docs only; ignored for component-scopedmode (optional) — replace (default) overwrites, append adds the new content after existingEmbed 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.
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.
edit_theme can doedit_theme descriptionWhen 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)@theme blocks (Tailwind v4) or :root { --color-... } declarations — globals.css, app.css, index.csstokens.ts, tokens.json, theme.ts, Style Dictionary exports)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 AI's accuracy on color, spacing, and typography depends on the exact strings.
Only let the design AI invent tokens when there is genuinely no codebase theme source. In that case, say so explicitly in the description.
Read the user's prompt and classify before invoking:
| Risk | Operations | Action | | --- | --- | --- | | Low | Adding tokens, tweaking values | Call directly; mention what changed after | | Medium | Renaming tokens, broad multi-token tweaks | Briefly note the scope (whole project) and call | | High | Anything 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.
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):
brand-primary with the desired color, and update brand-200 to alias brand-primary in the same call.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.
edit_themeedit_page instead.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 flow 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_flow({ id|name|url, projectId, deleteChildPages? }) — deletes a flow. Refuses if it contains pages. Use deleteChildPages: true to delete the flow plus every page inside 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.
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:
pageId, call get_flow_info with the flowId from the original design_page response — it returns the pages in the flow with names and IDs. Then use edit_page with that page's id for targeted changes, or call design_page with the page passed via additionalPages if they want a fresh set of options grounded in that direction.get_flow_info to look up page IDs by name), then call design_page with those pages via additionalPages and a description of the combination.design_page again with a refined description and any reference pages via additionalPages. Use the same flowName to keep related work grouped.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.
development
Implement Subframe designs with business logic. Use after designing with /subframe:design or when given a Subframe URL/page ID.
tools
Install Subframe into a codebase so you can implement designs locally. Sets up the CLI, syncs components, configures Tailwind and fonts. Only needed when you're ready to write code — you can design without installing.
tools
Bulk-import many components from an existing codebase to Subframe in one CLI batch. Use only when the user explicitly asks to use this exact skill. Available for select teams.
tools
Initialize Subframe in a new or existing project. Sets up the CLI, syncs components, configures Tailwind and fonts.