Skip to content

Latest commit

 

History

History
202 lines (162 loc) · 7.22 KB

DESIGN_SYSTEM_TEMPLATE.md

File metadata and controls

202 lines (162 loc) · 7.22 KB

Design System

Generated by /1-setup-design. This file is the single source of truth for how the app looks. Agents: read it before writing any UI code, and never deviate from it without the user's sign-off.

Palette: {{THEME_NAME}}

Role Hex Description
Primary {{PRIMARY_COLOR}} {{PRIMARY_NAME}}
Success {{SUCCESS_COLOR}} {{SUCCESS_NAME}}
Error {{ERROR_COLOR}} {{ERROR_NAME}}
Warning {{WARNING_COLOR}} {{WARNING_NAME}}
Background {{BACKGROUND_COLOR}} {{BACKGROUND_NAME}}
Surface {{SURFACE_COLOR}} {{SURFACE_NAME}}
Primary Text {{TEXT_PRIMARY_COLOR}} {{TEXT_PRIMARY_NAME}}
Secondary Text {{TEXT_SECONDARY_COLOR}} {{TEXT_SECONDARY_NAME}}

CSS Variables (Tailwind CSS v4, OKLCH)

The full shadcn/ui-compatible palette below is derived from the table above. /2-setup-foundation writes it into src/app/globals.css and maps every variable to a Tailwind utility through @theme inline — there is no tailwind.config.js in Tailwind v4. In components you use the utility names (bg-primary, text-muted-foreground, border-border), never these raw values.

:root {
  --background: {{BACKGROUND_OKLCH}};
  --foreground: {{FOREGROUND_OKLCH}};
  --card: {{CARD_OKLCH}};
  --card-foreground: {{CARD_FOREGROUND_OKLCH}};
  --popover: {{POPOVER_OKLCH}};
  --popover-foreground: {{POPOVER_FOREGROUND_OKLCH}};
  --primary: {{PRIMARY_OKLCH}};
  --primary-foreground: {{PRIMARY_FOREGROUND_OKLCH}};
  --secondary: {{SECONDARY_OKLCH}};
  --secondary-foreground: {{SECONDARY_FOREGROUND_OKLCH}};
  --muted: {{MUTED_OKLCH}};
  --muted-foreground: {{MUTED_FOREGROUND_OKLCH}};
  --accent: {{ACCENT_OKLCH}};
  --accent-foreground: {{ACCENT_FOREGROUND_OKLCH}};
  --destructive: {{DESTRUCTIVE_OKLCH}};
  --destructive-foreground: {{DESTRUCTIVE_FOREGROUND_OKLCH}};
  --border: {{BORDER_OKLCH}};
  --input: {{INPUT_OKLCH}};
  --ring: {{RING_OKLCH}};
  --radius: {{RADIUS_REM}}rem;
  --success: {{SUCCESS_OKLCH}};
  --warning: {{WARNING_OKLCH}};
  --surface: {{SURFACE_OKLCH}};
  --text-primary: {{TEXT_PRIMARY_OKLCH}};
  --text-secondary: {{TEXT_SECONDARY_OKLCH}};
}

.dark {
  --background: {{BACKGROUND_DARK_OKLCH}};
  --foreground: {{FOREGROUND_DARK_OKLCH}};
  --card: {{CARD_DARK_OKLCH}};
  --card-foreground: {{CARD_FOREGROUND_DARK_OKLCH}};
  --popover: {{POPOVER_DARK_OKLCH}};
  --popover-foreground: {{POPOVER_FOREGROUND_DARK_OKLCH}};
  --primary: {{PRIMARY_DARK_OKLCH}};
  --primary-foreground: {{PRIMARY_FOREGROUND_DARK_OKLCH}};
  --secondary: {{SECONDARY_DARK_OKLCH}};
  --secondary-foreground: {{SECONDARY_FOREGROUND_DARK_OKLCH}};
  --muted: {{MUTED_DARK_OKLCH}};
  --muted-foreground: {{MUTED_FOREGROUND_DARK_OKLCH}};
  --accent: {{ACCENT_DARK_OKLCH}};
  --accent-foreground: {{ACCENT_FOREGROUND_DARK_OKLCH}};
  --destructive: {{DESTRUCTIVE_DARK_OKLCH}};
  --destructive-foreground: {{DESTRUCTIVE_FOREGROUND_DARK_OKLCH}};
  --border: {{BORDER_DARK_OKLCH}};
  --input: {{INPUT_DARK_OKLCH}};
  --ring: {{RING_DARK_OKLCH}};
  --success: {{SUCCESS_DARK_OKLCH}};
  --warning: {{WARNING_DARK_OKLCH}};
  --surface: {{SURFACE_DARK_OKLCH}};
  --text-primary: {{TEXT_PRIMARY_DARK_OKLCH}};
  --text-secondary: {{TEXT_SECONDARY_DARK_OKLCH}};
}

Dark mode is class-based (.dark on the root element), which is what next-themes expects. Wire up a ThemeProvider if the project needs a toggle; until then the app renders in light mode.

Typography: {{FONT_PAIRING_NAME}}

  • Headings: {{HEADING_FONT}} {{HEADING_WEIGHT}}
  • Body: {{BODY_FONT}} {{BODY_WEIGHT}}

Fonts load through next/font/google in src/app/layout.tsx — never via CSS @import. They surface as the font-heading and font-body utilities.

Type scale (use Tailwind's text utilities):

Level Size Utility
H1 48px / 3rem text-5xl
H2 32px / 2rem text-3xl
H3 24px / 1.5rem text-2xl
H4 20px / 1.25rem text-xl
Body 16px / 1rem text-base
Small 14px / 0.875rem text-sm
<h1 className="font-heading text-5xl font-semibold text-foreground">Page title</h1>
<p className="font-body text-base text-foreground">Body copy</p>
<p className="font-body text-sm text-muted-foreground">Supporting detail</p>

Spacing: {{SPACING_TYPE}}

Base unit {{BASE_UNIT}}px, six steps: {{SPACING_SCALE}}.

  • ds-1: {{SPACING_1}} — hairline gaps, icon padding
  • ds-2: {{SPACING_2}} — padding inside buttons and inputs
  • ds-3: {{SPACING_3}} — padding inside cards
  • ds-4: {{SPACING_4}} — gaps between sections
  • ds-5: {{SPACING_5}} — page margins
  • ds-6: {{SPACING_6}} — hero / major landmark spacing

These exist as spacing utilities: p-ds-3, gap-ds-2, mt-ds-4, and so on. Every margin, padding, and gap in the app comes from this scale — if a design seems to need a value that isn't on it, pick the nearest step.

Shape & Motion

Corner radius: {{RADIUS_SIZE}}

--radius-sm: {{RADIUS_SM}}px;
--radius-md: {{RADIUS_MD}}px;
--radius-lg: {{RADIUS_LG}}px;

Elevation: stick to shadow-sm for raised controls, shadow-md for cards, shadow-lg for popovers and modals. Nothing heavier.

Motion: transitions run 150–300ms with Tailwind's default easing. Animate opacity and transform only; never animate layout properties.

Hard Rules

Required

  • Every color comes from a token utility (bg-primary, text-foreground, border-border, bg-success…)
  • Every font is font-heading or font-body — two families, total
  • Every spacing value is a ds-* step or a Tailwind default that matches the scale
  • Text contrast meets WCAG AA: 4.5:1 for copy, 3:1 for large text and UI chrome

Forbidden

  • Hex, rgb, or named colors in class names or styles (bg-[#1a73e8], text-red-500)
  • Arbitrary pixel values (p-[13px], gap-[27px])
  • Font families outside the pair above (font-[Georgia])
  • Low-contrast pairings — pale gray text on white is the classic failure; check before you ship

Interface State Patterns

Every data-driven view handles four states. Reference implementations:

Loading — skeleton, not spinner

<div className="space-y-ds-2 animate-pulse">
  <div className="bg-muted h-4 w-2/3 rounded-md" />
  <div className="bg-muted h-4 w-1/2 rounded-md" />
</div>

Empty — say what belongs here and offer the first step

<div className="py-ds-6 text-center">
  <p className="mb-ds-2 text-muted-foreground">No quotes yet.</p>
  <Button variant="outline">Create your first quote</Button>
</div>

Error — actionable, never a dead end

<Alert variant="destructive">
  <AlertDescription>
    Couldn&apos;t load your data.{" "}
    <Button variant="link" onClick={retry}>
      Retry
    </Button>
  </AlertDescription>
</Alert>

Success — confirm via toast (sonner), then get out of the way

toast.success("Quote sent to the customer");

Agents: the tokens above are the entire visual vocabulary of this app. If a screen can't be built from them, raise it with the user instead of improvising.