DOMAIN:DESIGN:COMPONENT_LIBRARY¶
OWNER: alexander (Design System Engineer, shared) ALSO_USED_BY: floris, floor, felice UPDATED: 2026-03-26 SCOPE: component hierarchy, documentation requirements, variant system, composition, testing, versioning PREREQUISITE: design-system-engineering.md (shadcn/ui as base, three-layer design model)
COMPONENT_LIBRARY:OVERVIEW¶
FOUNDATION: shadcn/ui — unstyled, accessible components as starting point CUSTOMIZATION: design tokens applied via CSS custom properties PRINCIPLE: copy-and-own, not npm dependency — components live in the project
WHY_SHADCN: - components are copied into your project, not installed as opaque dependency - full control over markup, styling, and behavior - accessible by default (radix-ui primitives under the hood) - works with Tailwind CSS - agent-friendly — component code is readable and modifiable
COMPONENT_LIBRARY:ATOMIC_DESIGN_HIERARCHY¶
ATOMS — smallest building blocks¶
DEFINITION: single-purpose elements that cannot be broken down further EXAMPLES: Button, Input, Label, Badge, Avatar, Separator, Skeleton
CHARACTERISTICS: - no internal composition (no child components) - styled entirely by design tokens - reusable across all contexts - one responsibility per atom
MOLECULES — composed of atoms¶
DEFINITION: small groups of atoms that function together as a unit EXAMPLES: FormField (Label + Input + ErrorMessage), SearchBar (Input + Button), UserChip (Avatar + Name)
CHARACTERISTICS: - combine 2-4 atoms into a functional unit - have their own props/API that abstract internal atom configuration - reusable across multiple organisms
ORGANISMS — composed of molecules and atoms¶
DEFINITION: complex UI sections that form distinct areas of the interface EXAMPLES: NavigationBar, ProjectCard, DataTable, FormSection, DashboardWidget
CHARACTERISTICS: - combine molecules and atoms into meaningful sections - often map to specific business concepts - may include local state management - less reusable than molecules — more context-specific
TEMPLATES — page-level layout¶
DEFINITION: page layouts that arrange organisms into a complete screen structure EXAMPLES: DashboardLayout, SettingsLayout, AuthLayout, OnboardingLayout
CHARACTERISTICS: - define where organisms go on the page - handle responsive layout behavior - no business logic — purely structural
PAGES — templates with real data¶
DEFINITION: template instances with actual content and data EXAMPLES: ProjectDashboardPage, UserSettingsPage, LoginPage
CHARACTERISTICS: - fetch and pass real data to templates - handle page-level state and data loading - one per route
COMPONENT_LIBRARY:DOCUMENTATION_REQUIREMENTS¶
PER_COMPONENT_DOCUMENTATION (in DESIGN.md)¶
REQUIRED: component name and hierarchy level (atom, molecule, organism) REQUIRED: purpose — one sentence describing what it does REQUIRED: shadcn/ui base component (if applicable) REQUIRED: props/API — every configurable property REQUIRED: variants — all visual variants with usage guidelines REQUIRED: design tokens used — list of tokens this component consumes REQUIRED: states — default, hover, focus, active, disabled, loading, error, empty REQUIRED: accessibility — ARIA attributes, keyboard interaction, screen reader behavior REQUIRED: composition — what atoms/molecules it contains (for molecules/organisms) REQUIRED: responsive behavior — what changes at each breakpoint
OPTIONAL: animation specification OPTIONAL: related components OPTIONAL: do/don't usage examples
EXAMPLE_DOCUMENTATION¶
## COMPONENT:BUTTON
LEVEL: atom
PURPOSE: trigger an action or navigate to a URL
BASE: shadcn/ui Button
VARIANTS:
default: primary action (filled, brand color)
secondary: secondary action (outlined)
destructive: dangerous action (red)
ghost: minimal visual weight
link: looks like a link, behaves like a button
SIZES:
sm: height 32px, font --font-size-sm, padding-x --spacing-inline-sm
default: height 40px, font --font-size-base, padding-x --spacing-inline-md
lg: height 48px, font --font-size-lg, padding-x --spacing-inline-lg
icon: square 40x40, icon only, requires aria-label
PROPS:
variant: "default" | "secondary" | "destructive" | "ghost" | "link"
size: "sm" | "default" | "lg" | "icon"
disabled: boolean
loading: boolean (shows spinner, disables interaction)
asChild: boolean (render as child element, e.g., <a>)
STATES:
default: --button-{variant}-bg, --button-{variant}-text
hover: --button-{variant}-bg-hover
focus: focus ring --color-border-focus, 2px offset
active: --button-{variant}-bg-active
disabled: --color-interactive-disabled, cursor not-allowed, opacity 0.5
loading: spinner replaces icon/text, aria-busy="true"
ACCESSIBILITY:
KEYBOARD: Enter and Space activate
ARIA: native <button> element, no additional ARIA needed
ARIA_ICON: icon-only buttons require aria-label
LOADING: aria-busy="true" during loading state
FOCUS: visible focus ring meeting 3:1 contrast
TARGET: minimum 24x24 CSS pixels (2.5.8), GE targets 44x44 for touch
DO:
- use descriptive labels ("Save project", not "Click here")
- use loading state for async actions
- use destructive variant for irreversible actions
DON'T:
- don't use more than one primary button per section
- don't use ghost variant for critical actions
- don't disable without explaining why (use tooltip or helper text)
COMPONENT_LIBRARY:VARIANT_SYSTEM¶
WHAT_ARE_VARIANTS¶
DEFINITION: visual or behavioral alternatives of the same component PURPOSE: reduce component count while supporting different contexts
VARIANT_TYPES¶
VISUAL_VARIANT: changes appearance (primary, secondary, destructive) SIZE_VARIANT: changes dimensions (sm, default, lg) LAYOUT_VARIANT: changes arrangement (horizontal, vertical, stacked) STATE_VARIANT: changes interactive state (default, loading, error)
IMPLEMENTATION_PATTERN¶
APPROACH: class-variance-authority (cva) — used by shadcn/ui
import { cva, type VariantProps } from 'class-variance-authority';
const buttonVariants = cva(
'inline-flex items-center justify-center rounded-md font-medium transition-colors',
{
variants: {
variant: {
default: 'bg-[var(--button-primary-bg)] text-[var(--button-primary-text)]',
secondary: 'border border-[var(--color-border-default)] bg-transparent',
destructive: 'bg-[var(--color-error)] text-[var(--color-text-inverse)]',
ghost: 'hover:bg-[var(--color-surface-secondary)]',
},
size: {
sm: 'h-8 px-3 text-sm',
default: 'h-10 px-4',
lg: 'h-12 px-6 text-lg',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
);
RULE: every variant must be documented in DESIGN.md with usage guidelines RULE: variants must be visually distinct — if two variants look the same, consolidate RULE: limit variants to what is actually needed — fewer variants = simpler system
COMPONENT_LIBRARY:COMPOSITION_PATTERNS¶
SLOT_PATTERN¶
WHAT: components expose named slots for content injection WHY: flexible composition without creating variant explosion
<Card>
<CardHeader>
<CardTitle>Project name</CardTitle>
<CardDescription>Last updated 2 hours ago</CardDescription>
</CardHeader>
<CardContent>
{/* flexible content area */}
</CardContent>
<CardFooter>
<Button variant="secondary">Cancel</Button>
<Button>Save</Button>
</CardFooter>
</Card>
RENDER_PROP_PATTERN¶
WHAT: parent passes rendering function to child for custom rendering WHEN: component needs to render different content based on state
<Combobox
options={countries}
renderOption={(option) => (
<div className="flex items-center gap-2">
<Flag code={option.code} />
<span>{option.name}</span>
</div>
)}
/>
COMPOUND_COMPONENT_PATTERN¶
WHAT: related components share implicit state through React context WHEN: components form a logical group (Tabs + TabList + Tab + TabPanel)
<Tabs defaultValue="general">
<TabsList>
<TabsTrigger value="general">General</TabsTrigger>
<TabsTrigger value="members">Members</TabsTrigger>
</TabsList>
<TabsContent value="general">General settings...</TabsContent>
<TabsContent value="members">Team members...</TabsContent>
</Tabs>
RULE: prefer slot pattern for simple composition RULE: use compound pattern for components with shared state RULE: document composition examples in DESIGN.md
COMPONENT_LIBRARY:VISUAL_TESTING¶
APPROACH¶
TOOL: Playwright visual regression tests (preferred over Storybook for GE's agentic workflow) REASON: agents can run Playwright tests programmatically — Storybook requires manual visual inspection ALTERNATIVE: if client project uses Storybook, integrate with Chromatic for visual testing
PLAYWRIGHT_VISUAL_TESTS¶
import { test, expect } from '@playwright/test';
test('Button variants render correctly', async ({ page }) => {
await page.goto('/design-system/button');
// Snapshot each variant
await expect(page.locator('[data-variant="default"]'))
.toHaveScreenshot('button-default.png');
await expect(page.locator('[data-variant="secondary"]'))
.toHaveScreenshot('button-secondary.png');
await expect(page.locator('[data-variant="destructive"]'))
.toHaveScreenshot('button-destructive.png');
});
WHAT_TO_VISUALLY_TEST¶
TEST: every component in every variant TEST: every component in every size TEST: every component in every state (hover, focus, disabled, loading, error) TEST: dark mode rendering of every component TEST: responsive rendering at key breakpoints TEST: client brand override rendering
VISUAL_REGRESSION_THRESHOLD¶
THRESHOLD: 0.1% pixel difference tolerance PROCESS: if visual regression detected, reviewer decides if intentional or bug PROCESS: intentional change → update baseline screenshot PROCESS: unintentional change → fix the regression
COMPONENT_LIBRARY:VERSIONING¶
APPROACH¶
METHOD: components live in the project repo — version with the project METHOD: design system changes are tracked via DESIGN.md changelog METHOD: breaking changes (removed props, changed behavior) require migration note
BREAKING_CHANGE_PROTOCOL¶
STEP: document the breaking change in DESIGN.md changelog STEP: provide migration instructions (before/after code example) STEP: update all usages in the project before merging STEP: notify floris/floor if change affects multiple screens
DESIGN_MD_CHANGELOG¶
## CHANGELOG
### 2026-03-26
- ADDED: Button "loading" prop with spinner
- CHANGED: Card border radius from --radius-md to --radius-lg (BREAKING)
MIGRATION: no code change needed — token value updated
- REMOVED: Badge "outline" variant — use "secondary" instead (BREAKING)
MIGRATION: replace variant="outline" with variant="secondary"
COMPONENT_LIBRARY:AGENT_INSTRUCTIONS¶
FOR alexander: - document every component following the per-component template above - define variants with clear usage guidelines — when to use which - keep component count minimal — prefer variants over new components - review all new component implementations for design fidelity
FOR floris, floor: - implement components from DESIGN.md specification - use shadcn/ui as starting point — extend, do not rebuild from scratch - follow composition patterns (slot, compound, render prop) - write Playwright visual tests for new components - never create a component without documentation in DESIGN.md
FOR felice: - visually test every component variant and state - compare implementation against Figma using Dev Mode - flag undocumented components (exists in code but not in DESIGN.md)
READ_ALSO: domains/design/design-system-engineering.md, domains/design/design-tokens.md, domains/design/figma-handoff.md, domains/design/responsive-design.md