Subchapter 2.8
design-system-page.mdMarkdown15 KBView on GitHub
What goes on a design system page, and how to show it. Distilled from Primer, Carbon, Atlassian, GitLab Pajamas, Cloudscape, Twilio Paste, Salesforce Lightning, Material 3, Evergreen and Orbit, and from EightShapes’ component documentation guidance.
| # | Section | Contents |
|---|---|---|
| 1 | Header | System name, “Themed from DESIGN.md”, section anchors, light/dark toggle, primary CTA. |
| 2 | Overview | The DESIGN.md hero recipe: display type, one-line personality, its signature artifact. |
| 3 | Foundations | Color roles, palette, typography, spacing, radius, elevation, motion (if specified), icons. |
| 4 | Actions | Button, ButtonGroup, Toggle, ToggleGroup, Badge, Kbd. |
| 5 | Inputs | Field, Input, InputGroup, Textarea, Select, NativeSelect, Combobox, Checkbox, RadioGroup, Switch, Slider, InputOTP, Calendar, Label. |
| 6 | Navigation | Tabs, Breadcrumb, Pagination, NavigationMenu, Menubar, Sidebar. |
| 7 | Data display | Card, Table, Chart, Item, Avatar, Accordion, Collapsible, Carousel, ScrollArea, Resizable, AspectRatio, Separator. |
| 8 | Feedback | Alert, toast (and Sonner in Base UI projects), Progress, Spinner, Skeleton, Empty. |
| 9 | Overlays | Dialog, AlertDialog, Sheet, Drawer, Popover, HoverCard, Tooltip, DropdownMenu, ContextMenu, Command. |
| 10 | Conversation | MessageScroller, Message, Bubble, Attachment, Marker, Questionnaire. See rules/chat.md. |
| 11 | Do and Don’t | Rendered pairs for the DESIGN.md’s rules. |
| 12 | Recipes and example screen | The DESIGN.md’s named components, then one realistic screen. |
| 13 | Footer | The DESIGN.md footer recipe. |
DirectionProvider goes wherever RTL matters (an RTL row in Inputs or Navigation).
Alternate section surfaces the way the DESIGN.md paces its bands. Use className="dark" on a section to render a dark band with the same components.
A grid of pairs, each rendered as the surface with “Aa” in its foreground color, plus the token names, resolved value and measured contrast (Paste, Material 3, Pajamas):
| Pair | Required |
|---|---|
background / foreground | Yes |
card / card-foreground | Yes |
popover / popover-foreground | Yes |
primary / primary-foreground | Yes |
secondary / secondary-foreground | Yes |
muted / muted-foreground | Yes |
accent / accent-foreground | Yes |
destructive on background | Yes |
Each extra text token on background | When defined |
Use ColorPair from the helpers. It measures contrast live and re-measures when the theme changes. Report any pair below 4.5:1 for body-size text, even when the DESIGN.md specifies it. A brand primary at 3:1 behind 14px labels is a real finding.
Swatches grouped the way the DESIGN.md groups them (brand, surfaces, text, status, accents). Label each with its DESIGN.md name and token, for example “Warm Stone · surface-warm“. Show border, input, ring, chart-1…5 and sidebar-* here too.
A specimen table, one row per type token (Cloudscape, Carbon, Atlassian):
| Column | Content |
|---|---|
| Token | type-display-lg |
| Spec | size / line height / weight / tracking |
| Use | “Section heads”, taken from the DESIGN.md |
| Specimen | Real copy set in the token, truncated to one line |
Group the rows display → title → body → label/caption → code. Below the table, show one paragraph of running text at body size, with a link and inline code, so line length and rhythm are visible.
The base unit and the scale as labeled horizontal bars (Carbon, Atlassian), then a semantic ladder: inside a component, between related items, between groups, between sections (Pajamas). Skip it if the DESIGN.md defines no spacing.
The same box at every step, labeled with the token, the value and what uses it (Material 3). Example: md · 8px · buttons, inputs.
Each level as a raised surface with the components that rest there (Pajamas, Material 3): flat for canvas and bands, hairline for cards, soft for popovers and menus, deep for dialogs.
Show motion only if the DESIGN.md defines durations or easings: a table of token, milliseconds and use. For icons, show one row with the icon library at each size used by components, in foreground and muted-foreground.
| Tier | Components | How to show |
|---|---|---|
| Primitives | Button, Badge, Toggle, ToggleGroup, Input, Textarea, Select, NativeSelect, Checkbox, RadioGroup, Switch, Slider, Tabs | Variant × state matrix, size ladder, icon row. |
| Fields | Field, InputGroup, Combobox, InputOTP, Calendar, Label | Typical field, then description, error, disabled, required. |
| Composed | Card, Table, Item, Avatar, Alert, Empty, toast, Sonner, Accordion, Collapsible, Breadcrumb, Pagination, Progress, Skeleton, Spinner, Carousel, Chart, ScrollArea, Resizable, AspectRatio, Separator, Kbd, ButtonGroup | Typical example, then one element added at a time, then edge cases. |
| Overlays | Dialog, AlertDialog, Sheet, Drawer, Popover, HoverCard, Tooltip, DropdownMenu, ContextMenu, Menubar, NavigationMenu, Command | A real trigger per overlay, labeled with what it opens. |
| Shell | Sidebar, DirectionProvider | Inline in a framed preview (collapsible="none"). |
| Conversation | MessageScroller, Message, Bubble, Attachment, Marker, Questionnaire | One realistic thread, then variant rows. |
Each component block, in this order. Skip the parts that don’t apply:
Wide components (Table, NavigationMenu, Menubar, Sidebar, Chart, MessageScroller) span the full row.
Pin interaction states with a data-preview attribute. Override the hover, focus-visible and active variants so each also matches the attribute. Copy assets/showcase/preview-states.css into tailwindCssFile:
@custom-variant hover {
@media (hover: hover) {
&:hover {
@slot;
}
}
&[data-preview~="hover"] {
@slot;
}
}
@custom-variant focus-visible (&:focus-visible, &[data-preview~="focus"]);
@custom-variant active (&:active, &[data-preview~="active"]);If the theme adds an unlayered focus rule (for an outline-style focus), add [data-preview~="focus"] to its selector too.
Render the matrix with StateMatrix and previewState from the helpers:
const variants = ["default", "secondary", "outline", "ghost", "destructive", "link"] as const
<StateMatrix
rows={variants}
render={(variant, state) => (
<Button variant={variant} {...previewState(state)}>
Save draft
</Button>
)}
/>The columns are default, hover, focus, active and disabled. Add loading with Spinner + disabled for Button, and invalid (aria-invalid) for fields:
<StateMatrix
rows={["input"] as const}
states={["default", "hover", "focus", "disabled", "invalid"]}
render={(_, state) => (
<Input
placeholder="you@company.com"
aria-invalid={state === "invalid" || undefined}
{...previewState(state)}
/>
)}
/>If the DESIGN.md says a state doesn’t change (“primary darkens on press only”), leave that column identical. The matrix shows the spec is followed.
Show these next to the component they stress:
| Case | Where |
|---|---|
| Long text | Button label, Badge, Item title, Table cell, Breadcrumb (truncation). |
| Empty | Table with no rows → Empty, Combobox with no matches, empty Command. |
| Error | Field with FieldError, Alert destructive, Attachment state="error". |
| Loading | Button with Spinner, Skeleton for Card and Item, Progress, toast loading. |
| Many items | AvatarGroup with count, Pagination with ellipsis, ScrollArea list. |
| Missing image | Avatar falling back to AvatarFallback. |
| Disabled | Every field and its Field data-disabled. |
| RTL | One row inside DirectionProvider with dir="rtl". |
Pick the two to four DESIGN.md rules that are easiest to break and render each as a pair (Primer, Orbit, Carbon):
<div className="grid gap-4 md:grid-cols-2">
<Example title="Do">
<Button>Get started</Button>
<Button variant="outline">Contact sales</Button>
</Example>
<Example title="Don't">
<Button>Get started</Button>
<Button>Contact sales</Button>
</Example>
</div>Caption each with the rule in one line, for example “One primary action per group.” Mark Don’t with destructive text, not a new color.
Recipes. Build each named component in the DESIGN.md (hero-band, feature-card, pricing-tier-card, callout-card-coral, footer) from shadcn components and tokens. These are the spec’s acceptance tests. If a recipe can’t be built without raw values, the token mapping is incomplete.
Example screen. One realistic screen in the brand’s domain (Cloudscape demos, Paste page templates): a settings page (FieldGroup, Switch, Select, Button) or a list view (Table, Badge, Pagination, Empty). Use only tokens and components.
Copy from assets/showcase/ into the showcase folder:
| File | Purpose |
|---|---|
preview-states.css | Variant overrides that pin hover, focus and active through data-preview. |
state-matrix.tsx | StateMatrix (labeled variant × state table) and previewState(state). |
color-pair.tsx | ColorPair: surface + foreground with live contrast ratio and WCAG level. |
They import cn from @/lib/utils. Rewrite that path to the project’s utils alias.