# Code generation
Source: https://docs.subframe.com/concepts/code-generation
How Subframe generates clean, production-ready React code.
Subframe generates production-ready React code deterministically—no LLM involved.
## How it works
When you make edits in Subframe, you're editing the underlying code directly. If it can't be translated to code, you can't do it in Subframe.
Subframe's code output is designed to be:
* **Deterministic** — The same design always produces the same code, without AI hallucinations or unexpected output.
* **Clean** — You get a minimal set of changes. No extra wrappers, one-off components, or redundant styles.
* **Instant** — Changes reflect real-time, without waiting for AI calls.
* **Presentational** — No API calls, state management, or business logic for developers to unwind during handoff.
## AI-ready code
Subframe's exported code is designed to work well with AI coding tools like Claude Code, Cursor, Codex, and Copilot. Our [MCP server](/guides/mcp-server) allows you to grant direct access to your designs to these tools.
### Tailwind CSS
Tailwind CSS is a popular, robust CSS framework that, in our testing, is very AI-friendly:
* Tailwind's utility classes make it easy to copy, paste, and refactor code after AI edits.
* A well-architected Tailwind theme give guardrails to AI tools, which dissuades it from inventing arbitrary colors or spacing. Subframe generates a robust Tailwind theme for you automatically.
### Headless components
Subframe uses headless components for interactivity because we believe [they are the future](https://www.subframe.com/blog/how-headless-components-became-the-future-for-building-ui-libraries) of building UI libraries. Headless components are unstyled components that help separate styling from functionality, giving you:
* **Flexibility** — Restyle without breaking behavior
* **Composability** — Mix and match primitives, without reinventing the wheel
* **Accessibility** — Built-in ARIA support and keyboard navigation
Subframe publishes an [open-source ↗](https://github.com/SubframeApp/subframe/tree/main/packages/subframe-core) package called `@subframe/core` that's a thin wrapper around [Radix ↗](https://www.radix-ui.com/). We use this package instead of Radix directly to support other headless libraries like [BaseUI](https://base-ui.com/) and [React Aria](https://react-aria.adobe.com/) in the future.
We also found headless components to be AI-friendly. Relying on an open-source, battle-tested library for component logic—rather than asking AI to build dropdowns, modals, and accordions from scratch—produces higher quality, more consistent code that is easier to modify with AI.
## Future support
We're exploring support for other frameworks and languages. If you're interested, [join our Slack community ↗](https://join.slack.com/t/subframecommunity/shared_invite/zt-380uma6dv-_lr7_bDLU5DJcoygfUYkeQ) and let us know what you'd like to see.
# Design to code workflow
Source: https://docs.subframe.com/concepts/design-to-code
How Subframe connects designs to your codebase.
Subframe is designed for building interfaces—the visual layer that designers own. Code generation in Subframe is **deterministic** and **purely presentational** (no API calls, state management, etc). Developers can later add application logic themselves using IDEs like Cursor or VS Code after export.
This guide explains Subframe's design-to-code workflow in detail.
#### Components vs pages
As a developer, you should treat **components** and **pages** differently:
* **Components** are your design system (e.g. buttons, inputs, cards). They are [synced via CLI](/concepts/syncing-components) to a folder in your codebase (default: `./ui/components`). Each component syncs as a directory — a source file that Subframe generates and you don't modify, plus a wrapper `index.tsx` that you can extend (see [Component directories](/upgrading/component-directories)).
* **Pages** are screens built from components. They are [exported](/concepts/exporting-pages) as copyable React code or using the MCP server and are meant to be modified after export.
In a nutshell, **components are synced, pages are exported**. This is because pages are typically modified with business logic like API calls after export. If your component needs logic after syncing, see our guide on best practices for [exporting components](/concepts/syncing-components).
## Design handoff
Suppose your designer creates a sign in page in Subframe:
When designers use Subframe, they are modifying the underlying code, which uses components that eventually live in your codebase. In this sign in page, the designer used the `Button` and `SocialSignInButton` components:
### Syncing components
When the designs are ready for handoff, sync the `Button` and `SocialSignInButton` code to your codebase by running the following command:
```bash npm theme={null}
npx @subframe/cli@latest sync Button SocialSignInButton
```
```bash yarn theme={null}
yarn dlx @subframe/cli@latest sync Button SocialSignInButton
```
```bash pnpm theme={null}
pnpx @subframe/cli@latest sync Button SocialSignInButton
```
```bash bun theme={null}
bunx @subframe/cli@latest sync Button SocialSignInButton
```
You can also sync all design system components at once:
```bash npm theme={null}
npx @subframe/cli@latest sync --all
```
```bash yarn theme={null}
yarn dlx @subframe/cli@latest sync --all
```
```bash pnpm theme={null}
pnpx @subframe/cli@latest sync --all
```
```bash bun theme={null}
bunx @subframe/cli@latest sync --all
```
The CLI sync command pulls `Button`, `SocialSignInButton`, and any other components into a specific folder in your codebase. By default this folder is located in the `./src/ui/components` folder but can be configured in the [project settings](/learn/projects/project-settings).
```
src/ui/
└─ components/
// [!code ++:6]
├─ Button/
│ ├─ Button.tsx
│ └─ index.tsx
└─ SocialSignInButton/
├─ SocialSignInButton.tsx
└─ index.tsx
```
Each component syncs as a directory: the source file Subframe generates (`Button.tsx`) plus a wrapper `index.tsx` that re-exports it and is the import entrypoint. See [Component directories](/upgrading/component-directories) for why, and how to add your own logic in the wrapper.
```tsx Button.tsx expandable theme={null}
import React from "react"
import * as SubframeCore from "@subframe/core"
import * as SubframeUtils from "../utils"
interface ButtonRootProps extends React.ButtonHTMLAttributes {
disabled?: boolean
variant?: "brand-primary" | "brand-secondary" | "destructive-primary"
size?: "large" | "medium" | "small"
children?: React.ReactNode
icon?: SubframeCore.IconName
iconRight?: SubframeCore.IconName
onClick?: (event: React.MouseEvent) => void
className?: string
}
const ButtonRoot = React.forwardRef(function ButtonRoot(
{
disabled = false,
variant = "brand-primary",
size = "medium",
children,
icon = null,
iconRight = null,
className,
type = "button",
...otherProps
}: ButtonRootProps,
ref,
) {
return (
)
})
export const Button = ButtonRoot
```
```tsx SocialSignInButton.tsx expandable theme={null}
import React from "react"
import * as SubframeUtils from "../utils"
interface SocialSignInButtonRootProps extends React.ButtonHTMLAttributes {
disabled?: boolean
variant?: "facebook" | "google" | "apple"
onClick?: (event: React.MouseEvent) => void
className?: string
}
const SocialSignInButtonRoot = React.forwardRef(
function SocialSignInButtonRoot(
{ disabled = false, variant = "facebook", className, type = "button", ...otherProps }: SocialSignInButtonRootProps,
ref,
) {
return (
)
},
)
export const SocialSignInButton = SocialSignInButtonRoot
```
For more information on syncing components, see the [Syncing components](/concepts/syncing-components) guide.
### Exporting pages
Subframe generates page code with stubs for business logic that need to be filled in:
```tsx SignInPage.tsx expandable theme={null}
import React from "react"
import { Button } from "@/ui/components/Button"
import { SocialSignInButton } from "@/ui/components/SocialSignInButton"
function SignInPage() {
return (
WelcomeLogin or sign up below
) => {
// [!code highlight:1]
// TODO: Implement Google sign in
}}
/>
) => {
// [!code highlight:1]
// TODO: Implement Apple sign in
}}
/>
)
}
export default SignInPage
```
The page code can be exported in two ways:
* **MCP server (recommended)** - install Subframe MCP server and ask your AI tool to integrate the page code directly using the [page link](/guides/mcp-server#using-the-mcp-server).
* **Copy/paste** - Open **Code** > **Inspect** in Subframe and copy the React code.
We recommend using the MCP server because you can also ask AI to add business logic or update the page code based on code changes. Once exported, refactor the code or add any business logic as needed.
```tsx SignInPage.tsx expandable theme={null}
import React from "react"
import { Button } from "@/ui/components/Button"
import { SocialSignInButton } from "@/ui/components/SocialSignInButton"
// [!code ++:2]
import { useNavigate } from "react-router-dom"
import { signInWithGoogle, signInWithApple } from "@/lib/auth"
function SignInPage() {
// [!code ++:1]
const navigate = useNavigate()
return (
)
}
export default SignInPage
```
## Iterating on designs
After the initial export, both designs and code will evolve. Here's how to handle changes:
### Designer makes changes
Subframe lets designers own the visual layer. When a change is made in Subframe, you can re-export the diff into your codebase.
**Component updates**
Run the sync command to get the latest component code:
```bash npm theme={null}
npx @subframe/cli@latest sync --all
```
```bash yarn theme={null}
yarn dlx @subframe/cli@latest sync --all
```
```bash pnpm theme={null}
pnpx @subframe/cli@latest sync --all
```
```bash bun theme={null}
bunx @subframe/cli@latest sync --all
```
If a component has breaking changes, TypeScript will throw errors wherever that component is used. This makes it easy for developers to find and update the affected code.
**Page updates**
The recommended way is to prompt your AI tool using the MCP server to update the page code:
```text theme={null}
Update the existing page to match the Subframe design at
https://app.subframe.com//design//edit.
Preserve all existing functionality unless the new design requires a change.
```
AI will fetch the latest design and merge it with your existing code, preserving your business logic.
### Developer makes changes
Often times, you will need to add or modify the component behavior after handoff.
The best practice is to add your logic to the component's [wrapper `index.tsx`](/concepts/syncing-components#wrapping-components). You probably don't need to sync code back to Subframe, since the source file that Subframe generates stays untouched and keeps receiving design updates. As a last resort, you can [disable sync](/concepts/syncing-components#disabling-sync) for the source file itself.
In the near future, we will add CLI commands to sync component code back to Subframe. If you are interested in this feature, please let us know by [joining our Slack community](https://join.slack.com/t/subframecommunity/shared_invite/zt-380uma6dv-_lr7_bDLU5DJcoygfUYkeQ).
To import an existing page code back to Subframe, you can take a screenshot of the page and ask Subframe AI to recreate the design in Subframe.
## Beyond handoff
Once a feature ships, page designs may drift from code. That's okay — page designs are artifacts for communication. Once the feature is live, the code is the source of truth.
Components are different. They stay synced, so Subframe remains the single source of truth for your design system. When a designer updates a button or input, you re-run sync and every page using that component gets the update.
# Exporting pages
Source: https://docs.subframe.com/concepts/exporting-pages
Export page designs to your codebase and add business logic.
Pages are screens built from components. Design the layout and UI in Subframe, then export and add business logic like API calls, state management, and routing in code.
## Using the MCP server (recommended)
The fastest way to export pages is through AI assistants connected to Subframe's [MCP server](/guides/mcp-server). Paste a page link and let AI integrate the design directly:
```text theme={null}
Implement the design at https://app.subframe.com/PROJECT_ID/design/DESIGN_ID/edit.
Wire up relevant app logic where applicable.
```
AI fetches the latest design and generates code that fits your existing project structure. You can also ask AI to update existing pages when designs change:
```text theme={null}
Update the existing page to match the Subframe design at
https://app.subframe.com/PROJECT_ID/design/DESIGN_ID/edit.
Preserve all existing functionality unless the new design requires a change.
```
## Copy and paste
You can also copy page code directly from Subframe:
1. Open the page in Subframe
2. Click **Code** > **Inspect**
3. Copy the React code
To export just part of a page, select the elements you want and the code panel will show only your selection.
# Syncing components
Source: https://docs.subframe.com/concepts/syncing-components
Keep your design system in sync between Subframe and your codebase.
Subframe is the single source of truth for your design system. Components you build in Subframe sync directly to your codebase — same code, same behavior.
## Using the CLI
Sync all components to your codebase with the CLI:
```bash theme={null}
npx @subframe/cli@latest sync --all
```
To sync specific components:
```bash theme={null}
npx @subframe/cli@latest sync Button Alert Accordion
```
The CLI pulls all components into your configured directory in your [project settings](/learn/projects/project-settings).
Note that **sync is one-way** from Subframe to your codebase. Local changes to synced files will be overwritten on the next sync. If you don't want to overwrite local changes, you can [disable sync](/concepts/syncing-components#disabling-sync) for the component. In the future, we'll support syncing code changes back to Subframe.
## Adding business logic
Subframe components are presentational — they handle layout, styling, and UI interactions like accordion open/close, but avoid adding product-specific application logic like API calls or state management.
Under the hood, we use [Radix](https://www.radix-ui.com/) for interactive behavior, which gives you accessible, well-tested interactions out of the box.
### Adding handlers and attributes
All components pass through props to the top-level element. This means you can add `data-*` attributes, `tabIndex`, `onClick`, or any other standard HTML attribute directly to any component.
```tsx theme={null}
```
### Using slots to access nested props
It's common for components to contain interactive elements within them—like a card with a button. Slots let you access these nested elements' props:
```tsx theme={null}
import { TrackCard } from "@/ui/components/TrackCard"
function MyTrackCard({ onFavorite }) {
return (
{
// Add any business logic here
}}
/>
}
/>
)
}
```
The flexibility of slots lets you pass handlers, custom icons, or even swap in entirely different components. You can create [slots ↗](https://www.youtube.com/watch?v=7C9cRkvKbQY) using Subframe's component editor.
### Wrapping components
For logic not supported by Radix or slots, wrap the Subframe component.
Each component syncs as a directory: `components/Button/Button.tsx` is the source Subframe generates, and `components/Button/index.tsx` is a wrapper that re-exports it. The wrapper is yours to edit — add your logic there and mark it with `@subframe/sync-disable` so the CLI won't overwrite it:
```tsx components/Button/index.tsx theme={null}
// @subframe/sync-disable
import { Button as ButtonComponent } from "./Button"
export function Button({ onSubmit, ...props }) {
const [loading, setLoading] = useState(false)
async function handleClick() {
setLoading(true)
await onSubmit()
setLoading(false)
}
return
}
```
`Button.tsx` keeps syncing, so Subframe stays the source of truth for the visual layer — while `index.tsx` gives you the flexibility of code to add any business logic. Anything importing `@/ui/components/Button` gets your wrapped version, with no import changes.
See [Component directories](/upgrading/component-directories) for the full layout and migration details.
In the future, we'll let you add component logic directly in Subframe.
### Disabling sync
Add the `@subframe/sync-disable` comment anywhere in a file to tell the CLI to skip it on the next sync:
```tsx theme={null}
// @subframe/sync-disable
```
The marker applies per file, so which file you add it to matters:
* **The wrapper `index.tsx`** — the usual choice once you've added [wrapping logic](/concepts/syncing-components#wrapping-components). Your wrapper is preserved, while `Button.tsx` keeps receiving Subframe's updates.
* **The main `Button.tsx`** — a last resort that fully freezes the file Subframe generates, so the component stops receiving updates. Prefer wrapping in `index.tsx` instead.
You can alternatively copy/paste component code directly from Subframe if you need a one-off version.
# Astro
Source: https://docs.subframe.com/frameworks/astro
Install Subframe in Astro projects.
This guide assumes that you have already setup React and Tailwind CSS in your Astro app. If you haven't, please follow
the [Astro](https://docs.astro.build/en/install-and-setup/#install-from-the-cli-wizard), [Astro
React](https://docs.astro.build/en/guides/integrations-guide/react/), and [Tailwind
CSS](https://v3.tailwindcss.com/docs/installation/using-postcss) guides.
### Install Subframe
Run the following command in the root of your repository to install Subframe and configure your project:
```bash npm theme={null}
npx @subframe/cli@latest init
```
```bash yarn theme={null}
yarn dlx @subframe/cli@latest init
```
```bash pnpm theme={null}
pnpx @subframe/cli@latest init
```
```bash bun theme={null}
bunx --bun @subframe/cli@latest init
```
### Configure Astro to use import aliases
```json tsconfig.json {3-8} theme={null}
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": [
"./src/*"
]
},
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true+
},
"include": ["src"]
}
```
### Troubleshooting
If you run into any issues with the installation, refer to the [manual installation guide](/frameworks/manual) for all of the steps needed to get Subframe working in your codebase.
```
```
# Manual installation
Source: https://docs.subframe.com/frameworks/manual
Step-by-step guide to manually install Subframe without the CLI.
We recommend using `@subframe/cli@latest` to initialize your project. If you're troubleshooting issues, you can follow the
steps below instead.
This guide will walk through all of the steps needed to get Subframe working in your codebase.
We'll assume you have a project with the following file structure:
```
my-app/
|-- src/
| |-- main.tsx
| `-- styles.css
|-- index.html
|-- package.json
`-- tsconfig.json
```
```json .subframe/sync.json theme={null}
{
"directory": "./src/ui",
"importAlias": "@/ui/*",
"projectId": "YOUR_PROJECT_ID"
}
```
The file contains three settings:
* `directory`: The directory where your Subframe components will be synced to.
* `importAlias`: The import alias for your Subframe components.
* `projectId`: The project ID for your Subframe project.
You can find `YOUR_PROJECT_ID` in your URL in the Subframe app: `https://app.subframe.com//rest/of/url`
For example, if the URL you see is `https://app.subframe.com/abcdef123456/library`, your project ID is `abcdef123456`.
Subframe depends on `@subframe/core`. Run the following command to install the dependencies:
```bash npm theme={null}
npm install @subframe/core@latest
```
```bash yarn theme={null}
yarn add @subframe/core@latest
```
```bash pnpm theme={null}
pnpm add @subframe/core@latest
```
```bash bun theme={null}
bun add @subframe/core@latest
```
Run the following command to sync your Subframe components and theme to your codebase:
```bash npm theme={null}
npx @subframe/cli@latest sync --all
```
```bash yarn theme={null}
yarn dlx @subframe/cli@latest sync --all
```
```bash pnpm theme={null}
pnpx @subframe/cli@latest sync --all
```
```bash bun theme={null}
bunx --bun @subframe/cli@latest sync --all
```
The Subframe CLI will look for the project settings in your `.subframe` directory. It may ask you for an access token to authenticate with your project.
After syncing, Subframe creates theme configuration files. You need to import these into your Tailwind setup.
You can configure which version of Tailwind CSS you're using in your [project settings](/learn/projects/project-settings).
Extend your `tailwind.config.js` with your Subframe theme:
```javascript tailwind.config.js {11-13} theme={null}
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./index.html", "./src/**/*.{js,jsx,ts,tsx}"],
theme: {
extend: {},
},
plugins: [],
// [!code ++]
presets: [require("./src/ui/tailwind.config")],
}
```
Anytime you update your theme in Subframe, you'll need to rerun sync again to update the theme in your codebase. You can view the generated `tailwind.config.js` file in your [Subframe theme](https://app.subframe.com/library?component=theme\&showThemeModalCSSType=tailwindV3).
Import Subframe's generated `theme.css` in your global CSS file (typically `index.css`, `styles.css`, or `globals.css`):
```css src/styles.css theme={null}
@import "tailwindcss";
// [!code ++]
@import "./ui/theme.css";
/* Remaining code in your global CSS file... */
```
Anytime you update your theme in Subframe, you'll need to rerun sync again to update the theme in your codebase. You can view the generated `theme.css` file in your [Subframe theme](https://app.subframe.com/library?component=theme\&showThemeModalCSSType=tailwindV4).
# Monorepo
Source: https://docs.subframe.com/frameworks/monorepo
Set up Subframe in a monorepo to share components across multiple apps.
When building complex apps with multiple frontends, it can be useful to use a monorepo setup so you can import the same components into many different apps.
For this guide we'll assume that you're using [Turborepo](https://turbo.build/repo/docs) as your monorepo and
[Next.js](https://nextjs.org/) for your frontend. Monorepo-specific functionality should work with any package manager
or framework that supports [workspaces](https://docs.npmjs.com/cli/v7/using-npm/workspaces?v=true). For more
information visit the [manual installation guide](/frameworks/manual) and the framework guide for your frontend of
choice.
### Create a package for Subframe
Since we're using Turborepo, we can use the `turbo generate` command to create a package in our monorepo where we'll be importing our Subframe components. We'll call the package `@repo/subframe`.
If you're using a different monorepo framework or just plain workspaces, you can just duplicate an existing package
directory and change the contents accordingly.
```bash npm theme={null}
npx turbo generate workspace --name @repo/subframe --type package
```
```bash yarn theme={null}
yarn turbo generate workspace --name @repo/subframe --type package
```
```bash pnpm theme={null}
pnpm turbo generate workspace --name @repo/subframe --type package
```
```bash bun theme={null}
bunx turbo generate workspace --name @repo/subframe --type package
```
On the interactive prompt, make sure to select your monorepo's TypeScript and ESLint config packages as `devDependencies` for the next step.
We'll also want to integrate our monorepo's TypeScript and ESLint configuration, so we'll create the following two files:
```javascript packages/subframe/eslint.config.mjs theme={null}
import { config } from "@repo/eslint-config/react-internal"
/** @type {import("eslint").Linter.Config} */
export default config
```
```json packages/subframe/tsconfig.json theme={null}
{
"extends": "@repo/typescript-config/react-library.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["ui"],
"exclude": ["node_modules", "dist"]
}
```
### Install Subframe
We now want to install and configure Subframe in our new package. Let's do so by running the `@subframe/cli@latest init` command.
```bash npm theme={null}
cd packages/subframe && npx @subframe/cli@latest init --sync --dir ui
```
```bash yarn theme={null}
cd packages/subframe && yarn dlx @subframe/cli@latest init --sync --dir ui
```
```bash pnpm theme={null}
cd packages/subframe && pnpx @subframe/cli@latest init --sync --dir ui
```
```bash bun theme={null}
cd packages/subframe && bunx --bun @subframe/cli@latest init --sync --dir ui
```
Make sure that you set the import alias to `@repo/subframe/*` during the initialization process. This will make sure that the code imports from Subframe will work within our UI apps later.
If you forgot to set it, simply edit the importAlias key in your `.subframe/sync.json` file and run `@subframe/cli@latest init` again to sync the changed import alias to your Subframe project.
```json packages/subframe/.subframe/sync.json {3} theme={null}
{
"directory": "ui",
"importAlias": "@repo/subframe/*"
}
```
### Export Subframe components from your package
To be able to import our components from this package in other apps in the monorepo, we'll have to make sure they can find the components within our package, as well as Subframe's TailwindCSS config.
We can easily achieve this by setting the `exports` field in our `package.json` accordingly:
```json packages/subframe/package.json {5-10} theme={null}
{
"name": "@repo/subframe",
"version": "0.0.0",
"private": true,
"exports": {
".": "./ui/index.ts",
"./components/*": "./ui/components/*/index.tsx",
"./layouts/*": "./ui/layouts/*/index.tsx",
"./tailwind-config": "./ui/tailwind.config.js"
},
"dependencies": {
"@subframe/core": "^1.141.0"
},
"devDependencies": {
"@repo/eslint-config": "*",
"@repo/typescript-config": "*"
}
}
```
Components and page layouts sync as directories (e.g. `components/Button/`), so each subpath export points at the directory's `index.tsx` wrapper rather than a flat `.tsx` file. Subpath patterns are literal — they don't resolve a directory to its `index`, so the `/index.tsx` must be explicit. See [Component directories](/upgrading/component-directories).
### Install the `@repo/subframe` package into your app
To install your local package to your app, add the following line to your frontend apps' `dependencies`:
```text apps/web/package.json (npm and yarn) theme={null}
"@repo/subframe": "*"
```
```text apps/web/package.json (pnpm and bun) theme={null}
"@repo/subframe": "workspace:*"
```
Then run your package manager's `install` command to link your dependencies:
```bash npm theme={null}
npm install
```
```bash yarn theme={null}
yarn install
```
```bash pnpm theme={null}
pnpm install
```
```bash bun theme={null}
bun install
```
### Set up TailwindCSS in your frontend app
We assume you've already set up Tailwind CSS for your frontend app. If you haven't done so, follow the [Tailwind
CSS](https://v3.tailwindcss.com/docs/installation/using-postcss) guide.
The key change here is that we need to import Subframe's tailwind config `preset` from our local package, as well as include the Subframe component source files in tailwind's `content` array:
```javascript apps/web/tailwind.config.js {8, 14} theme={null}
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
"./app/**/*.{js,ts,jsx,tsx}",
"./ui/**/*.{js,ts,jsx,tsx}",
"../../packages/subframe/ui/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
presets: [require("@repo/subframe/tailwind-config")],
}
```
We assume you've already set up Tailwind CSS v4 for your frontend app. If you haven't done so, follow the [Tailwind
CSS v4](https://tailwindcss.com/docs/installation) guide.
With Tailwind v4, you need to add two directives to your global CSS file (typically `globals.css`, `index.css`, or `styles.css`):
```css apps/web/globals.css {2,3} theme={null}
@import "tailwindcss";
@import "../../packages/subframe/ui/theme.css";
@source "../../packages/subframe/**";
```
* The `@import` directive extends your styles with the Subframe theme
* The `@source` directive ensures Tailwind discovers the Subframe component files during class name detection
Make sure to adjust the relative paths based on your monorepo structure.
### Use Subframe components in your app
You can now import Subframe components directly into your app. Since we changed the import alias to the name of your local package, all imports generated by the Subframe app will resolve to the components in your `@repo/subframe` package.
# Next.js
Source: https://docs.subframe.com/frameworks/nextjs
Install Subframe in Next.js projects.
This guide assumes that you have already setup Tailwind CSS in your Next.js app. If you haven't, first follow the
[Next.js](https://nextjs.org/docs/app/getting-started/installation) and [Tailwind
CSS](https://v3.tailwindcss.com/docs/installation/using-postcss) installation guides.
### Install Subframe
Run the following command in the root of your repository to install Subframe and configure your project:
```bash npm theme={null}
npx @subframe/cli@latest init
```
```bash yarn theme={null}
yarn dlx @subframe/cli@latest init
```
```bash pnpm theme={null}
pnpx @subframe/cli@latest init
```
```bash bun theme={null}
bunx --bun @subframe/cli@latest init
```
### Troubleshooting
If you run into any issues with the installation, refer to the [manual installation guide](/frameworks/manual) for all of the steps needed to get Subframe working in your codebase.
# Vite
Source: https://docs.subframe.com/frameworks/vite
Install Subframe in Vite projects.
This guide assumes that you have already setup React and Tailwind CSS in your Vite app. If you haven't, please follow
the [Vite](https://vite.dev/guide/#scaffolding-your-first-vite-project) and [Tailwind
CSS](https://v3.tailwindcss.com/docs/installation/using-postcss) installation guides.
### Install Subframe
Run the following command in the root of your repository to install Subframe and configure your project:
```bash npm theme={null}
npx @subframe/cli@latest init
```
```bash yarn theme={null}
yarn dlx @subframe/cli@latest init
```
```bash pnpm theme={null}
pnpx @subframe/cli@latest init
```
```bash bun theme={null}
bunx --bun @subframe/cli@latest init
```
### Configure Vite to use import aliases
1. Configure the `compilerOptions` in the `tsconfig.app.json` file so Typescript understands your import aliases.
```json tsconfig.app.json {3-8} theme={null}
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": [
"./src/*"
]
},
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}
```
2. Run `npm install -D @types/node` and then update `vite.config.ts` so Vite can resolve paths without error:
```tsx vite.config.ts {7-11} theme={null}
import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"
import { resolve } from "node:path"
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": resolve(__dirname, "./src"),
}
}
})
```
### Troubleshooting
If you run into any issues with the installation, refer to the [manual installation guide](/frameworks/manual) for all of the steps needed to get Subframe working in your codebase.
# Accessibility
Source: https://docs.subframe.com/guides/accessibility
Build accessible interfaces with Subframe components.
Subframe components are accessible by default. We use [Radix](https://www.radix-ui.com/) under the hood, which provides built-in keyboard navigation, focus management, and ARIA attributes.
## Adding accessibility attributes
All components pass through props to the top-level element. You can add any ARIA attribute directly:
```tsx theme={null}
```
Common attributes you might use:
* `aria-label` — Describes the element for screen readers
* `aria-describedby` — References an element that describes this one
* `aria-required` — Indicates a required form field
* `aria-invalid` — Indicates validation errors
* `aria-expanded` — Indicates expandable content state
* `aria-hidden` — Hides decorative elements from screen readers
* `role` — Overrides the semantic role of an element
For more complex accessibility needs, you can also add logic in the component's [wrapper `index.tsx`](/concepts/syncing-components#wrapping-components) for code reuse.
# CLI in CI & agents
Source: https://docs.subframe.com/guides/cli-automation
Run the Subframe CLI non-interactively from CI pipelines and AI coding agents.
The Subframe CLI normally prompts for anything it's missing. When stdin is not a
TTY — in CI, a Docker build, or an AI coding agent — it switches to
**non-interactive mode** automatically: instead of hanging on a prompt it uses
the value from a flag, falls back to a safe default, or exits non-zero with a
message telling you exactly which flag to pass.
You can also force this mode in an interactive terminal with `--yes`.
## Authentication
Provide a token without the interactive login. Generate one from the
[CLI auth page ↗](https://app.subframe.com/cli/auth).
Preferred for CI and agents — the token never appears in the process list or
shell history.
```bash theme={null}
export SUBFRAME_AUTH_TOKEN=""
npx @subframe/cli@latest sync --all
```
```bash theme={null}
npx @subframe/cli@latest sync --all --auth-token ""
```
The `--auth-token` flag takes precedence over `SUBFRAME_AUTH_TOKEN` if both are
set. Either is verified and cached on first use, so later commands in the same
environment reuse it without re-supplying or re-verifying it.
## Global flags
| Flag | Description |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `-y, --yes` | Accept the safe defaults and never prompt. Implied automatically when stdin is not a TTY. |
| `--non-interactive` | Strict mode: never prompt **and** never assume a default — fail if any required value is missing. Use this when you want a run to error rather than guess. |
| `--json` | Print a machine-readable JSON result to stdout. Human logs go to stderr. Implies non-interactive. |
Every command exits non-zero on failure (and, with `--json`, prints an
`{ "ok": false, "error": ... }` envelope to stdout), so you can rely on either
the exit code or the JSON in a pipeline.
## Examples
### Sync components in CI
The most common case. After `init` has been run once and committed
`.subframe/sync.json`, syncing only needs a token:
```bash theme={null}
SUBFRAME_AUTH_TOKEN="" npx @subframe/cli@latest sync --all
```
### Initialize an existing project non-interactively
Pass the values the CLI would otherwise prompt for:
```bash theme={null}
SUBFRAME_AUTH_TOKEN="" npx @subframe/cli@latest init \
--yes \
--projectId \
--css-type tailwind-v4 \
--dir ./src \
--css-path ./src/app/globals.css \
--no-install
```
`init` flags worth knowing:
* `--projectId ` — required when your account has more than one project (otherwise the CLI can't choose for you and will list the available ids).
* `--css-type ` — required if the CLI can't detect your Tailwind version.
* `--dir ` — where components sync to.
* `--alias ` — the import alias to use. Must end with `/*` (e.g. `@/ui/*`) so it matches every file in the directory; the CLI rejects an alias without it.
* `--no-install`, `--no-sync`, `--no-tailwind` — skip a step that would otherwise prompt. The matching `--install` / `--sync` / `--tailwind` force it on.
* `--no-update-import-alias` — don't change the import alias stored in your Subframe project.
### Scaffold a brand new project
Creating a project from scratch can't guess your framework or name, so pass them:
```bash theme={null}
SUBFRAME_AUTH_TOKEN="" npx @subframe/cli@latest init \
--template nextjs \
--name my-app \
--projectId
```
### Parse the result
With `--json`, stdout carries only the result object:
```bash theme={null}
npx @subframe/cli@latest sync --all --json
# { "ok": true, "command": "sync", "projectId": "...", "components": "all", ... }
```
# Component docs
Source: https://docs.subframe.com/guides/component-docs
Find documentation for Subframe components and their underlying libraries.
## Viewing component docs
Each component in your design system has documentation for its React props, source code, and interactive examples.
1. Open your project and select **Components** under **Design System** in the left sidebar
2. Click on the component you want
3. View the props, source code, and examples
## Radix docs
Subframe uses [Radix](https://www.radix-ui.com/) for interactive component behavior. For components like Accordion, Dialog, or Tabs, you can find detailed prop documentation and examples in the [Radix documentation](https://www.radix-ui.com/primitives/docs/overview/introduction).
## Tailwind docs
All styling uses Tailwind CSS utility classes. Refer to the [Tailwind v3 documentation](https://v3.tailwindcss.com/docs) (or [v4 documentation](https://tailwindcss.com/docs)) for available classes and configuration options.
## Subframe core
The `@subframe/core` package is open source. You can browse the source code at [github.com/SubframeApp/subframe](https://github.com/SubframeApp/subframe/tree/main/packages/subframe-core).
# Dark mode
Source: https://docs.subframe.com/guides/dark-mode
Add dark mode to your project using Subframe's built-in theme support
Subframe has built-in dark mode support. Enable it in your theme to define light and dark values side by side, then sync to get a fully configured Tailwind setup.
## Enable dark mode
1. Open **Theme** under **Design System** in the left sidebar
2. At the top of the theme page, click **Add dark mode**
3. Each token now shows light and dark values — edit the dark values to define your dark palette
4. Preview your components and pages in both light and dark mode using the sun/moon toggle in the editor toolbar
Dark mode colors typically invert the scale: light mode's lightest shade becomes dark mode's darkest, and vice versa.
To remove dark mode, click **⋯** in the theme header and select **Remove dark mode**. This deletes all dark overrides. You can undo this using version history.
## How the generated code works
When dark mode is enabled, Subframe generates theme tokens as CSS variables so light and dark values can switch at runtime.
The CLI syncs two files:
* **`tailwind.config.js`** — references CSS variables instead of hardcoded values, with `darkMode: 'selector'` enabled
* **`theme.css`** — defines `:root` variables for light mode and `.dark` overrides for dark mode
```js tailwind.config.js theme={null}
module.exports = {
darkMode: 'selector',
theme: {
extend: {
colors: {
"brand-primary": "var(--color-brand-primary)",
// ... all your color tokens
},
},
},
}
```
```css theme.css theme={null}
:root {
--color-brand-primary: rgb(26 26 26);
--color-default-background: rgb(252 252 252);
/* ... light mode values */
}
.dark {
--color-brand-primary: rgb(212 212 212);
--color-default-background: rgb(10 10 10);
/* ... dark mode overrides */
}
```
Import `theme.css` in your global stylesheet or entry point:
```css globals.css theme={null}
@import "./subframe/theme.css";
```
The generated `theme.css` includes a `@custom-variant` for dark mode and a `.dark` block with overrides:
```css theme.css theme={null}
@custom-variant dark (&:where(.dark, .dark *));
@theme {
--color-brand-primary: rgb(26 26 26);
--color-default-background: rgb(252 252 252);
/* ... light mode values */
}
.dark {
--color-brand-primary: rgb(212 212 212);
--color-default-background: rgb(10 10 10);
/* ... dark mode overrides */
}
```
## Sync to code
Run the CLI to sync your theme (including dark mode) to your codebase:
```bash npm theme={null} theme={null} theme={null} theme={null} theme={null}
npx @subframe/cli@latest sync --all
```
```bash yarn theme={null} theme={null} theme={null} theme={null} theme={null}
yarn dlx @subframe/cli@latest sync --all
```
```bash pnpm theme={null} theme={null} theme={null} theme={null} theme={null}
pnpx @subframe/cli@latest sync --all
```
```bash bun theme={null} theme={null} theme={null} theme={null} theme={null}
bunx @subframe/cli@latest sync --all
```
## Enable dark mode in your app
To activate dark mode, set the `dark` class on the `` element. Here are a few ways to accomplish that:
### Next.js with next-themes
```bash theme={null}
npm install next-themes
```
```tsx app/layout.tsx theme={null}
import { ThemeProvider } from "next-themes"
export default function RootLayout({ children }) {
return (
{children}
)
}
```
### React with context
```tsx ThemeProvider.tsx theme={null}
import { createContext, useContext, useEffect, useState } from "react"
const ThemeContext = createContext({ theme: "light", toggleTheme: () => {} })
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light")
useEffect(() => {
const root = window.document.documentElement
root.classList.remove("light", "dark")
root.classList.add(theme)
}, [theme])
const toggleTheme = () => setTheme(theme === "light" ? "dark" : "light")
return {children}
}
export const useTheme = () => useContext(ThemeContext)
```
### Theme toggle button
```tsx theme={null}
import { useTheme } from "next-themes"
export function ThemeToggle() {
const { theme, setTheme } = useTheme()
return
}
```
## Best practices
Always test your application in both light and dark modes. Check for:
* Sufficient contrast ratios (use browser DevTools)
* Readability of all text
* Visibility of borders and dividers
* Proper styling of interactive states
Use the user's system preference as the default:
```tsx theme={null}
```
# MCP server
Source: https://docs.subframe.com/guides/mcp-server
Let AI coding assistants directly access and edit your Subframe designs and documentation.
The Subframe MCP server gives AI coding assistants like Claude Code, Cursor, and Codex direct access to your Subframe projects. AI can read, design, and delete pages, components, and snippets, screenshot pages and components, read prototypes, write design documents, edit the theme, and search the Subframe docs.
A separate Subframe Docs MCP server gives AI access to Subframe documentation (this site).
## Installation
```bash theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
claude plugin marketplace add https://github.com/SubframeApp/subframe && claude plugin install subframe@subframe
```
The Subframe plugin for Claude Code sets up the MCP server and agent skills in one install.
Keep the Subframe plugin up to date automatically:
1. Run `/plugin` to open the plugin manager
2. Select the **Marketplaces** tab
3. Choose the **subframe** marketplace
4. Select **Enable auto-update**
Run `/mcp` to check that the Subframe MCP server is connected, then try asking Claude Code to use Subframe.
Follow the [Working with AI agents](/learn/guides/working-with-ai-agents) guide to design and implement your first page.
1. Go to [**Customize > Connectors**](https://claude.ai/customize/connectors)
2. Click and select **Add custom connector**
3. Set the name to **Subframe**
4. For **Remote MCP Server URL**, paste the following URL:
```
https://mcp.subframe.com/mcp
```
5. Click **Add**
Find the Subframe connector in your connectors list and click **Connect**. Follow the instructions on the Subframe website to complete authentication.
Follow the [Working with AI agents](/learn/guides/working-with-ai-agents) guide to design and implement your first page.
Add the Subframe MCP servers to Cursor. You'll be prompted to authenticate via OAuth.
* [Add Subframe MCP server to Cursor ↗](https://cursor.com/en-US/install-mcp?name=subframe\&config=eyJ1cmwiOiJodHRwczovL21jcC5zdWJmcmFtZS5jb20vbWNwIn0%3D)
* (Optional) [Add Subframe Docs MCP server to Cursor ↗](https://cursor.com/en-US/install-mcp?name=subframe-docs\&config=eyJ1cmwiOiJodHRwczovL2RvY3Muc3ViZnJhbWUuY29tL21jcCJ9)
If the install links don't work, make the following changes to `~/.cursor/mcp.json`.
```json ~/.cursor/mcp.json theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
{
"mcpServers": {
"subframe": {
"url": "https://mcp.subframe.com/mcp"
},
"subframe-docs": {
"url": "https://docs.subframe.com/mcp"
}
}
}
```
Cursor will handle OAuth authentication automatically when you first connect.
Agent skills are guided workflows that teach Cursor how to best use Subframe. Install them with:
```bash theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
npx skills add https://github.com/SubframeApp/subframe --skill '*' -g --agent cursor --yes
```
Check that the Subframe MCP server has successfully connected in **Cursor Settings** > **MCP**, then try asking Cursor to use Subframe.
Follow the [Working with AI agents](/learn/guides/working-with-ai-agents) guide to design and implement your first page.
Run the following commands to add the Subframe MCP servers and authenticate:
```bash theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
codex mcp add subframe --url https://mcp.subframe.com/mcp && codex mcp add subframe-docs --url https://docs.subframe.com/mcp && codex mcp login subframe
```
If the commands above don't work, add the following to `~/.codex/config.toml`:
```toml ~/.codex/config.toml theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
[mcp_servers.subframe]
url = "https://mcp.subframe.com/mcp"
[mcp_servers.subframe-docs]
url = "https://docs.subframe.com/mcp"
```
Then authenticate:
```bash theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
codex mcp login subframe
```
Agent skills are guided workflows that teach Codex how to best use Subframe. Install them with:
```bash theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
npx skills add https://github.com/SubframeApp/subframe --skill '*' -g --agent codex --yes
```
Run `/mcp` in Codex to check that the Subframe MCP server is connected, then try asking Codex to use Subframe.
Follow the [Working with AI agents](/learn/guides/working-with-ai-agents) guide to design and implement your first page.
Configure your MCP client to connect to the Subframe MCP server:
* **URL:** `https://mcp.subframe.com/mcp`
* **Transport:** HTTP
* **Authentication:** OAuth (your client will handle the authentication flow)
Optionally add the Subframe Docs MCP server for documentation access:
* **URL:** `https://docs.subframe.com/mcp`
* **Transport:** HTTP
* **Authentication:** None required
If your client supports the [Agent Skills](https://agentskills.io) standard, install the Subframe skills with:
```bash theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
npx skills add https://github.com/SubframeApp/subframe --skill '*' --agent '*' --yes
```
Restart your MCP client, then check that the Subframe MCP server has successfully connected and try asking your AI assistant to use Subframe.
Follow the [Working with AI agents](/learn/guides/working-with-ai-agents) guide to design and implement your first page.
## Using the MCP server
Once configured, your AI assistant can access Subframe automatically when you prompt or paste an MCP link to a page from the Code panel.
You can get the MCP link for any design by either:
* Copying the link from the browser address bar
* Copying the link under **Code** > **Inspect** in Subframe
## Available tools
The Subframe MCP server exposes tools across several categories. Most read tools take a `projectId`. If omitted, the first project the user has access to is used.
### Discovery
| Tool | Description | Returns |
| --------------------- | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| `list_projects` | Lists all projects you have access to | Array of `projectId`, `name`, `teamId`, `teamName` |
| `generate_auth_token` | Generates a CLI auth token for a team | `authToken` |
| `get_project_info` | Project metadata plus all project-level design documents | `id`, `name`, `docs` (array of `id`, `title`, `contents`) |
| `search_docs` | Searches the Subframe documentation (this site) for product and workflow questions | `results` (matching documentation content with titles and links) |
### Pages
| Tool | Description | Key inputs | Returns |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `list_pages` | List all pages with the flow each belongs to | `projectId` | Array of `id`, `name`, `url`, `lastModifiedAt`, `flowId`, `flowName` |
| `get_page_info` | Generated React/Tailwind code for a page. Pass `includeNodeIds: true` to get a `data-node-id` on every element for use with `edit_page` | `id`/`name`/`url`, `projectId`, `includeNodeIds?` | `id`, `name`, `lastModifiedAt`, `files` |
| `design_page` | Generates 1-4 page variations as an asynchronous background job. Successful variations land as pages in a flow as they finish; `wait_for_jobs` reports how many were applied | `description`, `variations`, `flowName`, `projectId`, `references?`, `sourcePageId?` | `flowId`, `flowUrl`, `jobId` |
| `edit_page` | Apply a targeted edit to one node of an existing page — `replace` the node and its subtree, `insert-above`/`insert-below` a new sibling, or `delete` it. Call `get_page_info` with `includeNodeIds: true` first to get each element's `data-node-id`. Applied immediately; `appliedCode` carries each element's `data-node-id` for follow-up edits | `id`/`name`/`url`, `nodeId`, `operation`, `code`, `projectId` | `pageUrl`, `appliedCode?`, `warnings?` |
| `screenshot_page` | Renders a page and returns a screenshot so AI can check the result against the intended design. Use after `design_page`/`edit_page`, once background jobs finish. Pass `nodeId` (from `get_page_info` with `includeNodeIds: true`) to capture one element, `breakpointId` (from `get_theme`) to render a responsive breakpoint, or `offsetX`/`offsetY` to cover a page taller or wider than one capture | `id`/`name`/`url`, `projectId`, `darkMode?`, `nodeId?`, `breakpointId?`, `offsetX?`, `offsetY?` | Screenshot image, `pageId`, `width`, `height` |
| `delete_page` | Delete 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 | `id`/`name`/`url`, `projectId`, `force?` | `deletedId`, `deletedName`, `references` |
### Components
| Tool | Description | Key inputs | Returns |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------- |
| `list_components` | List all components | `projectId` | Array of `id`, `name`, `url`, `lastModifiedAt` |
| `get_component_info` | Generated code plus attached design document | `id`/`name`/`url`, `projectId` | `id`, `name`, `lastModifiedAt`, `files`, `designDocuments` |
| `screenshot_component` | Renders a component's variants and interaction states and returns a screenshot so AI can validate the result against the intended design. Use after `design_component`/`edit_component`, once background jobs finish. Pass `breakpointId` (from `get_theme`) to render a responsive breakpoint | `id`/`name`/`url`, `projectId`, `darkMode?`, `breakpointId?` | Screenshot image, `componentId`, `width`, `height` |
| `design_component` | Designs a new component as an asynchronous background job | `description`, `name`, `projectId`, `references?` | `componentId`, `componentUrl`, `jobId` |
| `edit_component` | Edit an existing component as an asynchronous background job. Propagates to every page using the component | `id`/`name`/`url`, `description`, `projectId`, `references?` | `componentUrl`, `jobId` |
| `delete_component` | Delete a component or custom page layout. Detachable components detach their instances; non-detachable ones delete instances; page layouts clear assignments. Some built-in components can't be deleted (`force` does not override). Refuses by default if in use — pass `force: true` to delete anyway | `id`/`name`/`url`, `projectId`, `force?` | `deletedId`, `deletedName`, `references` |
### Snippets
Snippets are small, standalone bits of UI typically embedded inside design documents as live examples (e.g. a "Button variants" snippet showing every Button state). They live within Subframe and do not sync out.
| Tool | Description | Key inputs | Returns |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | ---------------------------------------------- |
| `list_snippets` | List all snippets | `projectId` | Array of `id`, `name`, `url`, `lastModifiedAt` |
| `get_snippet_info` | Generated code for a snippet. Pass `includeNodeIds: true` to get a `data-node-id` on every element for use with `edit_snippet` | `id`/`name`/`url`, `projectId`, `includeNodeIds?` | `id`, `name`, `lastModifiedAt`, `files` |
| `design_snippet` | Design a new snippet | `description`, `name?`, `projectId`, `references?` | `snippetId`, `snippetUrl` |
| `edit_snippet` | Apply a targeted edit to one node of an existing snippet (same model as `edit_page`). Call `get_snippet_info` with `includeNodeIds: true` first | `id`/`name`/`url`, `nodeId`, `operation`, `code`, `projectId` | `snippetUrl`, `appliedCode?`, `warnings?` |
| `delete_snippet` | Delete a snippet. Any design document embeds are removed automatically | `id`/`name`/`url`, `projectId` | `deletedId`, `deletedName` |
### Flows
A flow is a collection of related pages (e.g. "Onboarding", "Checkout").
| Tool | Description | Key inputs | Returns |
| --------------- | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | -------------------------------------------- |
| `list_flows` | List all flows | `projectId` | Array of `id`, `name`, `pageCount` |
| `get_flow_info` | A flow with its name and ordered pages | `id`/`name`/`url`, `projectId` | `id`, `name`, `pages` |
| `delete_flow` | Delete a flow. Refuses if it contains pages. Use `deleteChildPages: true` to delete the flow plus every page inside it | `id`/`name`/`url`, `projectId`, `deleteChildPages?` | `deletedId`, `deletedName`, `deletedPageIds` |
### Prototypes
A prototype is an interactive, AI-built running app — its own React + Vite codebase — created in the Subframe prototyping editor. The read tools below expose a prototype as a standalone, runnable Vite app: internal scaffolding is dropped and the Vite/Tailwind config plus an entrypoint are added, so the file paths and contents you get back are ready to run with `npm install && npm run dev`.
| Tool | Description | Key inputs | Returns |
| --------------------- | ----------------------------------------------------------------------- | ----------------------------- | --------------------------------------- |
| `list_prototypes` | List all prototypes in a project | `projectId` | Array of `id`, `name`, `lastModifiedAt` |
| `get_prototype_info` | A prototype's metadata and the list of files in its runnable Vite app | `id`, `projectId` | `id`, `name`, `lastBuiltAt`, `files` |
| `read_prototype_file` | Read the contents of a single file from a prototype's runnable Vite app | `id`, `filePath`, `projectId` | `id`, `filePath`, `contents` |
### Design documents
Design documents are markdown files that convey how to work within your design system — brand voice, design principles, component usage rules ("when to use Toggle vs. Checkbox"), accessibility requirements, do/don't examples. AI automatically reads them when designing or implementing. **Project-scoped** docs (many per project) cover broad guidance; **component-scoped** docs (one per component, attached directly to it) cover specifics for that component.
| Tool | Description | Key inputs | Returns |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | --------------------------- |
| `write_design_document` | Create or update a markdown design document. Project-scoped if no `componentId`; component-scoped otherwise. Snippet examples can be embedded like so `` | `content`, `id?`, `componentId?`, `title?`, `projectId`, `mode?` (`replace`/`append`) | `documentId`, `documentUrl` |
Read existing docs first via `get_project_info` (project-level) or `get_component_info` (component-level). When updating, always pass the existing `id` — components allow at most one design document, and creating a second is rejected.
### Theme
| Tool | Description | Key inputs | Returns |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | -------------------------------------------------------------------------- |
| `get_theme` | Generate the Tailwind theme for the project, along with its responsive breakpoints and the font families available to use | `projectId`, `cssType?` | `theme` config, `availableFonts`, `breakpoints` |
| `edit_theme` | Edits the project's visual theme (colors, fonts, corners, shadows, typography) from a natural-language prompt. Applies immediately to the whole project. Supports adding tokens, changing token values, renaming tokens, and **deleting tokens (destructive — references in designs are replaced with the token's concrete value at deletion time)**. | `description`, `projectId` | `themeUrl` |
| `search_icons` | Search the project's icon library by describing what you want (e.g. "warning triangle", "shopping cart"). Icon sets vary by project, so use this to find real names rather than assuming a set — reference a result's exact name in the code you pass to `edit_page`, `edit_snippet`, or a design tool | `query`, `projectId?` | `iconLibraryStatus`, `iconLibrarySize`, `icons` (array of `name`, `hints`) |
### Design references
`design_page`, `design_component`, `edit_component`, and `design_snippet` accept an optional `references` array that grounds the generation in real material. Each entry pairs a `source` with a `usage` note telling the generator how to use that reference:
```json theme={null}
{
"source": { "kind": "subframe", "id": "..." },
"usage": "Match this page's layout and header"
}
```
| Source kind | Shape | Use for |
| ----------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `subframe` | `{ "kind": "subframe", "id": "..." }` | An existing page, component, or snippet in the project, by ID or name. Resolved server-side — no need to inline its code |
| `code` | `{ "kind": "code", "content": "..." }` | Raw code from your codebase: a surface to recreate, data types/interfaces, or usage patterns |
| `image` | `{ "kind": "image", "url": "..." }` | A mockup or screenshot uploaded to Subframe. Only Subframe upload URLs work — external URLs are rejected — and up to 5 images are used per call |
Invalid references are dropped with a warning in the tool result rather than failing the design.
### Async jobs
| Tool | Description | Key inputs | Returns |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------- | ----------------------------------------------------------------------------- |
| `wait_for_jobs` | Wait for background AI jobs to finish. Each result is `running`, `done` (finished with at least one usable result — for `design_page` the summary reports how many pages were applied), `error` (the job failed, produced nothing, or stopped reporting — check the summary and don't assume the edit happened), or `not_found`. Call in a loop until every job is `done` or `error` | `jobIds` (1-10) | Array of `jobId`, `status` (`running`/`done`/`error`/`not_found`), `summary?` |
`design_page`, `design_component`, and `edit_component` return a `jobId` alongside their URL. The URL can be used immediately to view the real-time progress of the job in the editor. Pass the `jobId(s)` to `wait_for_jobs` before reading back the generated content with `get_page_info`, `get_component_info`, `get_snippet_info`, or `get_flow_info` — those reads return empty/stale state until the job is done.
## Prompt with MCP link
When prompting we recommend using the MCP link found in the Code Inspect panel for a page.
```text theme={null}
Implement the design at https://app.subframe.com/design/...
```
To get the latest version of components in your project, run `npx @subframe/cli@latest sync` to [sync
components](/concepts/syncing-components).
## Example prompts
```text theme={null}
Create a new page using the Subframe page at
https://app.subframe.com/PROJECT_ID/design/DESIGN_ID/edit as reference.
Wire up relevant app logic (API calls, hooks, routing) where applicable.
Keep it consistent with existing project conventions.
```
```text theme={null}
Update the existing page to match the Subframe design at
https://app.subframe.com/PROJECT_ID/design/DESIGN_ID/edit.
Preserve all existing functionality unless the new design requires a change.
```
```text theme={null}
Design a PrivacyToggle component in Subframe. It should have an on/off state,
a label, and a description below the label. Use the same toggle styling as
our existing Toggle component.
```
```text theme={null}
Write a design doc for the Toggle component covering when to use it,
accessibility considerations, and a snippet showing all variants.
```
```text theme={null}
List the components in my Subframe project and help me delete any that
aren't being used in any pages.
```
```text theme={null}
Review the Subframe design at
https://app.subframe.com/PROJECT_ID/design/DESIGN_ID/edit and add proper
accessibility attributes to the existing page.
Add ARIA labels, roles, and descriptions, and proper semantic HTML throughout.
```
```text theme={null}
Migrate the components on the existing page to use Subframe components instead
of the old components.
Get all components in my Subframe project and preserve all existing
functionality unless the new design requires a change.
```
```text theme={null}
Get the Button component from Subframe and use it in this file.
```
```text theme={null}
Show me all components in my Subframe project.
```
```text theme={null}
Get my Subframe theme configuration.
```
## Troubleshooting
The Subframe MCP server uses OAuth. If you're seeing authentication errors:
* Try re-authenticating by reconnecting to the MCP server in your client
* Check that you have the correct permissions for the project you're trying to access
* Make sure your browser session is active when authenticating
* Confirm your client supports MCP OAuth — Subframe access tokens are not accepted by the MCP server
Make sure your AI tool:
* Has MCP support enabled
* Has the Subframe server in its MCP configuration
* Has been restarted after adding the configuration
Check your tool's logs for MCP-related errors.
Use `list_components`, `list_pages`, `list_snippets`, or `list_flows` to see what's available.
Verify:
* The component/page/snippet exists in your Subframe project
* The name or URL matches exactly
* You have access to the project
`design_page`, `design_component`, and `edit_component` run as background jobs. Check `wait_for_jobs` with the `jobId` returned by the design tool — it reports `running`, `done`, `error`, or `not_found`. The URL is live throughout, so opening it in the editor shows real-time progress.
If a job stops reporting progress for \~10 minutes, the server treats it as stalled (worker died, request timed out) and reports it as `error` so polling never hangs — its summary explains that the result could not be verified. Open the URL to see whether anything was actually generated; the chat panel surfaces any errors from the AI agent.
If the MCP server is unreachable:
* Check your internet connection
* Verify the URL is `https://mcp.subframe.com/mcp`
* Reach out to the Subframe team for support
# Publishing to NPM
Source: https://docs.subframe.com/guides/publish-to-npm
Distribute components as a package.
For teams that want to distribute components as a package, set up a separate repository:
1. Create a new repo for your component library
2. Follow the [installation instructions](/installation) to initialize the project in that repo
3. Sync components to this repo
4. Publish to NPM with your standard workflow
Your apps can then install that package instead of syncing directly. If you need help with this, please join our [Slack community](https://join.slack.com/t/subframecommunity/shared_invite/zt-380uma6dv-_lr7_bDLU5DJcoygfUYkeQ) for additional support.
# Agent skills
Source: https://docs.subframe.com/guides/skills
Guided workflows that teach AI assistants how to design and implement UIs with Subframe.
Agent skills are structured instructions that teach AI assistants how to use tools correctly. While the [MCP server](/guides/mcp-server) gives your AI the *ability* to access Subframe, skills give it the *knowledge* of how to use it well.
Skills are an [open standard ↗](https://agentskills.io) and becoming widely adopted throughout the industry.
## Installation
```bash theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
claude plugin marketplace add https://github.com/SubframeApp/subframe && claude plugin install subframe@subframe
```
The Subframe plugin for Claude Code sets up the MCP server and agent skills in one install.
Keep the Subframe plugin up to date automatically:
1. Run `/plugin` to open the plugin manager
2. Select the **Marketplaces** tab
3. Choose the **subframe** marketplace
4. Select **Enable auto-update**
Run `/mcp` to check that the Subframe MCP server is connected, then try asking Claude Code to use Subframe.
Follow the [Working with AI agents](/learn/guides/working-with-ai-agents) guide to design and implement your first page.
1. Go to [**Customize > Connectors**](https://claude.ai/customize/connectors)
2. Click and select **Add custom connector**
3. Set the name to **Subframe**
4. For **Remote MCP Server URL**, paste the following URL:
```
https://mcp.subframe.com/mcp
```
5. Click **Add**
Find the Subframe connector in your connectors list and click **Connect**. Follow the instructions on the Subframe website to complete authentication.
Follow the [Working with AI agents](/learn/guides/working-with-ai-agents) guide to design and implement your first page.
Add the Subframe MCP servers to Cursor. You'll be prompted to authenticate via OAuth.
* [Add Subframe MCP server to Cursor ↗](https://cursor.com/en-US/install-mcp?name=subframe\&config=eyJ1cmwiOiJodHRwczovL21jcC5zdWJmcmFtZS5jb20vbWNwIn0%3D)
* (Optional) [Add Subframe Docs MCP server to Cursor ↗](https://cursor.com/en-US/install-mcp?name=subframe-docs\&config=eyJ1cmwiOiJodHRwczovL2RvY3Muc3ViZnJhbWUuY29tL21jcCJ9)
If the install links don't work, make the following changes to `~/.cursor/mcp.json`.
```json ~/.cursor/mcp.json theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
{
"mcpServers": {
"subframe": {
"url": "https://mcp.subframe.com/mcp"
},
"subframe-docs": {
"url": "https://docs.subframe.com/mcp"
}
}
}
```
Cursor will handle OAuth authentication automatically when you first connect.
Agent skills are guided workflows that teach Cursor how to best use Subframe. Install them with:
```bash theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
npx skills add https://github.com/SubframeApp/subframe --skill '*' -g --agent cursor --yes
```
Check that the Subframe MCP server has successfully connected in **Cursor Settings** > **MCP**, then try asking Cursor to use Subframe.
Follow the [Working with AI agents](/learn/guides/working-with-ai-agents) guide to design and implement your first page.
Run the following commands to add the Subframe MCP servers and authenticate:
```bash theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
codex mcp add subframe --url https://mcp.subframe.com/mcp && codex mcp add subframe-docs --url https://docs.subframe.com/mcp && codex mcp login subframe
```
If the commands above don't work, add the following to `~/.codex/config.toml`:
```toml ~/.codex/config.toml theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
[mcp_servers.subframe]
url = "https://mcp.subframe.com/mcp"
[mcp_servers.subframe-docs]
url = "https://docs.subframe.com/mcp"
```
Then authenticate:
```bash theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
codex mcp login subframe
```
Agent skills are guided workflows that teach Codex how to best use Subframe. Install them with:
```bash theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
npx skills add https://github.com/SubframeApp/subframe --skill '*' -g --agent codex --yes
```
Run `/mcp` in Codex to check that the Subframe MCP server is connected, then try asking Codex to use Subframe.
Follow the [Working with AI agents](/learn/guides/working-with-ai-agents) guide to design and implement your first page.
Configure your MCP client to connect to the Subframe MCP server:
* **URL:** `https://mcp.subframe.com/mcp`
* **Transport:** HTTP
* **Authentication:** OAuth (your client will handle the authentication flow)
Optionally add the Subframe Docs MCP server for documentation access:
* **URL:** `https://docs.subframe.com/mcp`
* **Transport:** HTTP
* **Authentication:** None required
If your client supports the [Agent Skills](https://agentskills.io) standard, install the Subframe skills with:
```bash theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
npx skills add https://github.com/SubframeApp/subframe --skill '*' --agent '*' --yes
```
Restart your MCP client, then check that the Subframe MCP server has successfully connected and try asking your AI assistant to use Subframe.
Follow the [Working with AI agents](/learn/guides/working-with-ai-agents) guide to design and implement your first page.
## Available skills
**`/subframe:design`** — Designs and edits anything in Subframe: pages, components, snippets, design documents, and the theme. Also handles deletion of those resources except theme. Gathers context from your codebase, kicks off background AI jobs in Subframe, and returns URLs where each design appears live as it generates. Use this for new UI, design system additions, written design docs, theme tweaks, and cleanup.
**`/subframe:develop`** — Implements designs in code with business logic. Fetches the design, syncs components if needed, places the code in your codebase, and wires up data fetching, forms, event handlers, and loading/error states.
**`/subframe:install`** — Installs Subframe into a codebase so you can implement designs locally. Detects the framework, runs the CLI, configures Tailwind and fonts, and syncs components. You don't need to install to create designs.
## Next steps
See these skills in action in the [Working with AI agents](/learn/guides/working-with-ai-agents) guide, which walks through the full design-to-code workflow.
## Keep skills up to date
### Claude Code
The Subframe plugin for Claude Code bundles the MCP server and skills together. To enable auto-updates:
1. Run `/plugin` to open the plugin manager
2. Select the **Marketplaces** tab
3. Choose the **subframe** marketplace
4. Select **Enable auto-update**
#### Manually update the plugin
Reload the plugin to pick up the latest version:
```bash theme={null}
/reload-plugins
```
If that doesn't work, reinstall the plugin:
```bash theme={null}
claude plugin uninstall subframe@subframe && claude plugin install subframe@subframe
```
### Cursor, Codex, and other clients
Check for available skill updates:
```bash theme={null}
npx skills check
```
Update all skills to the latest versions:
```bash theme={null}
npx skills update
```
Or re-run the original install command to get the latest version.
## Enable for your team
If you use Claude Code, you can add Subframe to your project settings so team members are automatically prompted to install the plugin when they open the project.
Add the following to `.claude/settings.json` in your repository root:
```json .claude/settings.json theme={null}
{
"enabledPlugins": {
"subframe@subframe": true
},
"extraKnownMarketplaces": {
"subframe": {
"source": {
"source": "github",
"repo": "SubframeApp/subframe"
}
}
}
}
```
Commit this file to your repository. When a team member opens the project in Claude Code, they'll be prompted to install the Subframe marketplace and plugin.
## FAQ
Skills depend on the MCP server being properly connected. Check these:
1. Make sure the [MCP server](/guides/mcp-server) is running and connected
2. Update to the latest version — see [Keep skills up to date](#keep-skills-up-to-date)
# Testing
Source: https://docs.subframe.com/guides/testing
Add test attributes to Subframe components.
All components pass through props to the top-level element. Add `data-testid` or other test attributes for use with testing tools like Playwright, Cypress, or Selenium:
```tsx theme={null}
```
For reusable test IDs, add them in the component's [wrapper `index.tsx`](/concepts/syncing-components#wrapping-components) so they're included by default.
# Installation
Source: https://docs.subframe.com/installation
Set up Subframe in your codebase.
## Prerequisites
Subframe generates React components with Tailwind CSS. Your project needs:
* React 16+
* Tailwind CSS 3.4+
* TypeScript
## Install Subframe
Start from a new project or install in an existing project. Our CLI is [open source ↗](https://github.com/SubframeApp/subframe/tree/main/packages/subframe-cli) and will help you get started quickly.
Run our CLI in a blank folder to scaffold a new pre-configured project and follow the prompts:
```bash npm theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
npx @subframe/cli@latest init
```
```bash yarn theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
yarn dlx @subframe/cli@latest init
```
```bash pnpm theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
pnpx @subframe/cli@latest init
```
```bash bun theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
bunx @subframe/cli@latest init
```
Run our CLI in your project root and follow the prompts:
```bash npm theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
npx @subframe/cli@latest init
```
```bash yarn theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
yarn dlx @subframe/cli@latest init
```
```bash pnpm theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
pnpx @subframe/cli@latest init
```
```bash bun theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
bunx @subframe/cli@latest init
```
If you run into installation issues, refer to one of these framework-specific troubleshooting guides:
If you have an existing component library, [import its theme first](/learn/theme/importing-tokens), then use the [Subframe MCP server](/guides/mcp-server) or the `/subframe:design` agent skill to recreate components and snippets one at a time.
## Install fonts
Your Subframe theme may use a Google Font or an [uploaded custom font](/learn/theme/adding-custom-fonts). You'll need to add a snippet to your codebase to render the fonts correctly.
You can get the import code snippet from your [Subframe theme ↗](https://app.subframe.com/library?component=theme\&showInstallFontsModal=true)
```html theme={null}
```
## Set up Subframe MCP and agent skills (recommended)
The MCP server lets AI tools like Claude Code, Cursor, and Codex fetch your designs directly and even create new ones. Agent skills teach your AI assistant how to use Subframe well — setting up projects, designing pages, and implementing with business logic.
```bash theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
claude plugin marketplace add https://github.com/SubframeApp/subframe && claude plugin install subframe@subframe
```
The Subframe plugin for Claude Code sets up the MCP server and agent skills in one install.
Keep the Subframe plugin up to date automatically:
1. Run `/plugin` to open the plugin manager
2. Select the **Marketplaces** tab
3. Choose the **subframe** marketplace
4. Select **Enable auto-update**
Run `/mcp` to check that the Subframe MCP server is connected, then try asking Claude Code to use Subframe.
Follow the [Working with AI agents](/learn/guides/working-with-ai-agents) guide to design and implement your first page.
1. Go to [**Customize > Connectors**](https://claude.ai/customize/connectors)
2. Click and select **Add custom connector**
3. Set the name to **Subframe**
4. For **Remote MCP Server URL**, paste the following URL:
```
https://mcp.subframe.com/mcp
```
5. Click **Add**
Find the Subframe connector in your connectors list and click **Connect**. Follow the instructions on the Subframe website to complete authentication.
Follow the [Working with AI agents](/learn/guides/working-with-ai-agents) guide to design and implement your first page.
Add the Subframe MCP servers to Cursor. You'll be prompted to authenticate via OAuth.
* [Add Subframe MCP server to Cursor ↗](https://cursor.com/en-US/install-mcp?name=subframe\&config=eyJ1cmwiOiJodHRwczovL21jcC5zdWJmcmFtZS5jb20vbWNwIn0%3D)
* (Optional) [Add Subframe Docs MCP server to Cursor ↗](https://cursor.com/en-US/install-mcp?name=subframe-docs\&config=eyJ1cmwiOiJodHRwczovL2RvY3Muc3ViZnJhbWUuY29tL21jcCJ9)
If the install links don't work, make the following changes to `~/.cursor/mcp.json`.
```json ~/.cursor/mcp.json theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
{
"mcpServers": {
"subframe": {
"url": "https://mcp.subframe.com/mcp"
},
"subframe-docs": {
"url": "https://docs.subframe.com/mcp"
}
}
}
```
Cursor will handle OAuth authentication automatically when you first connect.
Agent skills are guided workflows that teach Cursor how to best use Subframe. Install them with:
```bash theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
npx skills add https://github.com/SubframeApp/subframe --skill '*' -g --agent cursor --yes
```
Check that the Subframe MCP server has successfully connected in **Cursor Settings** > **MCP**, then try asking Cursor to use Subframe.
Follow the [Working with AI agents](/learn/guides/working-with-ai-agents) guide to design and implement your first page.
Run the following commands to add the Subframe MCP servers and authenticate:
```bash theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
codex mcp add subframe --url https://mcp.subframe.com/mcp && codex mcp add subframe-docs --url https://docs.subframe.com/mcp && codex mcp login subframe
```
If the commands above don't work, add the following to `~/.codex/config.toml`:
```toml ~/.codex/config.toml theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
[mcp_servers.subframe]
url = "https://mcp.subframe.com/mcp"
[mcp_servers.subframe-docs]
url = "https://docs.subframe.com/mcp"
```
Then authenticate:
```bash theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
codex mcp login subframe
```
Agent skills are guided workflows that teach Codex how to best use Subframe. Install them with:
```bash theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
npx skills add https://github.com/SubframeApp/subframe --skill '*' -g --agent codex --yes
```
Run `/mcp` in Codex to check that the Subframe MCP server is connected, then try asking Codex to use Subframe.
Follow the [Working with AI agents](/learn/guides/working-with-ai-agents) guide to design and implement your first page.
Configure your MCP client to connect to the Subframe MCP server:
* **URL:** `https://mcp.subframe.com/mcp`
* **Transport:** HTTP
* **Authentication:** OAuth (your client will handle the authentication flow)
Optionally add the Subframe Docs MCP server for documentation access:
* **URL:** `https://docs.subframe.com/mcp`
* **Transport:** HTTP
* **Authentication:** None required
If your client supports the [Agent Skills](https://agentskills.io) standard, install the Subframe skills with:
```bash theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null} theme={null}
npx skills add https://github.com/SubframeApp/subframe --skill '*' --agent '*' --yes
```
Restart your MCP client, then check that the Subframe MCP server has successfully connected and try asking your AI assistant to use Subframe.
Follow the [Working with AI agents](/learn/guides/working-with-ai-agents) guide to design and implement your first page.
See our [MCP server guide](/guides/mcp-server) and [agent skills guide](/guides/skills) for more details.
## FAQ
You can use our [MCP server](/guides/mcp-server) to access your designs and documentation in your AI coding assistant and convert them to your preferred language and framework.
While Subframe only generates React + Typescript + Tailwind code for now, we found that in practice, asking AI to convert the code to your preferred framework is far faster than starting from scratch. Our generated frontend code is high-quality and consistent, without the overhead that most LLMs add to AI-generated UI code, so AI's often are able to convert the code to your preferred framework consistently in a single prompt.
Yes. All generated code lives in your codebase and is yours to keep. Any dependencies you see are [open source ↗](https://github.com/SubframeApp/subframe).
Join our active [Slack community ↗](https://join.slack.com/t/subframecommunity/shared_invite/zt-380uma6dv-_lr7_bDLU5DJcoygfUYkeQ) for additional support from the Subframe team and other users.
# Auth tokens
Source: https://docs.subframe.com/learn/admin/auth-tokens
Create and manage auth tokens for the Subframe CLI.
Auth tokens let team members authenticate the Subframe CLI with their accounts.
## Create a token
If you're using an AI coding assistant with the [MCP server](/guides/mcp-server), auth tokens are generated automatically — no manual setup needed.
To create a token manually, go to [https://app.subframe.com/cli/auth](https://app.subframe.com/cli/auth) or follow the below steps:
1. Go to **Settings > Access Tokens**
2. Click **New**
3. Copy the token from the dialog
Tokens are shown once. Store them securely.
Only Admins and Editors can create tokens. Viewers don't have CLI access.
## Delete a token
In **Settings > Access Tokens**, click **⋯ > Delete** next to the token.
# Team members
Source: https://docs.subframe.com/learn/admin/managing-team
Invite collaborators and manage roles for your team.
Invite collaborators and manage roles and permissions in **Settings > Team**. Team members can access all projects within the team.
## Multiple teams
You can create and join multiple teams from a single Subframe account. Each team has its own projects, billing plan, and team members.
#### Creating a new team
1. Click the team dropdown in the top navigation
2. Select **New team**
3. Name your team and click **Create**
#### Switching teams
Click the team dropdown and select a team to switch. The dropdown shows a badge with each team's active plan.
Use multiple teams to:
* Separate work from personal projects
* Work with different clients
* Organize large organizations into smaller workspaces
## Roles
| Role | Access | Cost (Pro Plan) |
| ------------ | -------------------------------------------- | --------------- |
| **Admin** | Full access plus team and billing management | \$29/month |
| **Editor** | Edit designs, export code, use AI | \$29/month |
| **Viewer** | View designs only | Free |
| **Inactive** | No access | Free |
Only Admins and Editors count as paid seats. Viewers are always free.
### Permissions
| | Admin | Editor | Viewer |
| -------------------------- | :---: | :----: | :----: |
| View designs | ✓ | ✓ | ✓ |
| Edit designs | ✓ | ✓ | – |
| Export code | ✓ | ✓ | – |
| Use AI features | ✓ | ✓ | – |
| View version history | ✓ | ✓ | – |
| Create and delete projects | ✓ | ✓ | – |
| Invite Editors and Viewers | ✓ | ✓ | – |
| Invite Admins | ✓ | – | – |
| Change roles | ✓ | – | – |
| Manage billing | ✓ | – | – |
## Invite members
1. Go to **Settings > Team**
2. Click **Invite**
3. Enter email and select role
4. Click **Invite**
Admins can invite any role. Editors can invite Editors and Viewers. Viewers cannot invite others.
Pending invitations show an "Invited" badge. Cancel by clicking **⋯ > Cancel invitation**.
## Accept an invitation
If you already have a Subframe account, an invitation adds you to the team automatically. The next time you open Subframe, a **Welcome to Subframe** dialog shows the team and its members—click **Join team** to confirm, or **Leave team** to stay where you are.
The same prompt appears when you sign up with an email domain tied to an existing team.
If you don't have an account yet, click the link in your invitation email to sign up and join.
Joining a team removes you from your current team. If you're the only member of your current team, your projects
transfer with you.
## Change a role
Admins can change roles from the team member list. Click the role dropdown and select a new role. Changes take effect immediately. Any pricing changes to your plan update with the next billing cycle.
## Remove a member
Set their role to **Inactive**. They lose access immediately but can be reactivated later.
## Leave a team
Click **⋯ > Leave team** next to your name. You must make someone else Admin before leaving if you're the only one.
# Pricing and plans
Source: https://docs.subframe.com/learn/admin/pricing-and-plans
Compare plans and manage your subscription.
Subframe offers Free, Pro, and Custom plans. Billing is per team with seat-based pricing.
## Plans
| | Free | Pro | Custom |
| --------------------- | -------- | ----------------- | ---------- |
| **Price** | \$0 | \$29/editor/month | Contact us |
| **Projects** | 1 | Unlimited | Unlimited |
| **Pages** | 5 | Unlimited | Unlimited |
| **Prototypes** | 1 | Unlimited | Unlimited |
| **AI usage** | Limited | Unlimited | Unlimited |
| **Version history** | 24 hours | 7 days | Extended |
| **Custom fonts** | – | ✓ | ✓ |
| **Import components** | – | – | ✓ |
| **Dedicated support** | – | – | ✓ |
Viewers are free on all plans. Add as many as you need to collaborate.
## Upgrade to Pro
1. Go to **Settings > Billing**
2. Click **Upgrade**
3. Complete checkout in Stripe
Pro features activate immediately. Cost is calculated based on current Editors and Admins.
## Manage subscription
Go to **Settings > Billing** and click **Stripe portal** to:
* Update payment method
* View invoices
* Cancel subscription
Only Admins can access billing settings.
## Cancel or downgrade
Cancel your plan from the Stripe portal. Pro access continues until the billing period ends.
When downgrading to Free, all your existing projects, pages, and components are retained, but you won't be able to add new pages if over the Free plan limit.
## Billing changes
Adding or removing Editors/Admins updates your next invoice automatically. You're not charged immediately for new seats.
## Discounts
Contact [support@subframe.com](mailto:support@subframe.com) for student and educator pricing.
# Okta SSO
Source: https://docs.subframe.com/learn/admin/sso/okta
Configure Okta single sign-on for your Subframe team.
This feature is only available on the Custom Plan. If you are an existing Custom Plan customer, continue with the
setup below. Once completed, contact us to enable SSO for your team.
Navigate to the Applications dashboard of the Okta admin console. Click *Create App Integration*.
Subframe supports the SAML 2.0 SSO protocol. Choose it from the *Create a new app integration* dialog.
The information you enter here will be shown in your Okta applications menu. The App name should typically be **Subframe**.
You can download the Subframe logo to upload as the **App logo** below:
These settings let Subframe use SAML 2.0 properly with your Okta application. Make sure you enter this information exactly as shown in this table and screenshot.
| Setting | Value |
| ---------------------------------------------- | ---------------------------------------------------- |
| Single sign-on URL | `https://api.subframe.com/auth/v1/sso/saml/acs` |
| Use this for Recipient URL and Destination URL | ✔️ |
| Audience URI (SP Entity ID) | `https://api.subframe.com/auth/v1/sso/saml/metadata` |
| Default `RelayState` | `https://app.subframe.com` |
| Name ID format | `EmailAddress` |
| Application username | Email |
| Update applicate username on | Create and update |
Attribute Statements allow Subframe to get information about your Okta users on each login.
A `email` to `user.email` **statement is required**. Other mappings shown below are optional and configurable depending on your Okta setup. If in doubt, replicate the same config in the screenshot below.
Subframe needs to finalize enabling single sign-on with your Okta application.
After you finalize the creation of your Subframe Application in Okta copy the Metadata URL and send it to your Subframe contact. If you're not sure who to send this to or need further assistance, contact [support@subframe.com](mailto:support@subframe.com).
The Metadata URL usually has this structure: `https://.okta.com/apps//sso/saml/metadata`
Once you’ve configured the Okta app as describe above, send the Metadata URL to your support contact at Subframe.
Wait for confirmation that this information has successfully been added to Subframe. It usually takes us less than 1 business day to configure this for your team.
Once you’ve received confirmation from your support contact at Subframe that SSO setup has been completed, you can ask some of your users to sign in with SSO via their Okta account.
All they need to do is enter their work email address when they choose to sign in with SSO.
If sign in is not working correctly, reach out to your support contact at Subframe for further guidance.
# Adding context with @
Source: https://docs.subframe.com/learn/ask-ai/adding-context
Type @ to reference pages, components, snippets, and docs in your prompt.
Type @ anywhere in the chat bar to reference something in your project. Ask AI uses what you reference as context — useful for remixing an existing design, reusing a component, or pointing AI at a snippet or design doc.
1. Type @ in the chat bar
2. Pick a **page**, **component**, **snippet**, or **doc** from the menu — keep typing to filter
3. The reference appears as a chip in your prompt. Add as many as you need
4. Type the rest of your prompt and press Enter
Ask AI pulls in each reference and grounds its response in those designs.
Hover over a page, component, or snippet chip to preview what it references without leaving the chat.
The page you're working on is always included as context automatically. Use @ to bring in *other* pages, components, snippets, or docs.
# Image to design
Source: https://docs.subframe.com/learn/ask-ai/image-to-design
Upload images to prompt AI when generating designs.
Upload screenshots or mockups and Ask AI will recreate designs using your components and theme.
## Upload images
1. Click the **image icon** in the Ask AI toolbar or drag images directly into the prompt area.
2. Attach as many images as you need — each appears as a thumbnail you can remove before sending.
3. Add a prompt describing how to use them
4. Press Enter to generate
AI analyzes the images and creates 1-4 variations that match the structure using your Subframe components.
Supported formats: PNG, JPG, JPEG, WebP
## Import from Figma
1. In Figma, select a frame to copy as PNG (right-click or press Cmd + Shift + C)
2. In Ask AI, paste the image into the prompt area
3. Add a prompt and press Enter
## Reference a web page
Paste a URL into the prompt and Ask AI captures a screenshot and the page's content to use as a reference — matching the layout and pulling in exact copy.
1. Paste a URL into the Ask AI prompt
2. Add a prompt describing what to build from it
3. Press EnterAsk AI only fetches URLs you paste in. To reference another page inside your own project, use [@ mentions](/learn/ask-ai/adding-context) instead.
# Quick edits
Source: https://docs.subframe.com/learn/ask-ai/making-quick-edits
Ask AI in context to edit or insert specific elements.
You can make targeted edits to specific elements using [quick actions](/learn/design-mode/quick-actions).
1. Select an element
2. Right-click or press / to open [quick actions](/learn/design-mode/quick-actions)
3. Select **Ask AI to edit...**
4. Type what you want to change and press Enter
You can also make a quick edit from the floating toolbar. Select an element, type your change into the **Apply quick edit to…** input, and press Enter.
AI replaces or modifies the selected element based on your prompt.
## Quick insert with AI
Open the [quick insert](/learn/design-mode/adding-elements) menu to add new elements using AI.
1. Select an element
2. Click a **+** button (above, below, left, or right)
3. Type what you want to insert
4. Press **Ask AI to insert...**
AI adds the new element next to your selection, automatically applying layout and wrapping stacks for you.
## Reviewing variations
For open-ended prompts, AI generates up to three distinct variations so you can compare directions before committing. Deterministic edits—like "make the text bold" or "change the button to blue"—return a single result.
A review tray appears with a labeled thumbnail for each variation:
1. Click a variation to preview it live on the canvas
2. Type in the shared input to **Refine...** and regenerate the set (optional)
3. Click **Keep** to apply the active variation, or **Discard** to dismiss
When editing an existing element, the tray also includes an **Original** card so you can compare against the unedited version.
# Personalizing AI
Source: https://docs.subframe.com/learn/ask-ai/personalizing-ai
Train AI with your project context for more relevant design generations.
Subframe is the only AI design tool that learns from your designs, allowing you to train and personalize the AI for each project.
## Project context
Add a system prompt about your project (e.g. company description, tone preferences, design guidelines) to help AI generate designs that are more relevant to your product.
1. Open [project settings](/learn/projects/project-settings)
2. Enter your **Company name**
3. Enter your **project description** — describe your company, product, and design preferences
4. Click **Save** to apply your settings
## Referencing existing designs
Ask AI automatically references your existing designs when generating new ones. Three things influence AI in your project:
* **Design system**: Your components and theme keep everything on brand
* **Your pages**: AI learns from pages you design
* **Snippets**: Pre-built compositions like headers and nav bars that AI references
# Generating designs
Source: https://docs.subframe.com/learn/ask-ai/prompt-to-design
Use AI to generate page designs, make edits, and update your theme.
You can prompt AI to generate page designs, edit your existing designs, or update your theme.
* **Design new pages** — Generate multiple variations of page designs
* **Edit your page directly** — Make targeted changes to your current page
* **Design components and snippets** — Create reusable building blocks and design system examples
* **Update your theme** — Modify colors, fonts, and tokens project-wide
* **Answer questions** — Search Subframe docs for help with features and development workflows
Ask AI is for creating and updating designs. For adding interactivity and state, use [Prototype mode](/learn/prototype-mode/overview).
## Getting started
1. Click the **Ask AI** button in the toolbar or press Cmd + /
2. Type a prompt and press Enter or click the send button
3. Ask AI will create a new conversation and responds to your prompt
The Ask AI panel docks to the right side of the editor and follows you across your project — it's available on all project level pages.
Our AI will choose how to respond based on your prompt:
* **Variations** — For new designs, creative exploration, or prompts requiring new components. Ask AI will generate 1-4 variations for you to choose from.
* **Direct edits** — For simple changes to existing elements like colors, text, or layout tweaks
You can keep prompting while AI is working — follow-up messages queue and send when the current response finishes. Click the stop button (or press Esc) to stop a running response.
## Review designs
Variations land as real pages in a flow on your canvas as each one finishes generating. The chat shows a live tile for each variation — click a tile to jump to that page on the canvas.
From there you can:
* **Ask a follow-up** — Refine a variation or combine ideas from different ones ("use the header from variation 1 with the layout from variation 3")
* **Edit visually** — Open any variation in [design mode](/learn/design-mode/layers) for manual edits
* **Delete** the variations you don't want
## Conversation history
Your conversations persist across sessions and pages so you can reference past generations.
Click the history button at the top of the Ask AI panel to see every chat in the project, grouped by recency — or hover over it to peek at the list. An animated indicator means a chat is still working, and a dot marks unread results. Click the **+** button to start a new chat.
Each chat is labeled with the page, component, or flow it last worked on. Click the label in the chat header to jump to that resource.
## Ask AI toolbar
Ask AI includes additional tools:
* **[Adding context with @](/learn/ask-ai/adding-context)** — Reference other pages, components, snippets, and docs in your prompt
* **[Upload image](/learn/ask-ai/image-to-design)** — Add reference images
* **[Import from Figma](/learn/ask-ai/image-to-design#import-from-figma)** — Import Figma designs using the image model
* **[Reference a web page](/learn/ask-ai/image-to-design#reference-a-web-page)** — Paste a URL to use a live web page as a reference
* **[Personalize AI](/learn/ask-ai/personalizing-ai)** — Configure project-wide context
You can also use [quick edits](/learn/ask-ai/making-quick-edits) to make targeted edits to specific elements using AI.
## Troubleshooting
Ask AI is great for generating new designs or making broad changes to your page. But for more targeted edits, you may want to use [quick edits](/learn/ask-ai/making-quick-edits).
Open the component editor and use the same Ask AI panel to insert elements, create variants, and refactor components.
You can also right-click or press / to open [quick actions](/learn/design-mode/quick-actions) and select
**Ask AI to edit...**.
Cmd + Z undoes changes made directly in the visual editor. It does not revert
changes applied through MCP or API tools.
To revert a tool-applied change, restore the affected page, component, snippet, or theme using
[Version history](/learn/projects/version-history). Use project version history to recover deleted items
or roll back changes across multiple parts of your project.
# Overview
Source: https://docs.subframe.com/learn/code-mode/overview
View and export code from your designs.
Subframe generates high-quality React code [deterministically](/overview#code-quality-matters) from your designs.
For a complete guide to working with Subframe code, see the [developer docs](/overview).
There are three tabs in code mode:
* **Inspect** — Get code from your designs
* **Installation** — Install MCP servers or get setup with an existing codebase or Replit
* **Prompts** — Get prebuilt prompts after you install MCP server
## Installation
Use the **Installation** tab for the following:
* Setting up Subframe in your codebase
* Setting up Claude Code, Cursor, or Codex MCP servers
* Setting up a new Replit project
## Inspecting page code
1. Open code mode by clicking **Code** in navbar
2. Click on **Inspect** tab
3. You should now see all the code for your page
## Inspecting elements
1. Click on **Inspect** tab
2. Click on element you want to inspect
3. Use dropdown to select what code format you want:
* React + Tailwind code
* CSS code
* Properties
## Syncing components
1. Click on **Inspect** tab
2. Copy the `npx @subframe/cli@latest` command for syncing all components in the page or your selection
To learn more about how syncing works, see [syncing components](/concepts/syncing-components).
## Exporting using MCP server
1. Click on **Prompts** tab
2. Copy a prebuilt prompt, or copy the MCP link and ask your question directly
# Import existing design system
Source: https://docs.subframe.com/learn/components/importing
Import existing React components into Subframe.
You can import your existing design system into Subframe from code or from Figma.
1. [Import your theme](/learn/theme/importing-tokens)
2. [Import your components](/learn/components/overview#importing-components) one-by-one
3. Use [AI quick edits](/learn/ask-ai/making-quick-edits) to refine the design, if needed
# Overview
Source: https://docs.subframe.com/learn/components/overview
Reusable UI building blocks for your designs.
Components let you reuse UI across your designs.
* **Same as code** — Components in Subframe are the same building blocks your engineers use
* **Always in sync** — When you update a component, all instances update and sync to code
* **Interactive when needed** — Some components like checkboxes and accordions have built-in interaction logic using `@subframe/core`, our Radix-based headless component library. Learn more about [headless components](/concepts/code-generation#headless-components).
## Importing components
We recommend [importing your theme](/learn/theme/importing-tokens) first so that imported components match your design system.
1. Open **Components** under **Design System** in the left sidebar
2. Click **New component**
3. Paste the source code of an existing component from your codebase into the prompt
4. Press Enter or click the send button — the AI agent will take a few minutes to import your component
```
Recreate the Button component from the following code:
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-lg border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-3 aria-invalid:ring-3 [&_svg:not([class*='size-'])]:size-4 group/button inline-flex shrink-0 items-center justify-center whitespace-nowrap transition-all outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
outline: "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
destructive: "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
icon: "size-8",
"icon-xs": "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
"icon-sm": "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
"icon-lg": "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Button({
className,
variant = "default",
size = "default",
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps & {
asChild?: boolean
}) {
const Comp = asChild ? Slot.Root : "button"
return (
)
}
export { Button, buttonVariants }
```
1. Open **Components** under **Design System** in the left sidebar
2. Click **New component**
3. Take a screenshot of the component and its states from Figma and paste it into the prompt
4. Press Enter or click the send button — the AI agent will take a few minutes to import your component
## Creating components using AI
1. Open **Components** under **Design System** in the left sidebar
2. Click **New component**
3. Describe the component you want in the prompt
4. Press Enter or click the send button — the AI agent will take a few minutes to create your component
## Creating components from scratch
1. Open **Components** under **Design System** in the left sidebar
2. Click **New component**
3. Choose one of:
* Remix an existing prebuilt component
* Create from scratch
Some prebuilt components are based off of Radix primitives. If you want interactive logic like checked state, you must
remix an existing prebuilt component.
## Creating components from existing designs
You can create components directly from elements on a page:
1. Right-click or press / to open [quick actions](/learn/design-mode/quick-actions)
2. Select **Create component**
3. Name your component
4. Review AI-suggested properties
5. Click **Create**
Subframe AI will auto-suggest properties using best practices based on what you have selected.
## Editing components
To edit a component:
1. Open **Components** under **Design System** in the left sidebar
2. Click on the component
3. Click **Edit component**
To edit a component from the editor:
1. Right-click or press / to open [quick actions](/learn/design-mode/quick-actions)
2. Select **Edit component**
Any changes you make will update all component instances.
## Adding component instances
You can add a component instance by
1. Open the [quick insert](/learn/design-mode/adding-elements) menu
2. Select the component you want to add
## Detaching component instances
You can detach a component so the rendered elements are no longer linked to the component.
1. Right-click or press / to open [quick actions](/learn/design-mode/quick-actions)
2. Select **Detach instance**
Components whose structure relies on tightly coupled primitives — dropdowns, selects, dialogs, drawers, charts,
tables, and calendars — can't be detached. The **Detach component** action is hidden for these.
## Deleting components
You can delete a component from the **Components** page or from the **...** menu in the component editor.
When a deleted component is used in other designs, Subframe handles existing instances based on the component type. The delete dialog shows what will happen before you confirm:
* **Instances will be detached** — Custom components and most interactive primitives (buttons, switches, checkboxes, sliders, sidebars, navbars). Each instance becomes an independent copy of the rendered elements.
* **Instances will be deleted** — Components whose structure can't survive on their own (dropdowns, selects, dialogs, drawers, charts, tables, calendars). Customizations inside each instance are removed with it.
* **Layout assignments will be cleared** — Page layouts. The pages using the layout stay, but lose their layout assignment.
* **References will be removed** — Custom pages referenced from prototype actions or other pages.
Most deletes can't be undone with Cmd + Z. To recover a deleted component, restore an earlier snapshot from [Version history](/learn/projects/version-history).
## Resetting components
You can reset a prebuilt component to its original state, reverting any customizations you've made.
1. Right-click or press / to open [quick actions](/learn/design-mode/quick-actions)
2. Select **Reset to original**
3. Confirm the reset
This restores the component to its default configuration from the Subframe library.
## Sorting components
The **Components** page lists every component in your project along with when each was last edited. Use the sort control at the top of the list to change the order:
* **A-Z** — Alphabetical by name (default)
* **Recently edited** — Most recently edited first
* **Recently created** — Most recently created first
## Component docs
We auto-generate component docs with properties and sometimes examples, similar to Storybook. Any updates you make will automatically update the docs. You can directly edit the component from these docs or send them to a developer.
To view them, navigate to **Components**, click on the component.
## Exporting components to code
1. Click **Code**
2. Select **Inspect**
3. Copy and paste the `npx @subframe/cli@latest` command
For more information, see [syncing components](/concepts/syncing-components).
## Importing from another project
Copy components and pages from one of your other Subframe projects into the current one. Each selection brings along the dependencies it needs to render — nested components, theme tokens, fonts, custom icons, and image assets.
1. Open **Components** under **Design System** in the left sidebar
2. Click **Import** (top right) and select **Import from project...**
3. Pick the source project from the dropdown
4. Browse or search the source project's components and pages — click any item to preview it on the right
5. Check the box on each item you want, then click **Import**
Imported components join your component library. Imported pages land in your project's first flow.
Components that already exist in the current project don't appear in the picker.
# Props & slots
Source: https://docs.subframe.com/learn/components/props-and-slots
Add data and composition to your components.
Properties make your components reusable by accepting different data. There are four types:
* **Text / Number** — Simple text or numeric values
* **Icon** — Icon selection from available icon libraries
* **Image** — Image URLs or uploaded files
* **Slot** — Regions for dynamic content and composition
When using component instances, properties can be configured in the inspector.
Text, icon, and image props are for simple data. Slots are special properties that allow for composition—see [dedicated section](#slots) below.
## Creating a new property
1. Ensure you have nothing selected in design mode
2. On right-hand panel, click **Properties** >
3. Select the property type to add
You can also create a property when [linking](/learn/components/props-and-slots#linking-element-contents-to-a-property) elements.
## Removing properties
1. Ensure you have nothing selected in design mode
2. Look for your property under **Properties**
3. Click on the to delete
## Linking element contents to a property
1. Click on the element
2. In inspector, click **Content** >
3. Select the property or **New Property...** to create a new one
## Slots
Slots expose a region of your component as a prop. They're useful for:
* **Reusability & composition** — Add dynamic content like elements and other components within others
* **Adding interactions in code** — Let developers add business logic within your components. See [adding interactive logic](/guides/component-docs) for more.
#### Adding slots
1. Create a stack
2. In the inspector, click **Slot** >
3. Name your slot, click **Create**
You can also right-click or press / to open [quick actions](/learn/design-mode/quick-actions), then select **Add slot**.
#### Using slots
1. Insert a component instance
2. In inspector, look for the slot property and click **Edit X layers**
Within the canvas, you can also double-click any contents within a slot to edit.
#### Previewing slots
When viewing component docs, components in slots are exposed in the properties panel. You can edit these component props in the preview to test how your component handles different configurations.
# Hover, active, focus states
Source: https://docs.subframe.com/learn/components/states
Add interaction states to your components.
Subframe supports three interaction states:
* **Hover** — Mouse over (`:hover`)
* **Active** — Mouse press (`:active`)
* **Focused** — Keyboard focus (`:focus-visible`)
## Creating a state
1. Hover over the variant you want to add a state to
2. Click **+Hover**, **+Active**, or **+Focus**
## Modifying states
1. Select the state you want to modify
2. Select an element
3. Modify any property in the Inspector
4. An override indicator (pink dot) appears next to the property
To remove a state override, click the pink dot to reset to the default value.
## Hover states in code
If your element has a hover state, we add `cursor-pointer` in the generated code:
```tsx theme={null}
Click me
```
# Subcomponents
Source: https://docs.subframe.com/learn/components/subcomponents
Child components scoped to a parent component.
Subcomponents are child components scoped to a parent (also known as compound components). Use them to group related components together. In code, they are represented as properties of the parent component.
```tsx theme={null}
TitleContent goes hereActions
```
In this example, `Card` is the parent component. `Header`, `Body`, and `Footer` are subcomponents.
## Creating subcomponents
1. Open your parent component
2. In the left panel, click on **Subcomponents** > to create a new subcomponent
# Variants
Source: https://docs.subframe.com/learn/components/variants
Create different visual styles for your component.
Variants let you create different visual styles for your component.
## How variants work
Every component has a **default variant**—how it looks with no changes applied.
You can add variants to override styles. There are two types:
* **Boolean** — True or false (e.g. `disabled`)
* **Enum** — Multiple options (e.g. `variant` with primary, secondary, destructive)
Each variant only stores style overrides from the default—not the full design. When multiple variants are active, they layer together automatically.
For example, a button with `disabled` and `variant=destructive` variants:
* `disabled` changes opacity to 50%
* `destructive` changes background to red
When both are active, you get a red button at 50% opacity.
By defining each variant separately, you don't need to design every combination—just define what each variant changes.
## Creating a variant
1. Ensure you have nothing selected in design mode
2. On right-hand panel, click **Properties** >
3. Select the type of variant to add
## Adding options to an enum variant
1. Ensure you have nothing selected in design mode
2. On right-hand panel, find the enum to add an option to
3. Click to add an option
## Modifying styles in a variant
1. Select the variant you want to modify
2. Select an element
3. Modify any property in the Inspector
4. An override indicator (pink dot) appears next to the property
To remove a variant override, click the pink dot to reset to the default value.
# Concepts
Source: https://docs.subframe.com/learn/concepts
Learn the core concepts behind Subframe's design-to-code workflow.
Subframe is built for creating designs that hand off to code and managing design systems. Many concepts map to atomic design principles.
# Adding elements
Source: https://docs.subframe.com/learn/design-mode/adding-elements
Add elements to your design using quick insert or drag-and-drop.
There are three ways to add elements to your design in Subframe: quick insert, the insert panel, and the insert tools.
## Method 1: Quick insert (recommended)
1. Select an element on your canvas
2. Hover over the edges of the selection box
3. Click the button to open the search popup
4. Search for and select the element you want to add
5. The new element will be inserted in the direction you clicked
#### Keyboard shortcuts
You can also use these keyboard shortcuts after selecting an element:
* Quick insert above: Ctrl + I
* Quick insert below: Ctrl + K
* Quick insert left: Ctrl + J
* Quick insert right: Ctrl + L
## Method 2: Drag and drop from the Insert panel
1. Click the **Insert** button in the toolbar at the bottom of the canvas
2. Find the element you want to add
3. Drag it directly onto your canvas
#### What's in the panel
Search across everything from the bar at the top, or browse the sections:
* **Basic elements** — text, stacks, icons, images, and dividers.
* **Components** — buttons, inputs, and other components from your design system.
* **Snippets** — larger, pre-built compositions like page headers, navigation bars, and card layouts.
## Method 3: Insert tools
Press a shortcut to pick up an insert tool, then click where you want the element to land—no need to select an element first.
* Press F to insert a **stack**
* Press T to insert **text**
As you move over the canvas, a preview follows your cursor and a line shows where the element will land. Click to place it. Inserting text drops you straight into editing so you can start typing.
Press V or Esc to exit the insert tool and return to selecting.
# Comments
Source: https://docs.subframe.com/learn/design-mode/comments
Leave feedback and collaborate on your designs.
Comments let you leave feedback directly on your designs. Adding and viewing comments is available on all plans (including viewers). You can only add comments to pages today.
## Adding comments
1. Click **Comment** in the toolbar, or press C
2. Click on an element to add a comment
Comments stay attached to specific elements. When you move or edit an element, its comments move with it. Comments are shown as pins directly on the design or in the right side panel.
## Moving comments
1. Select a comment thread pin
2. Drag the pin to reposition it within the design
## Mentioning teammates
1. Type @ in a comment
2. Select a user to mention
Mentioned users will receive an email notification.
## Resolving comments
1. Open the comment thread
2. Click the checkmark to resolve
# Custom CSS
Source: https://docs.subframe.com/learn/design-mode/custom-css
Add custom Tailwind classes as an escape hatch for CSS properties not available in the Inspector.
If there's a CSS property you need that isn't available in the Inspector, you can add custom Tailwind CSS classes directly to any element. This is useful for properties like positioning, z-index, overflow, animations, and other styles that the visual editor doesn't expose.
## Adding custom classes
1. Select an element
2. In the Inspector, find **Tailwind CSS** and click **+**
3. Type or paste a Tailwind class name
You can also use [Ask AI](/learn/ask-ai/making-quick-edits) to add custom classes. For example, ask "make this element position absolute" and it will apply the right Tailwind classes for you.
## Common use cases
Here are some properties you can control with custom Tailwind classes that aren't available in the Inspector:
| Use case | Tailwind classes |
| -------------------- | ----------------------------------------------------------------- |
| Absolute positioning | `absolute`, `relative`, `fixed`, `sticky` |
| Z-index | `z-10`, `z-20`, `z-50` |
| Overflow | `overflow-hidden`, `overflow-auto`, `overflow-scroll` |
| Animations | `animate-spin`, `animate-ping`, `animate-pulse`, `animate-bounce` |
| Pointer events | `pointer-events-none`, `pointer-events-auto` |
| Opacity | `opacity-50`, `opacity-0` |
| Cursor | `cursor-pointer`, `cursor-not-allowed` |
| Transitions | `transition-all`, `duration-200`, `ease-in-out` |
| Transforms | `rotate-45`, `scale-110`, `translate-x-2` |
## Tailwind CSS documentation
For a full list of available classes, see the Tailwind CSS documentation:
* [Tailwind CSS v3 ↗](https://v3.tailwindcss.com)
* [Tailwind CSS v4 ↗](https://tailwindcss.com)
# Inspector
Source: https://docs.subframe.com/learn/design-mode/inspector
Configure element properties in the right sidebar.
The Inspector panel shows all editable properties for the selected element. Select any element on the canvas to see its properties in the right sidebar.
## Overrides properties
Overrides let you customize properties for specific contexts without changing the base element. There are two types of overrides:
* **[Responsive overrides](/learn/design-mode/responsive-design)** — breakpoint overrides for properties that need different values at specific screen sizes
* **[Variant overrides](/learn/components/variants)** — different values for component instances
When a property has an override, a pink dot appears next to it. Click the pink dot to remove the override and revert to the default value.
## Prebuilt interactive components
The inspector lets you set the default state for some prebuilt components, so you can preview how they look in different conditions:
* **Checkbox**, **Switch**, **Radio Group item**, **Toggle Group item** — toggle the **Checked** prop
* **Text Field**, **Text Area** — type into the **Value** prop
These controls only affect how the instance renders in design mode and preview. Runtime state in generated code is controlled by your application — see [headless components](/concepts/code-generation#headless-components).
## Keyboard shortcuts
Most properties in the inspector have a keyboard shortcut. Hover over any edit action to see its shortcut.
For the full list, see [Keyboard shortcuts](/learn/design-mode/keyboard-shortcuts).
# Keyboard shortcuts
Source: https://docs.subframe.com/learn/design-mode/keyboard-shortcuts
Speed up your workflow with keyboard shortcuts.
You can view the full list of keyboard shortcuts at any time by clicking the keyboard icon in the editor navbar.
You can also press Cmd + K to quickly navigate to another component.
## Step back to where you came from
Press Esc twice to navigate back up your editing history:
* From a component or snippet that you opened from another design, return to that previous design
* From a page inside a flow, return to the flow editor
In the page editor, the canvas pulses on the first press as visual feedback.
# Layers
Source: https://docs.subframe.com/learn/design-mode/layers
Navigate and organize the element hierarchy.
The Layers panel shows your design's element hierarchy as a tree structure.
The **Layers** section is always visible in the left panel while you design.
### Reordering elements
Drag layers up or down to reorder them within the hierarchy.
### Renaming layers
Double-click any layer name to rename it. Use descriptive names to make complex designs easier to navigate.
## FAQ
Some layers are locked because they're essential for the page or component to function. For example, most page layouts have a "children" layer that cannot be deleted because it's needed to render the actual page contents.
# Page layouts
Source: https://docs.subframe.com/learn/design-mode/page-layouts
Apply reusable navigation and modal layouts to your pages.
Page layouts are reusable templates that wrap your page content with headers, sidebars, modals, or drawers.
All layouts have a slot property called `children` that cannot be deleted. Any page contents will be rendered inside this slot.
## Applying a layout
1. Open a page in the editor.
2. Ensure you don't have any elements selected. You should see layouts in the right sidebar.
3. Click on the layout to apply it to the page.
## Creating a new layout
1. Open a page in the editor.
2. Ensure you don't have any elements selected. You should see layouts in the right sidebar.
3. Click on the **+** new layout button.
4. Select a layout template or start from blank to create your layout.
# Previewing designs
Source: https://docs.subframe.com/learn/design-mode/preview
Preview your designs and test interactions.
Preview mode shows how your design behaves outside the editor. Interactions like hover states work in preview.
To preview your design, click the **eye** icon in the navbar, or press Shift + Space. From the flow editor, the same shortcut previews the selected page, or the flow's first page when nothing is selected.
# Quick actions
Source: https://docs.subframe.com/learn/design-mode/quick-actions
Set properties and take actions on elements.
Quick actions let you set properties and take actions like [quick edit with AI](/learn/ask-ai/making-quick-edits), [create components](/learn/components/overview), or [create snippets](/learn/snippets/snippets).
1. Right-click or press /
2. Select an action
# Responsive design
Source: https://docs.subframe.com/learn/design-mode/responsive-design
Design across custom breakpoints for every screen size.
Every project starts with two breakpoints, and you can add as many as you need:
* **Desktop** — The base layer. Applies at every width and owns everything wider than your largest breakpoint.
* **Mobile** — Applies at 767px and below.
Each breakpoint other than Desktop is a **max-width** breakpoint: it applies from the next-narrower breakpoint up to its own width. The base **Desktop** layer has no media query, so its styles apply everywhere unless a narrower breakpoint overrides them.
## Switching breakpoints
Use the **Breakpoint** selector at the top of the Inspector to switch the breakpoint you're viewing and editing. Each option shows the breakpoint's device icon, name, and the width range it covers.
You can also:
* Press Cmd + K and search for a breakpoint by name
* Drag the handles on either edge of the page stage to resize it—the stage snaps to the breakpoint matching the width you release at
## Managing breakpoints
Click the button next to the **Breakpoint** selector to open the **Edit breakpoints** popover.
* **Add** — Click to add a breakpoint, then set its name and max width.
* **Rename** — Edit the name field. The name becomes the prefix in your exported code, so keep it short (for example, `Tablet` exports as `tablet:`).
* **Set width** — Edit the width field to change the max width a breakpoint covers.
* **Delete** — Open the row's menu and select **Delete**.
The base **Desktop** breakpoint can't be deleted, and its width range is derived from your other breakpoints.
## Creating breakpoint overrides
You can override properties at any breakpoint, on both pages and components.
1. Switch to the breakpoint you want to override
2. Select an element
3. Modify any property in the Inspector
4. An override indicator (pink dot) appears next to the property
While a non-base breakpoint is active, a **Making edits to** bar appears at the top of the canvas. Use it to toggle between editing the **current** breakpoint and **All breakpoints** at once.
Click the pink dot to open its menu:
* **Reset override** — Remove the override and fall back to the value from a wider breakpoint.
* **Apply to all breakpoints** — Promote the overridden value to the base layer so it applies everywhere, clearing that override from every breakpoint.
## Code export
Each breakpoint exports as a max-width Tailwind variant named after the breakpoint. An override on the **Mobile** breakpoint uses a `mobile:` prefix; an override on a breakpoint you named **Tablet** uses a `tablet:` prefix:
```tsx theme={null}
{/* Row on desktop, column at tablet and below, tighter gap on mobile */}
```
The generated Tailwind config registers each breakpoint under `screens` with its max width, so the variants map to standard media queries.
## FAQ
If you don't see a pink dot, no override is being set.
Overrides only work for styles that translate to CSS, so some properties without a CSS equivalent cannot have
a breakpoint override.
# Share designs
Source: https://docs.subframe.com/learn/design-mode/share-designs
Let your teammates and external stakeholders view your designs.
All team members have access to your designs by default. The easiest way to share your designs is to share the direct link.
## Invite teammates
1. Click the **Share** button in the top left, next to your avatar
2. Enter one or more teammate emails in the input field, separated by commas
3. Select a role, then press **Invite**. Everyone you invite receives an email to view the designs, and an invite to join your team if they're not already a member.
## Share public view-only link
1. Click the **Share** button in the top left, next to your avatar
2. Click the toggle to enable public sharing
3. Copy the link that appears
Anyone with the link can view the design, even if they're not part of your team or logged in.
# Working with stacks
Source: https://docs.subframe.com/learn/design-mode/working-with-stacks
Master stack-based layouts with direction, spacing, and alignment.
Stacks are the most fundamental building block in Subframe. They're essential for layout—equivalent to flexbox in code and auto-layouts in Figma.
## Grouping elements
To wrap elements in a stack:
1. Select one or more elements
2. Press Cmd + G, or right-click to open [quick actions](/learn/design-mode/quick-actions) and select **Wrap in Stack**
## Updating layout and alignment
Select a stack to configure its layout in the Inspector:
* **Direction** — Vertical, horizontal, or wrap
* **Gap** — Space between children (pixels or "fill" for space-between)
* **Alignment** — How children are positioned (start, center, end, space between, space around, stretch)
## FAQ
First, ensure your parent stack has enough space (set width / height to fill).
**Solution 1 — Make the sibling fill (recommended)**
1. Select the sibling element
2. Set width / height to **Fill**
The sibling expands and pushes the other element to the opposite side.
**Solution 2 — Set gap to fill**
1. Select the parent stack
2. Set gap to **Fill**
Alignment applies to all children in a stack, so you need to wrap the element in its own stack.
1. Wrap the element in its own stack
2. Set the wrapper stack's alignment as needed
3. Set the wrapper stack's width or height to fill if needed
The wrapper stack lets you control that element's alignment independently.
# Overview
Source: https://docs.subframe.com/learn/editor/overview
Learn the Subframe editor interface and navigate between editing modes.
The Subframe editor is where you design pages, build components, and export production-ready code. Switch between three modes depending on your task, and open Ask AI from any of them.
## Editor modes
You can use the mode buttons in the top-right of the editor to switch between the following modes:
Edit visually with drag-and-drop, responsive canvas, and layers & properties.
Build interactive prototypes from your designs by annotating and chatting with AI
View code, set up MCP server, and export to AI coding tools like Claude Code, Cursor, and Codex.
[Ask AI](/learn/ask-ai/prompt-to-design) is available in every mode — click the **Ask AI** button in the toolbar or press Cmd + / to open the docked panel and generate pages, components, and theme changes from a prompt.
## Editor layout
The editor consists of three main areas: left panel, canvas (center), and Inspector (right panel).
### Left panel
You have the following panels available to you:
**Pages** — Switch between pages and flows in your project. Navigate to different designs or subcomponents when editing components.
**Layers** — View and organize the element hierarchy for the current page. Select, reorder, or group elements. Shows the complete component tree.
### Canvas (center)
Your design surface where elements appear at their actual rendered size. The canvas shows exactly how your design will look at the current breakpoint.
Unlike infinite canvas tools, Subframe uses flexbox for layout (not absolute positioning). Elements flow and arrange using Stacks, resize responsively, and adapt to different screen sizes automatically. If you're familiar with Figma, this works similarly to auto-layout.
A toolbar at the bottom of the canvas gives you quick access to actions, the **Insert** panel for browsing components and snippets, comments, and annotations.
### Inspector (right panel)
This is the context-aware properties panel that changes based on editor mode and selected element.
For example, in design mode, you'll see layout, sizing, colors, backgrounds, borders, shadows, padding, overflow, and component properties. In code mode, you'll see the Inspect tab, which shows the code for the selected element.
### Show and hide panels
Press Cmd + \\ to cycle through panel layouts: both panels visible, left panel collapsed, then both panels hidden. Press Cmd + . to show or hide all panels at once, including the Ask AI panel.
## Responsive breakpoints
Switch between breakpoints using the **Breakpoint** selector in the Inspector panel. Every project starts with **Desktop** and **Mobile**, and you can add custom breakpoints for any screen size.
**Desktop (base)** — Applies at every width. Elements use their base flex properties.
**Mobile and custom breakpoints** — Add overrides for properties that need different values below a given width.
We recommend you design for desktop first, then add overrides only where needed. For details, see [Responsive design](/learn/design-mode/responsive-design).
## Collaboration
Multiple team members can edit simultaneously. See who's viewing or editing with avatars at the top of the left panel.
Click the **Share** button in the top left, next to your avatar, to invite team members or create view-only links. Set roles (viewer/editor) and toggle public preview access.
# Elements
Source: https://docs.subframe.com/learn/elements/elements
The building blocks of all designs.
Subframe has four elements that form the foundation of all designs:
* **Stack** — Arranges child elements using flexbox. Set direction (vertical, horizontal, wrap), gap, alignment, and padding.
* **Text** — Creates headings, paragraphs, and labels. Double-click to edit content.
* **Icon** — Displays SVG icons from Feather Icons, Lucide, Font Awesome, Heroicons, or custom uploads.
* **Image** — Displays pictures from URLs or uploaded files.
**Components** are reusable UI elements built from these four elements. See [Components](/learn/components/overview) to learn more.
# FAQ
Source: https://docs.subframe.com/learn/faq
Answers to frequently asked questions about Subframe.
Yes. [Import your theme](/learn/theme/importing-tokens) first, then [import your components](/learn/components/overview#importing-components) by pasting their source code — the AI agent recreates each one in Subframe.
You can use the Subframe's [MCP server](/guides/mcp-server) to build with tools like Claude Code, Cursor, or Codex.
We recommend wrapping your Subframe components in your own logical components. This way, you can make changes to the component without having to unsync them. For more information, see our guide on [syncing components](/concepts/syncing-components).
Pages often require refactoring and additional business logic after export. For that reason, pages are never synced via CLI and always copy / pasted. For more information, see our guide on [exporting pages](/concepts/exporting-pages).
All of your code is synced to your codebase and yours to keep. The libraries that Subframe components depend on are [open source](https://github.com/SubframeApp/subframe) and will remain available should you cancel your subscription or if Subframe were to shut down.
You can copy your Figma pages as PNGs and ask Subframe AI to recreate the design in Subframe.
Join our [Slack Community](https://join.slack.com/t/subframecommunity/shared_invite/zt-380uma6dv-_lr7_bDLU5DJcoygfUYkeQ) to get fast support from the Subframe team and other members of the community.
You can find our [Terms of Service](https://policies.subframe.com/tos), [Privacy Policy](https://policies.subframe.com/privacy), and [Data Processing Agreement](https://policies.subframe.com/dpa) on our policies site.
# Flows
Source: https://docs.subframe.com/learn/flows/flows
Organize pages into flows.
Flows are folders for grouping related pages together. Flows are the set of pages used to create [prototypes](/learn/prototype-mode/overview).
## Creating a flow
1. Open the **Pages** section in the left sidebar
2. Click **New flow** at the bottom of the left sidebar
## Renaming a flow
Double-click a flow name in the pages sidebar to rename it. Press Enter to confirm or Esc to cancel.
## Adding pages to a flow
1. Click on a page
2. Select **Move to...** > select the flow (or **New flow\...**)
You can also drag pages directly into a flow from the pages list when editing a page.
## Editing pages on the flow canvas
You can edit pages directly on the flow canvas without opening them:
* Click an element on any page to select it, then edit its properties in the Inspector panel
* Drag an element from one page to another to move it between pages
* Use the **Layers** section in the left panel to view and navigate each page's element hierarchy
To open a page in its dedicated editor, click **Open page editor** in the page's header.
## Duplicating a flow
1. Click on a flow in the pages sidebar
2. Select **Duplicate**
Duplicating a flow copies all of its pages along with the flow's [prototype](/learn/prototype-mode/overview).
## Deleting a flow
1. Click on a flow in the pages sidebar
2. Select **Delete flow\...**
3. Confirm in the dialog
Deleting a flow also deletes every page inside it. To keep those pages, move them to another flow with **Move to...** before deleting.
Deleting a flow can only be undone using project-level version history
## FAQ
Up to you. We usually recommend organizing screens that are part of one user journey into a single flow. This includes all page states such as loading, error, and empty states.
# Working with AI agents
Source: https://docs.subframe.com/learn/guides/working-with-ai-agents
Walk through the full design-to-code workflow using Subframe with AI coding assistants.
This guide walks through the full design-to-code workflow with Subframe and an AI coding assistant. The demo uses Claude Code, but the same workflow applies to any MCP-compatible AI assistant.
## Getting started
Install the Subframe MCP server and agent skills for your AI assistant. See [Agent skills](/guides/skills) for setup instructions.
This guide uses Claude Code as the example, but the workflow works with any MCP-compatible AI assistant including
Cursor and Codex.
## Design with AI
You can ask your AI agent to design a page at any stage — during planning, mid-implementation, or as a standalone task. The agent uses the `design_page` tool to generate design variations in Subframe.
After kicking off a design, Subframe returns a flow URL. Open it to watch each variation appear as a page on the flow canvas as it finishes generating.
## Review design variations
The flow canvas is your review surface — variations sit side-by-side as real pages in your project. From here you can:
* **Click a page** to open it in the editor and refine visually
* **Use Ask AI** on any page to iterate further
* **Delete** the variations you don't want
## Combine variations
There are two ways to mix and match elements from different variations:
* **Ask your AI agent** — your agent has access to all variations. Ask it to combine elements from different ones, for example "use the header from variation 1 with the layout from variation 3."
* **Edit in Subframe** — open any variation page from the canvas, then use [Ask AI](/learn/ask-ai) to incorporate elements from other variations.
## Edit in the Subframe editor
Click any page on the flow canvas to enter the full design editor. From there you can:
* Chat for edits in the [Ask AI](/learn/ask-ai) panel
* Drag-and-drop in [Design mode](/learn/design-mode)
## Using designs with your AI agent
To hand the design to your AI agent, copy the MCP link and reference it in your prompt.
You can get the MCP link for any design by either:
* Copying the link from the browser address bar
* Copying the link under **Code** > **Inspect** in Subframe
## How AI agents implement your designs
Implementation varies depending on your setup. If you're evaluating Subframe for the first time, we recommend starting with a blank project.
Your AI agent creates a new project, installs Subframe with `/subframe:install`, then implements your designs. This is the fastest path to see the full workflow end-to-end.
1. Ask your AI agent to scaffold a new project
2. Run `/subframe:install` to install Subframe
3. Share the MCP link and ask the agent to implement the design
If your project already has Subframe configured, your AI agent can start developing immediately using your synced components.
1. Share the MCP link with your AI agent
2. Ask the agent to implement the design using your existing components
Your AI agent will ask whether to:
* **Use Subframe's theme and components** for pixel-perfect implementation matching the design
* **Use the design as inspiration** and map to your existing UI framework
Choose the approach that fits your project.
# Introduction
Source: https://docs.subframe.com/learn/introduction
Design visually with real components and export production-ready React code.
Subframe is a design tool for building product interfaces that lets you ship what you design:
* Design visually with real UI components
* Brainstorm designs & prototypes with AI that understands your product
* Export production-ready React code
This is the product guide. To learn how to use Subframe as a developer, see the [developer docs](/overview).
## How Subframe works
**Design-first, code-native.** Subframe replaces the traditional design-to-code hand-off process by allowing designers and developers to build with the same material: code.
Subframe is the source-of-truth for your theme, components, and documentation. Start with the pre-built library or
create custom components. Everything syncs as production code.
Design with real components in a drag-and-drop editor with full design control. Then, build and share interactive
prototypes that match your designs exactly.
Generate designs from prompts or images with AI that learns from your theme, components, existing pages, and system
prompt. The more you design, the smarter Subframe gets.
Components sync via CLI. Pages export as copyable React code or using an MCP server. Subframe generates code
programmatically without the use of AI, and you own all the code locally.
# Pages
Source: https://docs.subframe.com/learn/pages/pages
Design individual pages in your project.
Pages are used to design screens of your product using your design system.
## Creating a page
1. In the **Pages** section of the left sidebar, click the **+** button
2. Select **New page** to create a blank page
To generate a page design with AI instead, use [Ask AI](/learn/ask-ai/prompt-to-design).
## Renaming a page
Double-click a page name in the pages sidebar or on the flow editor canvas to rename it. Press Enter to confirm or Esc to cancel.
Page names can contain any characters, including spaces and symbols. When you [export a page](/concepts/exporting-pages), Subframe normalizes the name into a valid component name in the generated code.
## Duplicating a page
1. Right-click a page or click
2. Select **Duplicate**
## Sorting pages
Use the sort control at the top of the **Pages** view to change how pages are ordered:
* **Group by flow** — Pages organized under their [flows](/learn/flows/flows) (default)
* **Recently edited** — Most recently edited first
* **Recently created** — Most recently created first
* **A-Z** — Alphabetical by name
## FAQ
For page states like loading, empty, and error states, we recommend creating a separate page for each state. You can easily duplicate the page for this. Organize the screens together with a [flow](/learn/flows/flows) and use [annotations](/learn/prototype-mode/annotations) to describe how the screens are connected during handoff.
# Overview
Source: https://docs.subframe.com/learn/projects/overview
Create, manage, and organize projects for your team.
Each project has its own theme, components, and pages. All team members can access every project.
## Create a project
1. Open the **Projects** page — click the Subframe logo in the top left
2. Click **New project**
3. Enter a name
4. Choose how to start:
* **New project** — A clean slate with no theme or components. Best for importing your designs.
* **Start from prebuilt** — A ready-made theme and components to build on.
* **Duplicate existing project** — A copy of one of your existing projects.
5. Click **Create**
Free plans allow 1 project. Upgrade to Pro for unlimited projects.
## Switching projects
Open the **Projects** page to see all projects, then click a project to open it. Type to search if you have many.
## Manage all projects
Open the **Projects** page to see every project in one place. Search by name or sort by most recent or alphabetically to find a project quickly.
## Rename a project
1. Open the **Projects** page
2. Hover over the project and click
3. Enter the new name and click **Save**
## Duplicate a project
Hover over a project on the **Projects** page and click . This creates a new project with the same components, theme, and pages.
## Delete a project
Hover over a project on the **Projects** page and click .
Deleting is permanent. All pages, components, and theme data are erased.
## Sync design system across projects
This feature is only available on custom plans. Reach out to [**support@subframe.com**](mailto:support@subframe.com)
to learn more.
If you maintain a separate design system project:
1. Click the **project menu** next to the Subframe logo
2. Select **Sync project...**
3. Follow the prompts to merge updates
## Transfer a project to another team
To transfer a project to another team, email [support@subframe.com](mailto:support@subframe.com).
## Permissions
Only Admins and Editors can create, rename, duplicate, and delete projects.
## FAQ
You can't copy/paste elements between projects, but you can [import both components and pages from another project](/learn/components/overview#importing-from-another-project).
# Project settings
Source: https://docs.subframe.com/learn/projects/project-settings
Configure project settings like code generation and AI preferences.
## Access project settings
1. Open your project in the editor
2. In the left sidebar, find the **Design System** section
3. Click the **Settings** icon next to **Design System**
You should see the project settings dialog.
## Import alias
Set the path prefix used for component imports in generated code.
**Default:** `@/ui/components`
This determines how import statements appear in your exported code:
```tsx theme={null}
import { Button } from "@/ui/components/Button"
```
Change this to match your project's directory structure and TypeScript path aliases.
## Company context
Provide context to help AI generate designs that are more relevant to your product.
#### Company name
Your company or product name. AI uses this to personalize generated content and copy.
#### Description
Describe your company, product, and design preferences. This helps AI understand:
* What your product does
* Your target audience
* Tone and style preferences
* Any specific design guidelines
The more context you provide, the better AI can tailor designs to your brand and product.
## Icon imports
Choose how icons are imported in generated code.
#### Import as component
Icons are imported as React components:
```tsx theme={null}
import { FeatherIcon } from "@/ui/components/FeatherIcon"
```
```tsx theme={null}
```
#### Import as string
Icons are referenced by name as strings:
```tsx theme={null}
import { Icon } from "@/ui/components/Icon"
```
```tsx theme={null}
```
If your project has uploaded custom icons, you must use "Import as component". The string option is disabled when
custom icons are present.
# Version history
Source: https://docs.subframe.com/learn/projects/version-history
View and restore previous versions.
Version history lets you restore all changes for your entire project – from page designs, to component edits, to theme changes. Subframe automatically saves versions of your work as you make changes.
You can restore previous versions at the project-level, or for individual pages, components, or theme.
## Project version history
Use project-level version history when you need to roll back multiple changes at once.
1. Click the **project menu** next to the Subframe logo
2. Select **Version history**
3. Select the version timestamp from the dropdown in the banner at the top
4. Click **Restore** to revert to that version.
## Page, component, and snippet version history
1. Open the page, component, or snippet in the editor
2. Click the **•••** options menu beside its name
3. Select **Version history...**
4. Select a version from the timeline to preview it
5. Click **Restore** to revert the page, component, or snippet to the selected version
## Theme version history
1. Open **Theme** under **Design System**
2. Click **•••** in the theme toolbar
3. Select **Version history**
4. Select a version from the timeline to preview it
5. Click **Restore** to revert the theme to the selected version
## Restoring versions
Version history for an individual page, component, snippet, or theme restores only that item. Use it to undo
a change without affecting the rest of your project.
Project version history restores the entire project, including all pages, components, snippets, and the theme.
Use it to recover deleted items or roll back changes across multiple parts of your project.
## Version retention
Version history retention varies by plan:
* **Free plan**: 24-hour history
* **Pro plan**: 7-day history
* **Custom plans**: Longer version history available
Contact the Subframe team for custom version history retention options.
# Annotations
Source: https://docs.subframe.com/learn/prototype-mode/annotations
Add implementation notes to guide AI prototype generation.
Annotations let you add implementation notes to your design. They are used by prototype mode to instruct AI how your designs should behave.
## Adding annotations
1. Click **Annotate** in navbar
2. Click on an element in canvas
3. Type the annotation
4. Press Cmd + Enter to save
You should see blue dots on elements for each annotation.
## Exporting to code
Annotations are also useful for documenting your design for handoff. Any annotation will be exported as comments in code:
# Interactions
Source: https://docs.subframe.com/learn/prototype-mode/click-through-prototypes
Create clickable prototypes in preview mode.
Interactions let you create clickable prototypes with page navigation and interactions on click, hover, and right click in preview mode — without using AI.
Use it to connect pages, open overlays (dialogs and drawers), show popovers, tooltips, dropdowns, and context menus.
## Adding an interaction
1. Select the element you want to make interactive.
2. In the inspector, find the Interactions section and click the + icon.
3. Choose a trigger, an action, and configure the destination or alignment.
## Triggers and actions
### On click
* **Navigate to...** — Opens a different page.
* **Open overlay...** — Opens a page as an overlay (dialog or drawer) on top of the current page.
* **Close overlay** — Closes the currently open overlay.
* **Open dropdown** — Opens a dropdown menu anchored to the element.
* **Open popover** — Opens a popover with custom contents anchored to the element.
Tip: Make sure pages opened as an overlay use a dialog or drawer layout to render properly over the existing page.
### On hover
* **Open tooltip** — Shows a tooltip on hover.
* **Open popover** — Shows a popover on hover with custom contents.
### On right click
* **Open context menu** — Opens a context menu at the cursor position.
## Editing popover and overlay contents
Click **Edit contents** to open the popover, tooltip, dropdown, or context menu contents in the editor. Design the contents the same way you would any other element on a page.
For overlays, select the destination page and design it using a dialog or drawer layout.
## Previewing interactions
Try out interactions in preview mode. Click the preview icon in the navbar or in the Interactions section to enter preview and test your interactions.
### Limitations
* Each element supports one interaction. You cannot stack multiple triggers (e.g. on hover + on click) on the same element. Wrap elements in stacks with different interactions instead.
* Preview interactions are separate from Prototype mode. In the future, interactions will automatically feed into AI-generated prototypes as prompt annotations.
* Click-through prototypes are best for simple navigation flows, opening overlays, and testing popover behavior. For complex interactions like form handling, state management, and dynamic data, use [Prototype with AI](/learn/prototype-mode/overview).
# Prototype with AI
Source: https://docs.subframe.com/learn/prototype-mode/overview
Create interactive prototypes with AI-powered code generation.
Prototypes let you build real web apps from your page designs, synced with your design system. They are created from screens in a [flow](/learn/flows/flows) by chatting and adding annotations.
## Creating a new prototype
1. Create a new [flow](/learn/flows/flows)
2. Design your screens in design mode
3. Add [annotations](/learn/prototype-mode/annotations) describing functionality
4. Click **Prototype** in navbar, or click **New prototype** under your flow in the pages sidebar
5. Type your first message in chat, then click **Send**
AI may take a few minutes to create a prototype and will preview it when finished.
## Iterating on prototypes
There are two ways to iterate on your prototype:
* **Chat** — Use prototype chat to add features, fix bugs, or refine interactions
* **Design updates** — Make changes in design mode, then apply them to your prototype
#### Chat
1. Open prototype
2. Type your prompt in the chat input
3. Press Enter to send
#### Making design updates
1. Make design changes or update annoations
2. Navigate to prototype mode
3. Click **Apply** in the chat bar
AI will update the code to match your new design.
Design changes don't sync from prototype back to pages.
#### Getting unstuck
If your prototype isn't working as expected, you have two options:
* **Start over** — Click **Start over** in the chat bar to regenerate a prototype from scratch using your current annotations
* **Revert to previous version** — Find a version in your conversation and click on to revert to that version
## Viewing code
1. Open prototype mode
2. Find version you want to inspect, click on
3. You should now see the code
## Downloading code
1. Open prototype mode
2. Find version you want to export, click on
3. Top-right, click on **Download**
This will download your prototype code as a Vite app in a zip file.
## Sharing prototypes
1. Click the **Share** button in the top left, next to your avatar
2. Toggle on **Share link to latest prototype**
3. Copy and paste the generated link
## Best practices for design
* Create a new screen for each page (including dialogs) and states (e.g. loading, empty state, selected tabs)
* Subframe prototyping is meant for smaller prototypes. Avoid prototypes larger than 6+ designed screens—it will get slow
* If you need to create larger prototypes, try [exporting to code](/installation) and using Claude Code, Cursor, or Codex
## FAQ
Not yet. To keep your designs in sync with your prototype, we recommend using chat and annotations for updating functionality, and applying changes from design mode for updating designs.
1. Create two screens: one with the button, one with the dialog open
2. Add an [annotation](/learn/prototype-mode/annotations) on the button to open the dialog on click
# Quickstart
Source: https://docs.subframe.com/learn/quickstart
Create your first project and export a design to code in minutes.
## Create your first project
1. Sign up at [subframe.com](https://subframe.com) to create your first project.
2. Configure your theme with quick presets or customize later in the Theme page.
3. Select a default navigation style for your app.
After onboarding, you will see a blank project with the base component library set up.
## Design your first page
You can create a new page with AI or start from scratch.
Ask AI generates multiple design variations using your theme and components.
1. Open your project and click the **Ask AI** button in the toolbar (or press Cmd + /).
2. Type a prompt for the page you want to design.
3. Submit your prompt and wait for designs to generate.
4. Variations land as pages in a flow on your canvas — click a variation tile in the chat to open one.
Ask a follow-up prompt to refine a variation or combine ideas from different ones.
Switch to Design Mode to refine your page in the responsive page editor.
* Drag-and-drop elements on the page to rearrange.
* Select an element and click the **+** Insert buttons to add new elements inline.
* Edit styles and properties in the Inspector panel on the right.
* Right-click or press / for contextual edits to a selected element.
## Set up your codebase
Next, we'll create a new project using the Subframe Vite starter kit to implement your design. Open a new folder on your computer using Cursor or your favorite IDE.
See [Installation](/installation) for detailed instructions on setting up Subframe in a new or existing project.
In the page editor, open **Code** > **Installation** to view Claude Code, Cursor, or Codex instructions specific to your project.
To learn more about the MCP server and its capabilities, see [MCP Server](/guides/mcp-server).
Copy the installation prompt in **Code** > **Installation** for Claude Code, Cursor, or Codex and run it in your project's root folder.
Switch to **Code** > **Inspect** and copy the MCP link from Subframe to add to your prompt:
> "Implement the design from this Subframe page \[YOUR\_PAGE\_MCP\_LINK]"
AI fetches the code via MCP and adds business logic.
If you created a new project, you can start your development server with:
```bash theme={null}
npm install
npm run dev
```
Open `localhost:3000`—your page renders exactly as designed.
You're all set! Your Subframe project is now connected and ready for development.
## Next steps
Understand how Subframe works under the hood.
Discover how to design using Subframe's visual editor.
Dive deeper into using Subframe for developer handoff.
Explore AI-powered design features.
# Snippets
Source: https://docs.subframe.com/learn/snippets/snippets
Save and reuse design patterns.
Snippets are reusable designs that can be inserted anywhere. They are usually composed designs—similar to molecules and organisms in an atomic design system.
Unlike components, changes to snippets will not affect anywhere that uses them.
## Creating a snippet
1. Right-click or press / to open [quick actions](/learn/design-mode/quick-actions)
2. Select **Save as snippet**
3. Preview your snippet and give it a name
4. Click **Create**
## Inserting snippets
There are two ways to insert snippets:
* Open the **Insert** panel from the toolbar at the bottom of the canvas and select **Snippets**
* Search for the snippet using the [quick insert](/learn/design-mode/adding-elements) menu
See [adding elements](/learn/design-mode/adding-elements) for more info.
## Managing snippets
Access your snippets at any time from **[Snippets](https://app.subframe.com/library?component=snippets)** in the left panel. You can edit or delete snippets from here.
Use the sort control at the top of the list to order snippets by **A-Z** (default), **Recently edited**, or **Recently created**.
# Adding custom fonts
Source: https://docs.subframe.com/learn/theme/adding-custom-fonts
Upload custom font files to use in your theme.
Upload custom font files to use proprietary or licensed fonts in your project.
Custom fonts require a Pro plan. Free plan includes Google Fonts.
## Uploading fonts
1. Navigate to **Theme > Typography**
2. Click **Upload font**
3. Select or drag font files into the dialog
4. Review and edit font metadata:
* **Font family name** — Editable per font group
* **Weight** — Dropdown for static fonts, range display for variable fonts
5. Click **Add fonts**
## Supported formats
**File formats:** WOFF, WOFF2, TTF, OTF
**Font types:**
* **Static fonts** — One file per weight (e.g., `MyFont-Regular.woff2`, `MyFont-Bold.woff2`)
* **Variable fonts** — Single file with weight range support
## Font validation
Subframe groups uploaded files by font family. Follow these rules when uploading:
* **Same file format** — All files in a family must use the same format (don't mix WOFF and TTF)
* **Same font type** — Static and variable fonts cannot be mixed in one family
* **Unique weights** — Each weight must be unique within the family
* **No Google Font conflicts** — Custom font names cannot match existing Google Font names
If validation fails, edit the font family name or remove conflicting files.
## Installing fonts in your codebase
Custom fonts require additional setup in your project to load correctly. See [Loading fonts](/installation#install-fonts) for instructions.
## Updating fonts
To add weights to an existing custom font:
1. Upload additional font files for each new weight
2. Make sure the font family name matches the original font
3. After adding, additional weights will be available for the font
## Troubleshooting
Check that:
* Upload completed successfully (toast notification appeared)
* Font family name doesn't conflict with Google Fonts
* File format is supported (WOFF, WOFF2, TTF, OTF)
Verify:
* Font files synced via `npx @subframe/cli@latest sync --all`
* `@font-face` declarations are in your CSS
* File paths match actual font file locations
* No CORS errors in browser console
For static fonts, the slider only shows uploaded weights. Upload additional font files to enable more weight options.
For variable fonts, the slider shows the full supported range from the font file metadata.
Remove existing font files or use a different family name. Subframe requires consistent format and type (static vs variable) within each font family.
# Customizing theme
Source: https://docs.subframe.com/learn/theme/customizing-theme
Define design tokens for colors, typography, borders, corners, and shadows.
Your theme defines design tokens for your project. Design tokens are reusable values for colors, typography, borders, corners, and shadows. Each project contains its own theme used to generate a custom Tailwind CSS configuration.
## Navigating the theme page
Open **Theme** under **Design System** in the left sidebar, or press Cmd + K and search "Theme". Expand the **Theme** tab to jump to any section (Colors, Typography, Borders, Corners, Shadows).
A toolbar at the top of the theme page holds the main actions — **Ask AI**, **Import**, **Export** — plus a **⋯** menu with **Version history**, **Reset theme**, and **Add dark mode** / **Add light mode**. When dark mode is enabled, the menu shows **Remove dark mode** / **Remove light mode** instead. See [Dark mode](/guides/dark-mode) for details.
All changes save automatically and apply immediately across your project.
See also: [Importing tokens](/learn/theme/importing-tokens), [Exporting theme](/learn/theme/exporting-theme), [Adding custom fonts](/learn/theme/adding-custom-fonts)
## Finding token usage
Right-click any color, typography, border, corner, or shadow token and select **Find usage...** to see every design that references it. Search the list, click a design to preview it, then click **Go to component** to jump into the editor.
Check usage before renaming or deleting a token so you know what your changes will affect.
## Edit theme with Ask AI
Use Ask AI to update your theme from a prompt:
1. Click **Ask AI** in the sidebar
2. Describe your changes: "warmer tones, rounder corners", "make it brutalist", or "retro theme with muted colors and bold typography"
3. Review the preview — AI updates colors, typography, corners, and shadows
4. Click **Apply** to update your theme
Ask AI can also rename or delete tokens when the changes call for it. All component references update automatically.
AI theme generation affects your entire project. Review the preview before applying.
## Colors
Subframe organizes colors into **color tokens** grouped by **folders**.
```tsx theme={null}
Neutral background with default text color
```
**Color tokens** define individual colors for your design system. They can be used for specific UI purposes like Brand Primary, Default Background, or Neutral Border. Tokens can reference other tokens as aliases (e.g. Brand Primary → Brand 700).
**Folders** group related color tokens together. Organize tokens however you want — by brand, by feature, by shade scale, or any grouping that fits your system. A default **Colors** folder holds standalone tokens.
To quickly add tokens from an existing design system, see [Importing tokens](/learn/theme/importing-tokens).
### Creating color tokens and folders
**New color token:**
1. Click **New color** in the **Colors** section header, click a folder's **...** menu and select **New color token**, or click the **+** button at the end of a folder's token row
2. Name your token descriptively: `accent-primary`, `surface-elevated`
3. Set color via picker or reference existing token
**New folder:**
1. Click **New folder** in the Colors section header
2. Rename your folder by clicking its name
3. Add tokens manually or via import
**Reordering:** Drag folders or tokens to reorder. Use the **...** menu on a folder to move it up or down.
Deleting a folder deletes all tokens inside it. Any component references to deleted tokens are detached.
### Editing color tokens
Click any token to open color picker. Set a direct color value or reference another token as an alias.
## Typography
Text tokens define typography styles with font family, size, weight, line height, and letter spacing.
```tsx theme={null}
Heading 1
```
### Creating text tokens
1. Click **+** in Typography section
2. Rename and adjust properties
New tokens duplicate the properties from the first token. Typography tokens auto-sort based on font size on the theme page.
### Editing text tokens
Click any text token to edit:
* **Font size** — Slider in pixels
* **Line height** — Slider in pixels
* **Font weight** — Slider based on font's supported weights
* **Letter spacing** — Slider from `-0.1em` to `0.1em`
### Changing font families
Click the font name card at the top to select a different font family. This updates all tokens using that font. Choose from a curated list of Google Fonts or custom uploaded fonts.
When dark mode is enabled, the font family card shows separate Light and Dark selectors so you can pair different fonts per mode.
### Adding custom fonts
Upgrade to the Pro plan to add custom fonts.
Upload WOFF, WOFF2, TTF, or OTF font files, with support for variable fonts.
For detailed upload steps and codebase installation, see [Adding custom fonts](/learn/theme/adding-custom-fonts).
## Borders
Border tokens define composite border styles consisting of color, width, and style.
When generating code, border styles turn into explicit values for the border:
```tsx theme={null}
```
### Creating border tokens
1. Click **+** in Borders section
2. Rename and adjust style, size, color
### Editing border tokens
Click any border token to edit:
* **Border style** — Solid or dashed
* **Border size** — Pixel width
* **Border color** — Color token reference or direct value
## Corners
Corner tokens define border radius values for consistent rounded corners.
```tsx theme={null}
Element with medium corner radius
```
### Creating corner tokens
1. Click **+** in Corners section
2. Set radius value
Corner tokens auto-sort based on value.
### Editing corner tokens
Click any corner token to edit radius value in pixels.
## Shadows
Shadow tokens define box shadows with multiple layers for depth and elevation.
```tsx theme={null}
Element with small shadow
```
### Creating shadow tokens
1. Click **+** in Shadows section
2. Add, remove, or edit shadow layers
Shadow tokens auto-sort based on perceived shadow effect.
### Editing shadow tokens
Click any shadow token to edit individual shadow layers. Each layer has:
* **Inset** — Whether shadow appears inside element
* **X/Y offset** — Horizontal and vertical offset in pixels
* **Blur radius** — Blur amount in pixels
* **Spread radius** — Spread amount in pixels
* **Color** — Shadow color with opacity
Add multiple layers for complex shadow effects.
## Theme presets
Apply a theme preset to start fresh:
1. Click **⋯** in the toolbar and select **Reset theme**
2. Browse 10 preset styles
3. Customize brand colors, fonts, and corner radius
4. Toggle between Light and Dark mode
Each preset configures color palettes (brand, neutral, error, success, warning), typography, and corner radius. Customize further after applying.
Applying a theme preset replaces values in your current theme.
## Version history
View and restore previous versions of your theme:
1. Click **⋯** in the toolbar and select **Version history**
2. Browse the timeline and select a version to preview
3. Click **Restore** to revert to that version
Select **Exit version history** from the same menu to return to editing.
## Limitations
**Spacing not configurable** — Spacing tokens (padding, gaps) use Tailwind defaults. Extend your Tailwind config manually for custom spacing in your codebase.
# Exporting theme
Source: https://docs.subframe.com/learn/theme/exporting-theme
View your generated Tailwind config and sync theme changes to your codebase.
Your theme generates a Tailwind CSS configuration that your codebase uses for styling. You can view the generated config at any time and sync changes with the CLI.
## View Tailwind config
Click **Export** in the sidebar to view the generated Tailwind configuration for your theme. The dialog has two tabs:
* **Tailwind CSS v3** — Shows a `tailwind.config.js` file with your tokens as a JavaScript module
* **Tailwind CSS v4** — Shows a `theme.css` file with your tokens as CSS variables inside a `@theme` block
Click **Copy to clipboard** to copy either format.
The generated config includes colors, font sizes, font families, box shadows, border radius, container settings, and responsive breakpoints.
When [dark mode](/guides/dark-mode) is enabled, the export includes additional dark mode overrides:
* **Tailwind v3** — An additional `theme.css` file with `:root` and `.dark` CSS variable blocks, and `darkMode: 'selector'` in the config
* **Tailwind v4** — A `.dark` block and `@custom-variant dark` declaration in the `theme.css` file
## Sync to code
Run the CLI to sync your theme and components to your codebase:
```bash npm theme={null}
npx @subframe/cli@latest sync --all
```
```bash yarn theme={null}
yarn dlx @subframe/cli@latest sync --all
```
```bash pnpm theme={null}
pnpx @subframe/cli@latest sync --all
```
```bash bun theme={null}
bunx @subframe/cli@latest sync --all
```
This updates your local Tailwind config with new token values. Components automatically use the updated theme.
See [Installation](/installation) for CLI setup instructions.
# Importing tokens
Source: https://docs.subframe.com/learn/theme/importing-tokens
Import design tokens from Tailwind config, CSS, or JSON files to set up your theme.
Import design tokens from your existing design system into Subframe. The theme importer supports Tailwind CSS (v3 and v4), CSS variables, and JSON files — including Figma design token exports.
## Supported formats
Subframe supports the following theme formats for import:
Paste or upload `.css` files containing `@theme` blocks with CSS custom properties:
```css theme={null}
@theme {
--color-brand-500: #3b82f6;
--color-brand-600: #2563eb;
--color-neutral-100: #f5f5f5;
}
.dark {
--color-brand-500: #60a5fa;
--color-brand-600: #3b82f6;
--color-neutral-100: #1f1f1f;
}
```
Subframe detects Tailwind v4 syntax automatically from `.css`, `.scss`, or `.less` files. Tokens declared inside a `.dark { }` block are imported as dark-mode values for their matching light tokens. Importing dark-mode tokens enables dark mode on your current theme automatically — see [Dark mode](/guides/dark-mode). Files with only a `.dark { }` block are also supported.
Paste or upload `.js`, `.ts`, `.mjs`, or `.cjs` config files:
```js theme={null}
module.exports = {
theme: {
extend: {
colors: {
brand: {
500: '#3b82f6',
600: '#2563eb',
},
},
},
},
}
```
Subframe parses the `theme.extend.colors` object from your config.
Paste CSS custom properties directly:
```css theme={null}
--color-primary-100: #eeeeee;
--color-primary-200: #dddddd;
--color-primary-300: #cccccc;
```
Color values can be hex, rgb, hsl, or modern CSS color syntax like `oklch()`, `lab()`, and `color()`. Wide-gamut colors (such as Display P3) are converted to the closest sRGB value.
Upload a `.json` file containing design tokens, following the W3C Design Tokens Format. This works with Figma's **variables** export.
Subframe reads color values and token structure from the JSON.
## Import your theme
Go to the **Theme** page and select **Import** from the sidebar.
Add your tokens by uploading or pasting:
* **Upload files:** Drag and drop or browse for files. Multiple files supported.
* **Code editor:** Click **Tailwind CSS (v4)** or **Tailwind CSS (v3)** to paste CSS or JavaScript directly. The editor provides syntax highlighting and detects the format automatically.
Subframe parses your input and shows a preview of detected tokens. Each token shows its status:
* **New** — token will be added to your theme
* **Updated** — token matches an existing token name and will update its value
* **Unchanged** — token already exists with the same value
When your imported tokens reference other variables (e.g. `var(--color-brand-500)`), Subframe automatically maps these as **aliases** — references to other tokens rather than hard-coded values. Aliases can reference other tokens in the same import or existing tokens in your theme. If a referenced variable can't be resolved, it's converted to a direct color value.
If your input includes dark-mode tokens, the preview shows the light and dark palettes side by side so you can review both before importing.
Click **Import** to update your theme. New tokens appear immediately in your theme, organized into folders matching the structure of your import. Use **Version History** to revert your import.
## Importing from Figma
You can export your Figma variables as design tokens and import them directly into Subframe:
1. Open **Variables** in your Figma file
2. Right-click on a collection and select **Export modes**
3. Open the ZIP file to see the JSON files for each mode's tokens
4. Upload the JSON file in the **Import** dialog in Subframe
You can upload multiple JSON files from different collections at once.
### Limitations:
* Subframe currently supports a single mode. Import one mode (e.g. light OR dark), not both.
* Figma does not export text styles as variables. Use a Figma plugin to export text tokens separately, or add text styles manually in your theme.
## Troubleshooting
Verify your file format:
* CSS files should contain `--` prefixed custom properties with valid color values — hex, rgb, hsl, or modern syntax like `oklch()` and `color()`
* Tailwind configs should export a `theme.extend.colors` object
* JSON files should contain color values in a supported design token format
* Remove preprocessor-specific syntax (Sass variables, Less mixins) before importing
Tokens with the same name as existing tokens in your theme are marked as updates. Rename the token in the preview step if you want to add it as a new token instead.
Alias references (e.g. `var(--color-brand-500)`) only resolve against other imported tokens or existing tokens in your theme. If the target token doesn't exist in either, the alias is converted to a direct color value.
# Why Subframe
Source: https://docs.subframe.com/overview
Subframe is an AI design tool that outputs production-ready React code.
Designers and developers have always worked in separate tools: designers make mockups, developers translate them to code. But what ships never quite matches what was designed, and developers had wasted hours making it that way.
Subframe is a design tool that eliminates the translation step. Designers use the same components you ship to production, so you spend more time building product, not translating designs.
This is the developer documentation. To learn how to use Subframe as a design tool, see the [product guide](/learn).
## Code quality matters
Unlike other AI design tools, **Subframe's code output is deterministic.** There's no LLM misinterpreting your design—what is designed is exactly what you get in clean React code, every time.
Our code philosophy:
* **Consistent** — All designs are created from the same consistent design system. No more custom, one-off implementations.
* **Open code** — Avoid platform lock-in. Code generated by Subframe lives in your codebase, copy/pasted or [synced via the CLI](/concepts/syncing-components).
* **Headless** — Subframe uses [Radix ↗](https://www.radix-ui.com/) under the hood, which maintains a cleaner separation of concerns between presentation and behavior.
* **AI-ready** — The generated code follows best practices to be modified further by AI tools like Claude Code, Cursor, Codex, Copilot. You can even connect these tools to Subframe's [MCP server](/guides/mcp-server) to grant direct access to your designs.
## Workflow example
Suppose your designer designs a sign in page in Subframe:
Subframe turns the designs into pixel-perfect React code determinstically. The generated code uses your design system components, with stubs for business logic:
```tsx SignInPage.tsx expandable theme={null}
import React from "react"
import { Button } from "@/ui/components/Button"
import { SocialSignInButton } from "@/ui/components/SocialSignInButton"
function SignInPage() {
return (
WelcomeLogin or sign up below
) => {
// [!code highlight:1]
// TODO: Implement Google sign in
}}
/>
) => {
// [!code highlight:1]
// TODO: Implement Apple sign in
}}
/>
)
}
export default SignInPage
```
```tsx Button.tsx expandable theme={null}
import React from "react"
import * as SubframeCore from "@subframe/core"
import * as SubframeUtils from "../utils"
interface ButtonRootProps extends React.ButtonHTMLAttributes {
disabled?: boolean
variant?: "brand-primary" | "brand-secondary" | "destructive-primary"
size?: "large" | "medium" | "small"
children?: React.ReactNode
icon?: SubframeCore.IconName
iconRight?: SubframeCore.IconName
onClick?: (event: React.MouseEvent) => void
className?: string
}
const ButtonRoot = React.forwardRef(function ButtonRoot(
{
disabled = false,
variant = "brand-primary",
size = "medium",
children,
icon = null,
iconRight = null,
className,
type = "button",
...otherProps
}: ButtonRootProps,
ref,
) {
return (
)
})
export const Button = ButtonRoot
```
```tsx SocialSignInButton.tsx expandable theme={null}
import React from "react"
import * as SubframeUtils from "../utils"
interface SocialSignInButtonRootProps extends React.ButtonHTMLAttributes {
disabled?: boolean
variant?: "facebook" | "google" | "apple"
onClick?: (event: React.MouseEvent) => void
className?: string
}
const SocialSignInButtonRoot = React.forwardRef(
function SocialSignInButtonRoot(
{ disabled = false, variant = "facebook", className, type = "button", ...otherProps }: SocialSignInButtonRootProps,
ref,
) {
return (
)
},
)
export const SocialSignInButton = SocialSignInButtonRoot
```
All that's left is to export it to your codebase and add business logic. You can do that manually or with AI assistants like Claude Code, Cursor, or Codex connected to our MCP server:
```tsx SignInPage.tsx expandable theme={null}
import React from "react"
import { Button } from "@/ui/components/Button"
import { SocialSignInButton } from "@/ui/components/SocialSignInButton"
// [!code ++:2]
import { useNavigate } from "react-router-dom"
import { signInWithGoogle, signInWithApple } from "@/lib/auth"
function SignInPage() {
// [!code ++:1]
const navigate = useNavigate()
return (
)
}
export default SignInPage
```
That's it.
You just went from design to production-ready code in minutes, without translating mockups or pixel-pushing. With Subframe, you focus on the real engineering work.
# Component directories
Source: https://docs.subframe.com/upgrading/component-directories
Subframe now syncs each component as a directory. What changed, why, and how to migrate.
Subframe now syncs each component as its own **directory** instead of a single file. This page explains what changed, why, and how to migrate an existing project.
## What changed
Previously, each component synced as a single flat file:
```
src/ui/components/
└─ Button.tsx
```
Now each component (and page layout) syncs as a directory:
```
src/ui/components/
└─ Button/
├─ Button.tsx // generated by Subframe — overwritten on every sync
└─ index.tsx // yours to edit — wraps and re-exports Button.tsx
```
`Button.tsx` is the component Subframe generates, exactly as before. `index.tsx` is a thin wrapper that re-exports it:
```tsx index.tsx theme={null}
"use client";
import { Button as ButtonComponent } from "./Button";
/**
* Add wrapper components and business logic here.
* If you modify this file, disable Subframe sync for it to prevent overwrites.
* Learn more: https://docs.subframe.com/concepts/syncing-components#wrapping-components
*/
export const Button = ButtonComponent;
```
Your imports don't change. `@/ui/components/Button` resolves to the directory's `index.tsx`, so existing code keeps working.
## Why
A per-component directory gives each component a home for everything that should live alongside it in code:
* **`index.tsx`** — a natural, stable place for your own wrapping logic and wrapper components, kept separate from the source Subframe generates.
* **`Button.md`** — component documentation describing what the component is and how it should be used.
* **In the future** — generated `.stories` files for Storybook, test files, and more.
It also makes [disabling sync](/concepts/syncing-components#disabling-sync) granular. `@subframe/sync-disable` works per file, so you can freeze your `index.tsx` while `Button.tsx` keeps receiving Subframe's visual updates — instead of having to freeze the whole component.
## Adding business logic
`index.tsx` is where your code goes. To extend a component, edit its `index.tsx` and add the `@subframe/sync-disable` marker so the CLI won't overwrite it on the next sync:
```tsx index.tsx theme={null}
// @subframe/sync-disable
import { Button as ButtonComponent } from "./Button";
export function Button({ onSubmit, ...props }) {
const [loading, setLoading] = useState(false);
async function handleClick() {
setLoading(true);
await onSubmit();
setLoading(false);
}
return ;
}
```
`Button.tsx` has no marker, so it keeps syncing — design changes from Subframe still flow in, while your logic in `index.tsx` is preserved. Anything importing `@/ui/components/Button` gets your wrapped version automatically, with no import changes.
## Migrating an existing project
The CLI migrates your project automatically as you sync. **There are no breaking changes** — imports are unchanged, so a component moving into a directory doesn't affect anything that imports it. The one exception is sync-disabled components, which need a quick import review (covered below).
Because nothing breaks, you can migrate **incrementally** — sync a few components at a time, or run a full sync to do everything at once. Flat and nested components coexist fine while you're partway through.
The migration runs in recent versions of the CLI. The commands below use `@subframe/cli@latest` so you always get the newest — if you've pinned an older `@subframe/cli`, update it before migrating.
Run a full sync to migrate everything at once:
```bash npm theme={null}
npx @subframe/cli@latest sync --all
```
```bash yarn theme={null}
yarn dlx @subframe/cli@latest sync --all
```
```bash pnpm theme={null}
pnpx @subframe/cli@latest sync --all
```
```bash bun theme={null}
bunx @subframe/cli@latest sync --all
```
Or sync specific components — `npx @subframe/cli@latest sync Button Alert` — to migrate just those. Either way, the CLI writes the new directory layout and removes the old flat files for the components it syncs.
If you expose Subframe components from a shared package using the `exports` field in `package.json`, update the subpath patterns to point to the new directory layout:
```json package.json theme={null}
{
"exports": {
"./components/*": "./ui/components/*/index.tsx",
"./layouts/*": "./ui/layouts/*/index.tsx"
}
}
```
The old `"./ui/components/*.tsx"` pattern no longer matches because the flat files have moved into per-component directories. See the [monorepo guide](/frameworks/monorepo) for the full setup.
If you'd added `@subframe/sync-disable` to a component file under the old layout, the CLI won't delete it — instead it **moves** it into the new directory (e.g. `components/Button.tsx` → `components/Button/Button.tsx`) and prints a warning listing each moved file.
Because the file moved one level deeper, two things need a quick check.
**Relative imports.** Paths that were correct when the file was flat are now off by one level — siblings, shared root files, and cross-directory references all shift:
```tsx theme={null}
// components/Button/Button.tsx
import * as SubframeUtils from "../utils"; // ❌ correct when flat
import * as SubframeUtils from "../../utils"; // ✅ shared root files
import { Tooltip } from "./Tooltip"; // ❌
import { Tooltip } from "../Tooltip"; // ✅ sibling components
// layouts/DialogLayout/DialogLayout.tsx
import { Dialog } from "../components/Dialog"; // ❌
import { Dialog } from "../../components/Dialog"; // ✅
```
**Exported prop types.** The directory's `index.tsx` re-exports the component (`export const Button = ButtonComponent`), so TypeScript has to be able to name the types in its signature. If a moved file declares its prop interfaces without `export`, you'll get errors like *`Exported variable 'Button' has or is using name 'ButtonRootProps' … but cannot be named`* (TS4023). Add `export` to those interfaces:
```tsx theme={null}
interface ButtonRootProps extends ... {} // ❌
export interface ButtonRootProps extends ... {} // ✅
```
Files the CLI regenerates already export their prop types, so this only affects files you'd frozen with `@subframe/sync-disable`.
A full migration touches every component, so the diff is large but mechanical — committing it separately keeps it easy to review.