Skip to main content
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.

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.
  • Headless — Subframe uses Radix ↗ 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 to grant direct access to your designs.

Workflow example

Suppose your designer designs a sign in page in Subframe:
Login page designed 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:
import React from "react"
import { Button } from "@/ui/components/Button"
import { SocialSignInButton } from "@/ui/components/SocialSignInButton"

function SignInPage() {
  return (
    <div className="flex h-full w-full flex-col items-center justify-center bg-white">
      <div className="flex w-64 flex-col items-center justify-center gap-8">
        <div className="flex flex-col items-center justify-center gap-1">
          <img
            className="h-16 flex-none object-cover"
            src="https://res.cloudinary.com/subframe/image/upload/v1767591134/uploads/302/zzfsz5tcwky0pkjkru9d.png"
          />
          <span className="text-heading-2 font-heading-2 text-default-font">Welcome</span>
          <span className="text-body font-body text-subtext-color">Login or sign up below</span>
        </div>
        <div className="flex w-full flex-col items-center gap-2">
          <SocialSignInButton
            variant="google"
            onClick={(event: React.MouseEvent<HTMLButtonElement>) => {
              // TODO: Implement Google sign in
            }}
          />
          <SocialSignInButton
            variant="apple"
            onClick={(event: React.MouseEvent<HTMLButtonElement>) => {
              // TODO: Implement Apple sign in
            }}
          />
        </div>
        <div className="flex h-px w-full flex-none flex-col items-center gap-2 bg-neutral-border" />
        <Button
          className="h-10 w-full flex-none"
          variant="brand-secondary"
          size="large"
          icon="FeatherMail"
          onClick={(event: React.MouseEvent<HTMLButtonElement>) => {
            // TODO: Implement email sign in
          }}
        >
          Continue with email
        </Button>
      </div>
    </div>
  )
}

export default SignInPage
import React from "react"
import * as SubframeCore from "@subframe/core"
import * as SubframeUtils from "../utils"

interface ButtonRootProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  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<HTMLButtonElement>) => void
  className?: string
}

const ButtonRoot = React.forwardRef<HTMLButtonElement, ButtonRootProps>(function ButtonRoot(
  {
    disabled = false,
    variant = "brand-primary",
    size = "medium",
    children,
    icon = null,
    iconRight = null,
    className,
    type = "button",
    ...otherProps
  }: ButtonRootProps,
  ref,
) {
  return (
    <button
      className={SubframeUtils.twClassNames(
        "group/3b777358 flex h-8 cursor-pointer items-center justify-center gap-2 rounded-md border-none bg-brand-600 px-3 text-left hover:bg-brand-500 active:bg-brand-600 disabled:cursor-default disabled:bg-neutral-200 hover:disabled:cursor-default hover:disabled:bg-neutral-200 active:disabled:cursor-default active:disabled:bg-neutral-200",
        {
          "h-6 w-auto flex-row flex-nowrap gap-1 px-2 py-0": size === "small",
          "h-10 w-auto px-4 py-0": size === "large",
          "bg-error-600 hover:bg-error-500 active:bg-error-600": variant === "destructive-primary",
          "bg-brand-50 hover:bg-brand-100 active:bg-brand-50": variant === "brand-secondary",
        },
        className,
      )}
      ref={ref}
      type={type}
      disabled={disabled}
      {...otherProps}
    >
      <SubframeCore.Icon
        className={SubframeUtils.twClassNames(
          "text-body font-body text-white group-disabled/3b777358:text-neutral-400",
          {
            "text-heading-3 font-heading-3": size === "large",
            "text-brand-700": variant === "brand-secondary",
          },
        )}
        name={icon}
      />
      <div
        className={SubframeUtils.twClassNames("hidden h-4 w-4 flex-none items-center justify-center gap-2", {
          "h-3 w-3 flex-none": size === "small",
        })}
      >
        <SubframeCore.Loader
          className={SubframeUtils.twClassNames(
            "font-['Inter'] text-[12px] font-[400] leading-[20px] text-white group-disabled/3b777358:text-neutral-400",
            {
              "text-caption font-caption": size === "small",
              "text-brand-700": variant === "brand-secondary",
            },
          )}
        />
      </div>
      {children ? (
        <span
          className={SubframeUtils.twClassNames(
            "whitespace-nowrap text-body-bold font-body-bold text-white group-disabled/3b777358:text-neutral-400",
            {
              "text-caption-bold font-caption-bold": size === "small",
              "text-brand-700": variant === "brand-secondary",
            },
          )}
        >
          {children}
        </span>
      ) : null}
      <SubframeCore.Icon
        className={SubframeUtils.twClassNames(
          "text-body font-body text-white group-disabled/3b777358:text-neutral-400",
          {
            "text-heading-3 font-heading-3": size === "large",
            "text-brand-700": variant === "brand-secondary",
          },
        )}
        name={iconRight}
      />
    </button>
  )
})

export const Button = ButtonRoot
import React from "react"
import * as SubframeUtils from "../utils"

interface SocialSignInButtonRootProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  disabled?: boolean
  variant?: "facebook" | "google" | "apple"
  onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void
  className?: string
}

const SocialSignInButtonRoot = React.forwardRef<HTMLButtonElement, SocialSignInButtonRootProps>(
  function SocialSignInButtonRoot(
    { disabled = false, variant = "facebook", className, type = "button", ...otherProps }: SocialSignInButtonRootProps,
    ref,
  ) {
    return (
      <button
        className={SubframeUtils.twClassNames(
          "group/f1948f75 flex h-10 w-full cursor-pointer items-center justify-center gap-2 rounded-md border border-solid border-neutral-border bg-white px-4 text-left hover:bg-neutral-50 active:bg-white disabled:cursor-default disabled:bg-white hover:disabled:cursor-default hover:disabled:bg-white active:disabled:cursor-default active:disabled:bg-white",
          className,
        )}
        ref={ref}
        type={type}
        disabled={disabled}
        {...otherProps}
      >
        <img
          className="h-5 w-5 flex-none object-cover"
          src={
            variant === "apple"
              ? "https://res.cloudinary.com/subframe/image/upload/v1711417561/shared/kplo8lv2zjit3brqmadv.png"
              : variant === "google"
              ? "https://res.cloudinary.com/subframe/image/upload/v1711417516/shared/z0i3zyjjqkobzuaecgno.svg"
              : "https://res.cloudinary.com/subframe/image/upload/v1711417550/shared/epocfym1zba2deri6krm.png"
          }
        />
        <span className="text-body-bold font-body-bold text-neutral-700 group-disabled/f1948f75:text-neutral-400">
          {variant === "apple"
            ? "Sign in with Apple"
            : variant === "google"
            ? "Sign in with Google"
            : "Sign in with Facebook"}
        </span>
      </button>
    )
  },
)

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:
SignInPage.tsx
import React from "react"
import { Button } from "@/ui/components/Button"
import { SocialSignInButton } from "@/ui/components/SocialSignInButton"
import { useNavigate } from "react-router-dom"
import { signInWithGoogle, signInWithApple } from "@/lib/auth"

function SignInPage() {
  const navigate = useNavigate()

  return (
    <div className="flex h-full w-full flex-col items-center justify-center bg-white">
      <div className="flex w-64 flex-col items-center justify-center gap-8">
        <div className="flex flex-col items-center justify-center gap-1">
          <img
            className="h-16 flex-none object-cover"
            src="https://res.cloudinary.com/subframe/image/upload/v1767591134/uploads/302/zzfsz5tcwky0pkjkru9d.png"
          />
          <span className="text-heading-2 font-heading-2 text-default-font">Welcome</span>
          <span className="text-body font-body text-subtext-color">Login or sign up below</span>
        </div>
        <div className="flex w-full flex-col items-center gap-2">
          <SocialSignInButton
            variant="google"
            onClick={async () => {
              await signInWithGoogle()
              navigate("/dashboard")
            }}
          />
          <SocialSignInButton
            variant="apple"
            onClick={async () => {
              await signInWithApple()
              navigate("/dashboard")
            }}
          />
        </div>
        <div className="flex h-px w-full flex-none flex-col items-center gap-2 bg-neutral-border" />
        <Button
          className="h-10 w-full flex-none"
          variant="brand-secondary"
          size="large"
          icon="FeatherMail"
          onClick={() => {
            navigate("/sign-in/email")
          }}
        >
          Continue with email
        </Button>
      </div>
    </div>
  )
}

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.
Last modified on February 12, 2026