cartwright
Designs

design.md spec (cartwright-design-v1)

Full schema reference for cartwright-design-v1 — the YAML-frontmatter format that powers design imports and exports in Cartwright v0.7.0.

design.md is Cartwright's portable design format. YAML frontmatter for structured config (palette, sections, metadata) + a Markdown body for designer notes. One file, no folder dependencies, round-trips cleanly between the registry and the file system.

The current schema version is cartwright-design-v1. The version string lives in the frontmatter as schema: and is validated on every import.

File structure

---
schema: cartwright-design-v1
slug: my-design                    # kebab-case, unique
name: My Design Name
description: One-paragraph description (max 280 chars).
mode: website                      # website | webshop | both
premium: false                     # optional, default false

tokens:
  prefix: my                       # CSS var prefix: --color-my-*
  palette:
    accent: "#0066cc"              # primary CTA / highlight
    accentDeep: "#003a99"          # hover state
    cream: "#fafaf9"               # page background
    sand: "#f5f5f4"                # surface / panel background
    ink: "#0a0a0b"                 # body text
    muted: "#737373"               # secondary text
  extraTokens:                     # optional, design-specific tokens
    color-my-glow: "rgba(0, 102, 204, 0.2)"
  fonts:                           # optional, Tailwind v4 @theme bindings
    sans: "Inter, sans-serif"
    mono: "JetBrains Mono, monospace"
  animations:                      # optional, CSS @keyframes bodies
    my-fade-in: "from { opacity: 0 } to { opacity: 1 }"

sections:
  - type: hero
    eyebrow: "Now in beta"
    headline: "Build something great"
    headlineAccent: "great"        # optional inline-underline accent
    tagline: "A short description that sits below the headline."
    cta: { label: "Get started", href: "/contact" }
    secondaryCta: { label: "Learn more", href: "/about" }   # optional
    microcopy: "MIT · Open source"  # optional
  # ... more sections
---

# My Design Name

Free-form Markdown body — designer notes, screenshots, attribution,
license info. NOT rendered on the site, only kept as reference for
fork-shops and code reviewers.

Top-level fields

FieldTypeRequiredNotes
schema"cartwright-design-v1" literalYesSchema version pin
slugstring (kebab-case, 1-50 chars)YesMust match ^[a-z0-9][a-z0-9-]*$, unique in registry
namestring (1-80)YesDisplay name in /admin/designs
descriptionstring (≤280)YesOne-paragraph blurb
mode"website" | "webshop" | "both"YesFilter in SetupWizard dropdown
premiumbooleanNoAdds ⭐ Pro badge when brand.features.cartwrightPlus === false
tokensobjectYesSee Tokens
sectionsarray (1-20)YesSee Sections

Tokens

The tokens block compiles to CSS variables injected at page render via lib/theme.ts:designToInlineCss().

tokens: {
  prefix: "cw",
  palette: { accent, accentDeep, cream, sand, ink, muted },
  extraTokens?: Record<string, string>,
  fonts?: { sans?: string, mono?: string },
  animations?: Record<string, string>,
}

prefix

Lower-case alphanumeric token namespace. Used as --color-{prefix}-{field} so palette colors don't collide between designs:

  • prefix: "cw"--color-cw-accent, --color-cw-cream, ...
  • prefix: "bold"--color-bold-accent, ...
  • prefix: "sol" → reuses the legacy sol-* tokens (compatible with old themeJson overrides)

Use a fresh prefix for new designs to avoid stepping on Studio (cw) or Webshop Classic (sol).

palette

The 6 core colors. Each maps to --color-{prefix}-{kebab(field)}:

FieldCSS varTypical role
accent--color-{p}-accentPrimary CTA, highlights
accentDeep--color-{p}-accent-deepHover state of accent
cream--color-{p}-creamPage background
sand--color-{p}-sandCard / panel background
ink--color-{p}-inkBody text
muted--color-{p}-mutedSecondary text

Per-shop overrides via BrandingSettings.themeJson write to these same 6 fields and emit AFTER the design pack tokens, so they win via CSS cascade.

extraTokens

Free-form CSS variables for design-specific tokens that don't fit the 6-color palette. Key is the variable name without the leading --. Value is any valid CSS color or length:

extraTokens:
  color-cw-terracotta: "#d97757"
  color-cw-oker: "#e8b339"
  color-cw-code-bg: "#1a1a1b"
  radius-cw-card: "16px"

Use these for the design-specific tokens your homepage component references directly (e.g., Studio's terracotta + oker pair, Bold's electric-yellow paper).

fonts

Tailwind v4 reads --font-sans and --font-mono via the font-sans and font-mono classes. Override here to swap typography:

fonts:
  sans: "Inter, ui-sans-serif, system-ui, sans-serif"
  mono: "JetBrains Mono, ui-monospace, monospace"

Caveat: this only updates the CSS variables. To actually load a non-Geist font, you still need to add it to app/layout.tsx via next/font/google (see Custom fonts).

animations

Optional @keyframes bodies. Codegen emits these to themes/<slug>.css when scaffolded via scripts/design-import.ts:

animations:
  cw-caret-blink: "0%, 60% { opacity: 1 } 61%, 100% { opacity: 0 }"
  my-slide-up: "from { transform: translateY(20px); opacity: 0 } to { transform: translateY(0); opacity: 1 }"

Sections

The sections array is rendered top-to-bottom on the homepage. 7 section types are supported:

hero

- type: hero
  eyebrow: "v0.6 launch"          # optional badge above headline
  headline: "Ship software that ships itself"
  headlineAccent: "ships"         # optional inline highlight
  tagline: "One-paragraph lead under the headline."
  cta: { label: "Get started", href: "/contact" }
  secondaryCta: { label: "See docs", href: "/info" }  # optional
  microcopy: "Next.js 16 · MIT"   # optional tagline below CTA row

value-props

3-card grid of "why us" promises. 1-6 items.

- type: value-props
  eyebrow: "Why us"
  title: "Three promises. No asterisks."
  description: "Optional intro paragraph."
  items:
    - { title: "Yours, forever", body: "..." }
    - { title: "AI-native", body: "..." }
    - { title: "Production-shaped", body: "..." }

feature-grid

Hairline-bordered grid of capabilities. 1-30 items.

- type: feature-grid
  eyebrow: "What's in the box"
  title: "A real product, not a starter kit."
  description: "Every cell is shipping code."
  items:
    - { title: "Admin panel", body: "..." }
    # ... up to 30

how-it-works

Numbered 3-step process with optional code snippets. 1-6 steps.

- type: how-it-works
  eyebrow: "From zero to selling"
  title: "Three steps. Five minutes."
  items:
    - { n: "01", title: "Scaffold", body: "...", code: "npx create-cartwright@latest my-shop" }
    - { n: "02", title: "Setup wizard", body: "...", code: "pnpm dev → /admin/setup" }
    - { n: "03", title: "Deploy", body: "...", code: "vercel --prod" }

stack-grid

Flat list of tech / tools rendered in monospace cells. 1-60 items.

- type: stack-grid
  eyebrow: "The stack"
  title: "All current versions."
  items:
    - "Next.js 16"
    - "React 19"
    - "Tailwind v4"
    # ...

Final-page conversion section. Headline + 1-2 CTAs.

- type: cta-footer
  title: "Ship something real this week."
  description: "Optional lead."
  cta: { label: "Get started", href: "/contact" }
  secondaryCta: { label: "Read the docs", href: "/info" }  # optional

opaque

Escape hatch for designs that can't be composed from generic sections (e.g., webshop-classic HeroVideo + ProductGrid integration, webshop-bold custom brutalism layout). Points at a React component that already exists in designs/<slug>/.

- type: opaque
  component: WebshopBoldHomepage     # must export from designs/<slug>/
  props: {}                          # optional JSON-serializable props

When you use opaque for the whole homepage, the homepage.tsx codegen is bypassed and the imported component is rendered as-is. This is how all three new webshop variants are structured — their layouts break too far from the generic section atoms to compose declaratively.

Validation

All imports go through lib/designs/parser.ts:parseDesignMd() which uses Zod. Failures produce structured errors:

design.md schema validation fejlede:
  - tokens.prefix: tokens.prefix skal være kebab-safe lower-case (fx 'cw', 'sol').
  - sections.0.cta.href: Invalid input: expected string, received undefined

Se lib/designs/spec.ts for full schema, eller eksisterende
designs/<slug>/design.md som reference.

The full schema is at lib/designs/spec.ts in your repo. Cross-reference there when extending the format.

Round-trip guarantee

serializeDesignMd(spec, body) produces output that parses back to the same spec via parseDesignMd. This means:

# Export a built-in design
tsx scripts/design-export.ts studio > /tmp/studio.md

# Re-import it into a different location
tsx scripts/design-import.ts /tmp/studio.md --slug my-studio-copy --force

...gives you a working duplicate at designs/my-studio-copy/. Useful for forking a Cartwright-shipped design as a starting point.

On this page