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.
| 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}} |
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.
- 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>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.
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.
Required
- Every color comes from a token utility (
bg-primary,text-foreground,border-border,bg-success…) - Every font is
font-headingorfont-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
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'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.