Design System — @dmwd-io/design-system
| Field | Value |
|---|---|
| Type | Agent Reference |
| Source | ~/.copilot/agents/_refs/expert-react-frontend-engineer/design-system.md |
| Description | Not specified |
Source Content
Design System — @dmwd-io/design-system
This file is the field guide for working with the dmwd-io design system. All component work must pass this knowledge before touching code.
Three-Layer Architecture (ADR-010)
Dependencies flow downward only. Never import from a higher layer.
| Layer | Location | Style | Strictness |
|---|---|---|---|
| 1. Foundations | src/foundations/, src/styles/, tailwind.config.ts | Token-first | Strict — ADR amendment required to change |
| 2. UI Components | src/components/ui/ (incl. cards/, selection/, dropdowns/) | Atomic + composed, prop-driven | Reusable |
| 3. Application Patterns | src/components/patterns/ (incl. cards/) | Composed workflows | Opinionated for a specific UX workflow |
| Cross-cutting | src/components/charts/, src/components/shared/ | Chart family + shared types | Family-scoped |
Layer 1 — Foundations
The single source of truth for color, typography, spacing, motion, icons, accessibility, and shape.
- No hardcoded hex, px, or font-family in component files — use CSS custom properties or Tailwind token classes.
- Every foundation has a Storybook gallery story.
- Motion tokens include
prefers-reduced-motionfallbacks. - Changes require an ADR amendment because they cascade everywhere.
Layer 2 — UI Components
Dumb and prop-driven. No fetching, no domain state, no business rules.
| Folder | Family | Key examples |
|---|---|---|
ui/ root | General primitives | Button, Badge, Banner, Callout, Chip, Dialog, Drawer, Popover, Tooltip, Accordion, Tabs, Pagination, Skeleton, Spinner, Toast, Avatar, Heading, RichText, Divider, DataGrid, Table, CommandPalette, SlideOutPanel, SlideOutWizard, PanelWizard, SideNav, TopBar |
ui/cards/ | Card primitives | ContentCard, IconCard, ListCard, MediaCard, NotificationCard, PricingCard, ProfileCard, StatCard, TestimonialCard |
ui/selection/ | Selection family | Toggle, RadioGroup, RadioBoolean, SegmentedControl, Select, ChoiceCard |
ui/dropdowns/ | Dropdown family | ActionDropdown, ComboboxDropdown, MultiSelect, SearchableSelect, TreeSelect |
Rules:
- Variant API via CVA. Class merging via
cn(). - Every prop has a Storybook control with realistic dummy data.
- Keyboard support, visible focus ring, ARIA attributes, no color-only meaning.
- A component family in a subfolder shares hooks, styles, and variants through
index.ts.
Layer 3 — Application Patterns
Composed workflows that encode a specific UX workflow. Compose Layer 2 — never re-implement primitives.
- May hold local UI state (open/closed, sort direction, current step) but never server or domain state.
- May accept slot-based or render-prop children, but defaults must work out of the box.
- Storybook sidebar: only use “Application Patterns” as the top-level label, not “patterns” as a subfolder label anywhere else.
Where new components go
| What you are building | Location |
|---|---|
| New token, semantic class, governance rule | Layer 1 — requires ADR |
| Reusable, prop-driven control or composed UI block | Layer 2 src/components/ui/ |
| New card primitive | src/components/ui/cards/ |
| New selection control | src/components/ui/selection/ |
| New dropdown variant | src/components/ui/dropdowns/ |
| Workflow that composes existing UI for a specific use case | Layer 3 src/components/patterns/ |
| Chart variant or utility | src/components/charts/ |
| Cross-component shared type | src/components/shared/types.ts |
Component Naming Conventions (ADR-010)
Use the standard interaction pattern name, not domain-specific names:
| Wrong | Right | Reason |
|---|---|---|
| Pill | Chip | Conflicts with the squared-off shape language |
| ToggleRow | SegmentedControl | Precise interaction pattern name |
| RuleGroup | RadioGroup | Standard pattern name |
| FooterNav | StepNavigation | Describes the wizard nav pattern |
| QuestionHeader | SectionHeader | Reusable beyond question contexts |
| InputField | TextField | Aligns with ARIA and Material conventions |
Interaction Patterns Reference (ADR-010)
| Pattern | Component | When to use |
|---|---|---|
| Single selection (compact) | RadioGroup | Simple text options, ≤6 items |
| Single selection (rich) | ChoiceCard | Options with title + description + tags |
| Multi-toggle (≤5 options) | SegmentedControl | Binary or small enum choices |
| Multi-toggle (>5 options) | Select | Too many options for segments |
| Status indicator | Badge | Inline status: done, active, pending, warning |
| Selectable tags | Chip | Filterable/removable tags, categories |
| Text navigation | Link | Inline references and secondary navigation |
| Primary action | Button | Save, continue, export, add, confirm |
Design Token System (ADR-001)
Color palette
| Token | Light | Dark | Usage |
|---|---|---|---|
--primary | Teal 168° 79% 25% | Teal 168° 65% 50% | Primary actions, success states |
--brand-navy | Navy 216° 44% 20% | Navy 214° 46% 78% | Display headings, dark card backgrounds |
--brand-amber | Amber 38° 92% 58% | Amber 41° 92% 64% | Warnings, attention states |
--brand-mist | Off-white 44° 35% 96% | Dark 217° 22% 18% | Page backgrounds, subtle surfaces |
--destructive | Red 5° 74% 48% | Red 8° 80% 63% | Errors, danger callouts |
--info | Blue 209° 66% 37% | Blue 209° 60% 60% | Informational callouts |
--surface-accent | Soft accent-tinted | Soft accent-tinted | Accent-tinted surfaces, chips |
--surface-strong | Theme-specific dark | Theme-specific dark | Top bars, strong cards, high-emphasis panels |
--surface-strong-foreground | White | White | Text on --surface-strong |
Semantic tones for component APIs (ADR-006)
Public component variant props use semantic tones. Never palette names in shared component APIs:
// GOOD<Badge tone="success" /><Callout tone="warning" /><Banner tone="info" />
// BAD — palette name leaks into API<Badge tone="teal" /><Callout tone="amber" />Allowed tones: info, success, warning, danger, accent
Typography (ADR-001, ADR-003)
- Headings: Fraunces Variable (serif), weight 500–700
- Body: Source Sans 3 Variable (sans-serif), weight 400–600
Governed semantic type classes (use these, not raw text-*):
| Class | Use |
|---|---|
ui-type-label | Section labels, row labels, compact metadata |
ui-type-body-xs | Meta copy, timestamps, compact helper text |
ui-type-body-sm | Standard product body inside cards, rows |
ui-type-body | Longer product prose |
ui-type-title-sm | Card titles, picker titles |
ui-type-title-md | Section headers, panel headers, dialog titles |
ui-type-title-lg | Higher-emphasis product headings |
ui-type-title-xl | Rare large headings |
ui-type-display | Hero-level, explicit display only |
ui-type-value-md | Numeric emphasis in product cards |
ui-type-value-lg | Pricing or KPI emphasis |
Density / spacing (ADR-003)
Governed density classes (use these, not raw p-5/6):
| Class | Use |
|---|---|
ui-pad-row | Choice rows, list items, interactive rows |
ui-pad-card | Default product cards and compact panels |
ui-pad-card-roomy | Larger cards that stay within product density |
ui-pad-panel | Drawers, dialogs, grouped content panels |
ui-pad-shell | Screen shells and larger page-local wrappers |
Shape language (ADR-002)
| Token class | Value | Usage |
|---|---|---|
rounded-xl | 12px | Cards, panels, modals, floating surfaces |
rounded-lg | 10px | Inputs, buttons, menu items, embedded controls |
rounded-md | 8px | Badges, chips, segmented options, compact tags |
rounded-sm | 6px | Tiny inline tokens, preview swatches, helper labels |
rounded-full | 50% | Circles ONLY: step markers, radio dots, icon discs, handles |
No pill shapes. No arbitrary radius values.
Theme Governance (ADR-006)
Components reference semantic surface tokens, not hardcoded brand colors:
--surface-accentfor accent-tinted surfaces--surface-strongfor high-emphasis themed surfaces--surface-strong-foregroundfor text on strong surfaces
Adding a new theme: write an ADR → add [data-accent="<name>"] + night variant in src/index.css → populate all palette and semantic surface tokens → validate in Storybook across day/night.
Storybook Authoring Rules
Setup
import type { Meta, StoryObj } from '@storybook/react-vite'import { createComponentDocs } from '@/lib/storybook-docs'import { MyComponent } from './my-component'
const meta = { component: MyComponent, tags: ['autodocs'], parameters: { docs: { description: { component: createComponentDocs({ summary: 'One-line summary.', when: 'When to use this component.', whenNot: 'When NOT to use this component.', }), }, }, },} satisfies Meta<typeof MyComponent>
export default metatype Story = StoryObj<typeof meta>
export const Default: Story = { args: { // realistic data },}Border truthfulness (ADR-008)
- Do NOT wrap stories in
rounded-xl border border-border bg-cardchrome unless the border belongs to the component itself. - If a story shows a component inside a bordered host, name it explicitly:
Inside Card,In Table Cell. - Use spacing, max-width, and tonal contrast instead of fake borders for preview separation.
Compound components in Storybook (ADR-011)
Register all sub-components in meta.subcomponents. Use the compound API directly in render functions — no function Demo() wrapper:
const meta = { component: FilterPanel, subcomponents: { 'FilterPanel.Search': FilterPanel.Search, 'FilterPanel.DateRange': FilterPanel.DateRange, 'FilterPanel.Status': FilterPanel.Status, }, tags: ['autodocs'],} satisfies Meta<typeof FilterPanel>
export const Default: Story = { render: (args) => ( <FilterPanel {...args}> <FilterPanel.Search placeholder="Search..." /> <FilterPanel.DateRange label="Date filed" /> <FilterPanel.Status options={mockOptions} /> </FilterPanel> ),}After changing stories
Run pnpm run catalog after any story title change, addition, or removal. Update .storybook/preview.tsx ordering if the sidebar order changes.
Validation pipeline (run for any component change)
pnpm lintpnpm typecheckpnpm testpnpm run readiness:checkpnpm build
# For Storybook changes:pnpm run catalogpnpm build-storybookpnpm test:visualNever claim a check passed unless it was run in the current session.
Don’t Reinvent the Wheel (ADR-014)
For behavior-heavy components, wrap a mature library before building custom. The repo already relies on:
| Domain | Library |
|---|---|
| Tabular state | TanStack Table |
| Charts | Recharts |
| Code editing | Monaco |
| Low-level overlay behavior | Radix Popover |
When adding a new behavior-heavy component (dropdown, calendar, command palette, drag-and-drop, upload zone, terminal, virtualized list), check the repo-level don't-reinvent-wheel.md shortlist first.
Wrapper rules: keep tokens/spacing/motion/copy in the design-system layer, constrain the public API to cases this system supports, do not expose every upstream option as a prop.
Compound Components (ADR-011)
Pattern selection
| Question | YES → | NO → |
|---|---|---|
| Do all items have the same semantic role? | Array args (Pattern A) | Compound components (Pattern B) |
| Are all item fields required or uniformly optional? | Pattern A | Pattern B |
| Would adding a new item type require an optional field or discriminator? | Pattern B | Either |
| Is the count of items fixed by structure? | Pattern B | Either |
Pattern A — array args (homogeneous)
// Every item has the same shape and same role<FactsBody facts={[{ text: '...' }, { text: '...' }]} />No discriminator fields (type, kind, variant). No optional fields only relevant to some items.
Pattern B — compound components (heterogeneous)
<ParentingPlanBody guidingStatement="..."> <ParentingPlanBody.Schedule period="School Year"> <ParentingPlanBody.WeekdayCustody> Mother has primary care Mon–Thu. </ParentingPlanBody.WeekdayCustody> <ParentingPlanBody.WeekendCustody> Father has alternating weekends. </ParentingPlanBody.WeekendCustody> </ParentingPlanBody.Schedule></ParentingPlanBody>Implementation: Object.assign, each sub-component one file, index.tsx is the only public export. Sub-components are never exported bare from the package.
ADR Quick Reference
| ADR | Topic | Key rule |
|---|---|---|
| ADR-001 | Design tokens | Semantic tokens, Fraunces/Source Sans, radius scale |
| ADR-010 | Component architecture | CVA+cn(), naming conventions, interaction patterns |
| ADR-002 | Shape language | No pills, restrained rectangles, rounded-full circles only |
| ADR-010 | Layered architecture | Three layers, downward imports only |
| ADR-010 | Component catalog | pnpm run catalog after story changes |
| ADR-006 | Theme governance | Semantic surface tokens, ADR-gate for new themes |
| ADR-014 | Don’t reinvent wheel | Wrap mature libraries for behavior-heavy components |
| ADR-010 | Component maturity | Stability labels in Storybook |
| ADR-011 | Variant governance | Multiple variants expected; each documents when to use |
| ADR-004 | Premium polish & motion | CSS tokens first, prefers-reduced-motion, one border per stack |
| ADR-005 | Testing & a11y gates | WCAG 2.2 AA, axe CI, keyboard tests, visual regression |
| ADR-003 | Typography & density | ui-type-* and ui-pad-* semantic classes |
| ADR-008 | Storybook border truthfulness | No fake border chrome in stories |
| ADR-009 | Icon conventions | leadingIcon, trailingIcon, triggerIcon, endAdornment |
| ADR-006 | Semantic color governance | Semantic tones in public APIs, not palette names |
| ADR-011 | Compound components | Pattern A vs B selection; Object.assign; no bare sub-exports |