Skip to content

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