Getting started
Customize the system
Replace the visual identity without rebuilding the documentation. Tokens, presets and product identity are configurable; the existing page shell, component anatomy, usage guidance, API tables and accessibility notes stay intact.
Try the theme contractPermalink to this section
These controls change the real site, not an isolated mockup. Your preference is saved locally and restored before the next page paints.
Live component contract
Colour, radius, sizing and elevation all come from the active tokens.
What changes, what staysPermalink to this section
Visual identity and documentation structure are deliberately separate concerns.
| Area | Source | Effect |
|---|---|---|
| Brand and status colour | src/tokens/primitives.ts | Recolours every semantic and component consumer |
| Light and dark roles | src/tokens/semantic.ts | Changes surfaces, text, borders and states per mode |
| Control shape and sizing | src/tokens/components.ts | Reshapes buttons, fields, dialogs, cards and menus |
| Typography scale | src/tokens/typography.ts | Updates every .type-* utility from one definition |
| Theme presets | src/tokens/presets.ts | Adds a selectable brand and shape combination |
| Name, package and repository | src/config/site.ts | Renames product identity across shared navigation and metadata |
| Docs information architecture | src/app/docs + src/components/docs | Protected: keep the existing page structure and content model |
1 · Replace the visual foundationPermalink to this section
Start at the primitive layer for a real rebrand. Semantic names and component contracts should usually survive unchanged.
export const primitiveGroups = [
{
id: "brand",
tokens: {
"brand-50": { value: "#f5f8ff", description: "Faint brand wash." },
"brand-600": { value: "#2457d6", description: "Primary brand action." },
"brand-700": { value: "#1946b8", description: "Primary hover." },
// Keep every step referenced by semantic.ts.
},
},
];2 · Reshape componentsPermalink to this section
The control contract keeps equivalent sizes aligned across Button, Icon Button, Input, Textarea and Select. Component-specific radius and padding tokens allow intentional exceptions.
"control-radius": {
value: "radius-full",
category: "radius",
description: "Shared button and field corner.",
},
"control-height-md": {
value: "44px",
description: "Default control height.",
},3 · Add your own presetPermalink to this section
A preset is a named set of token overrides. It can change only colour, only shape, or both, and is automatically exposed by both theme controls.
{
id: "my-brand",
label: "My Brand",
description: "The product identity used by our application.",
overrides: {
"brand-600": "#2457d6",
"brand-700": "#1946b8",
"control-radius": "radius-sm",
"card-radius": "radius-md",
},
}A full-foundation preset, worked end to end
The resendpreset (“Resend template”) is a complete reference: a dark-native translation that overrides the neutral and brand ramps, surface and border roles, radius, weight, motion and the signature hairline-ring elevation — while retargeting only the primary button to the inverted surface so its call to action stays white-on-black without touching the accent ramp. It authors both light and dark from one set of ramps, exactly as the rules below require. Read it in src/tokens/presets.ts before writing your own.
4 · Apply a preset to one subtreePermalink to this section
A preset does not have to own the whole page. Any element carrying data-theme and data-preset becomes a scope, and every component below it resolves that preset instead of the page's.
Page presetDeploy preview
Colour, typeface, radius, density, elevation and timing all resolve from whichever preset this subtree sits in.
data-preset="replit"Deploy preview
Colour, typeface, radius, density, elevation and timing all resolve from whichever preset this subtree sits in.
// Both attributes are required: the generated rules key on the pair,
// so a scope that names only data-preset will not match. Pass the page's
// resolved mode so the subtree still follows light and dark.
<div data-theme={resolvedTheme} data-preset="replit">
<Card>
<Button>Deploy</Button>
</Card>
</div>No second runtime
Scoping is entirely CSS custom-property inheritance. There is no nested provider, no context and no per-preset branch inside any component — a component reads var(--button-radius) and gets whichever value its ancestor scope resolved.
5 · Change product identity and layoutPermalink to this section
Text identity is centralized. Layout dimensions are semantic tokens, so changing the shell does not require editing the docs page templates.
export const siteConfig = {
name: "Your Product",
fullName: "Your Product Design System",
wordmarkSuffix: "Design System",
package: "@your-org/ui",
repository: { label: "your-org/ui", url: "https://github.com/…" },
};Keep the documentation contract
Do not replace the docs layout, the four component-page tabs, anatomy, usage guidance, API tables or accessibility content during a visual customization. The detailed AI checklist lives in CUSTOMIZATION.md.
VerificationPermalink to this section
A customization is complete only when the generated contract, documentation and both colour modes agree.
- 1Run npm run lint and npm run build. Registry validation fails loudly on missing aliases, duplicate names and cycles.
- 2Review the homepage plus Button, Input, Dialog and token explorer pages in light and dark mode.
- 3Switch every preset and check focus, hover, invalid and disabled states — not only the resting component.
- 4Confirm the existing docs routes and page sections are unchanged unless the content change was explicitly requested.