Frontend Design System
Purpose
Build a three-tier design token system with CVA-based component variants and theme-aware architecture. Every visual value is a token. No hardcoded colors, spacing, or typography. Components support dark mode, responsive variants, and accessibility out of the box.
Agent Protocol
Trigger
Exact user phrases: "design system", "design tokens", "component library", "theme", "colors", "typography system", "spacing system", "component API design", "variant system", "create a design system".
Input Context
Before activating, verify:
- The framework is known (React, Vue, Angular) or ask.
- The styling approach is known (CSS modules, Tailwind, styled-components, etc.).
Output Artifact
No file output. Produces token definitions and component APIs as text.
Response Format
Token definitions:
Primitive: --color-{scale}-{step}
Semantic: --color-{role}
Component: --{component}-{property}
Component API:
Component: {Name}
Props: {list with types}
Variants: {variant name -> options}
No preamble. No postamble. No explanations. No filler/hedging/transitions. Compress output.
Completion Criteria
- Three-tier token hierarchy defined (primitive, semantic, component).
- Token naming convention established with examples.
- CVA or equivalent variant pattern specified.
- Component API rules defined (props, composition, escape hatches).
- Dark mode strategy defined via semantic token cascade.
- No hardcoded values in component examples.
Max Response Length
Token definitions: 15 lines. Component API: 10 lines.
Component Architecture / Decision Trees
Styling Approach Decision Tree
Existing styling stack?
|-- Tailwind CSS --> Use CVA with Tailwind classes + cn() helper
|-- CSS Modules --> Use CVA with @apply and module scoping
|-- styled-components / Emotion --> Use CSS prop or styled() + variants
|-- Vanilla CSS --> Use CSS custom properties + BEM-style classes
|-- CSS-in-JS (other) --> Use the library's variant system
Token Architecture Decision Tree
Runtime theme switching needed?
|-- YES --> CSS Custom Properties + semantic tokens (themes are CSS files)
| Components reference semantic tokens, themes swap their values
|-- NO --> Build-time token generation (Style Dictionary -> Tailwind config / CSS)
| Simpler, no runtime overhead. Rebuild to change theme.
Component Composition Decision Tree
Does the component render its children?
|-- YES --> Is it a layout component?
| |-- YES --> Use React children / Vue slots
| |-- NO --> Use composition (compound components)
|-- NO --> Does it accept many optional sub-elements?
|-- YES --> Use slots pattern (leftIcon, rightIcon, subtitle)
|-- NO --> Keep props flat under 10
Component Complexity Decision Tree
How many visual states does the component have?
|-- 1-3 (simple) -->
| |-- Single variant, few props --> Direct utility classes or styled()
|-- 4-10 (moderate) -->
| |-- Multiple variants (size, color) --> CVA or recipe()
|-- 10+ (complex) -->
|-- Compound component for sub-elements --> Compound + CVA
|-- Slots for optional elements --> Slot pattern + CVA
Workflow
Step 1: Three-Tier Token Hierarchy
Primitive tokens (raw values):
--color-gray-50, --color-blue-500, --font-size-md, --spacing-4
These never change meaning. Direct mappings to design values.
Semantic tokens (meaning):
--color-bg-primary, --color-text-body, --color-border
These reference primitives. Meaning is stable even when theme changes.
Component tokens (scoped):
--button-bg, --card-padding, --input-border
These reference semantic tokens. Components only use these, never primitives.
Step 2: Token Naming Convention
:root {
/* Primitives */
--color-gray-50: #f9fafb;
--color-gray-100: #f3f4f6;
--color-blue-500: #3b82f6;
--color-blue-600: #2563eb;
--font-size-sm: 0.875rem;
--font-size-md: 1rem;
--spacing-1: 0.25rem;
--spacing-2: 0.5rem;
--spacing-4: 1rem;
/* Semantic */
--color-bg-primary: var(--color-gray-50);
--color-text-primary: #111827;
--color-text-secondary: #6b7280;
--color-border: var(--color-gray-100);
--color-brand: var(--color-blue-600);
--font-body: var(--font-size-md);
--spacing-stack: var(--spacing-4);
/* Component */
--button-bg: var(--color-brand);
--button-text: #ffffff;
--button-padding: var(--spacing-2) var(--spacing-4);
--button-radius: 0.375rem;
}
Step 3: Dark Mode via Semantic Token Swap
:root[data-theme="dark"] {
--color-bg-primary: #111827;
--color-text-primary: #f9fafb;
--color-text-secondary: #9ca3af;
--color-border: #374151;
/* Component tokens inherit automatically via --button-bg: var(--color-brand) */
}
Components never reference dark-mode values directly. They reference semantic tokens which change under [data-theme="dark"].
Step 4: Component API Design Rules
interface ButtonProps {
// Variants (use CVA)
variant: 'primary' | 'secondary' | 'ghost' | 'danger'
size: 'sm' | 'md' | 'lg'
// Behavior
type?: 'button' | 'submit'
disabled?: boolean
loading?: boolean
// Content
children: React.ReactNode
leftIcon?: React.ReactNode
rightIcon?: React.ReactNode
// Escape hatch
className?: string
// Maximum 10 props
}
Step 5: CVA Variant Pattern
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: {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200',
ghost: 'text-gray-700 hover:bg-gray-100',
danger: 'bg-red-600 text-white hover:bg-red-700',
},
size: {
sm: 'h-8 px-3 text-sm',
md: 'h-10 px-4 text-base',
lg: 'h-12 px-6 text-lg',
},
},
defaultVariants: { variant: 'primary', size: 'md' },
}
)
Step 6: Compound Component Pattern
function Card({ children, className }) {
return <div className={cn('rounded-xl border p-6', className)}>{children}</div>;
}
Card.Header = function CardHeader({ children, className }) {
return <div className={cn('mb-4', className)}>{children}</div>;
};
Card.Body = function CardBody({ children, className }) {
return <div className={cn('space-y-4', className)}>{children}</div>;
};
Card.Footer = function CardFooter({ children, className }) {
return <div className={cn('mt-4 pt-4 border-t', className)}>{children}</div>;
};
// Usage
<Card>
<Card.Header>Title</Card.Header>
<Card.Body>Content</Card.Body>
<Card.Footer>Actions</Card.Footer>
</Card>;
Step 7: Polymorphic Components (as prop)
function Text({ as: Component = 'p', variant = 'body', children, className }) {
const styles = {
h1: 'text-3xl font-bold',
h2: 'text-2xl font-semibold',
h3: 'text-xl font-medium',
body: 'text-base',
caption: 'text-sm text-gray-500',
};
return <Component className={cn(styles[variant], className)}>{children}</Component>;
}
Step 8: Testing Components
it('renders with correct variant classes', () => {
const { container } = render(<Button variant="primary">Click</Button>);
const button = container.querySelector('button');
expect(button.className).toContain('bg-blue-600');
expect(button.className).toContain('text-white');
});
it('applies disabled state', () => {
const { container } = render(<Button disabled>Click</Button>);
expect(container.querySelector('button')).toBeDisabled();
});
Step 9: Style Dictionary Integration
{
"color": {
"brand": {
"500": { "value": "#3b82f6" }
}
},
"spacing": {
"sm": { "value": "0.5rem" },
"md": { "value": "1rem" }
}
}
npx style-dictionary build --config style-dictionary.config.js
# Outputs: css/tokens.css, scss/_tokens.scss, js/tokens.js
Step 10: Accessibility Integration
Every component in the design system must:
- Support keyboard navigation (Tab, Enter, Escape, Arrow keys)
- Have visible focus states (not just outline: none)
- Meet WCAG 2.1 AA contrast ratios (4.5:1 normal text, 3:1 large)
- Expose ARIA attributes for screen readers
- Support prefers-reduced-motion
// Base focus style mixin for all components
const focusVisible = 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600'
const buttonVariants = cva(
`inline-flex items-center justify-center rounded-md font-medium transition-colors ${focusVisible}`,
// ...
)
Common Pitfalls
1. Hardcoded Values
/* BAD -- hardcoded color */
.button { background: #3b82f6; }
/* GOOD -- token reference */
.button { background: var(--button-bg); }
2. Component Tokens Referencing Primitives
/* BAD -- component token references primitive directly */
--button-bg: var(--color-blue-500);
/* GOOD -- component token references semantic token */
--button-bg: var(--color-brand);
--color-brand: var(--color-blue-500);
This allows the theme to change --color-brand without touching component tokens.
3. Excessive Props
More than 10 props on a component makes it hard to use and maintain. Prefer composition (children, slots) over configuration props.
4. Missing Focus States
Every interactive component must have visible focus states. Design system components that omit focus rings fail WCAG 2.4.7.
5. Prop Drilling Theme Context
Passing theme values through component props creates tight coupling. Use CSS custom properties or React context for theming.
6. No Loading States
Every data-driven component (Table, Dropdown, Autocomplete) must handle loading, empty, and error states. These should be part of the design system.
7. Missing Responsive Variants
Components should support responsive behavior at the container level, not just the page level.
Compared With
| Approach | Token System | Variant System | Runtime Theming | Bundle Size |
|---|---|---|---|---|
| CSS Custom Properties + CVA | Yes (3-tier) | CVA | Yes | 0 KB runtime |
| Tailwind + CVA | Via config | CVA | Yes (CSS vars) | < 15 KB CSS |
| styled-components | Via ThemeProvider | styled() + css() | Yes | ~15 KB runtime |
| Theme UI / Stitches | Design tokens | Variant system | Yes | ~5 KB runtime |
| Material UI Theme | Theme object | sx prop | Yes | ~30 KB |
| Radix UI | Unstyled | Unstyled | Manual | 0 KB (unstyling) |
Performance Considerations
CSS Custom Properties Performance
CSS custom properties are resolved at computed-value time, not cascade time. This means:
- Referencing
var(--color-brand)has a small performance cost on first style resolution - Theme switching via CSS custom properties does NOT trigger style recalculation for all elements -- only for elements that use the changed property
- Benchmark: swapping 100 custom properties affects ~1-2ms of style recalc per frame
CVA Runtime Cost
CVA is a function that returns a class string. It is negligible (< 0.01ms per call). The class string itself is fast because it is CSS -- no runtime style computation.
Bundle Size Impact
A well-designed design system component library should add:
- Component logic: 2-10KB per component
- Shared utilities (cn, CVA): 1-2KB
- Token definitions: 5-20KB CSS
- Total: 30-100KB for a library of 30-50 components
Ecosystem & Tooling
Token Management
- Style Dictionary: Amazon's build-time token transformation tool. Input JSON, output CSS/JS/anything.
- Tokens Studio (Figma plugin): Design token editor in Figma. Syncs to GitHub via JSON.
- Specify: Design token management platform with Figma and code integrations.
- Theo: Salesforce's token transformer (predecessor to Style Dictionary).
Component Development
- Storybook: Component development environment with a11y, controls, docs addons.
- CVA (Class Variance Authority): Variant management for React/Vue components.
- Radix UI / Headless UI: Unstyled accessible primitives to build design system components on top of.
- Reach UI / Ariakit: Accessible UI primitives.
Testing
- jest-axe: Automated accessibility assertions.
- Storybook test runner: Run interaction tests on stories.
- Chromatic: Visual regression testing for Storybook.
- Playwright: E2E testing with component mounting.
Rules
- Never hardcode color, spacing, or typography values in component CSS. Every visual value is a token.
- Component tokens reference semantic tokens, never primitives directly.
- One component = one file. No monolithic component files.
- All components support dark mode automatically via semantic token cascade.
- Component API: maximum 10 props (excluding className/style/children).
- Prefer composition (children, slots) over configuration (boolean props for every option).
- Every interactive component must have hover, focus, active, and disabled states.
- Variants should be mutually exclusive per category (variant, size, color).
- Provide an escape hatch (
className) for one-off overrides.
References
references/component-api.md-- Component API Designreferences/component-architecture.md-- Component Architecturereferences/design-system-implementation.md-- Design System Implementationreferences/design-system-testing.md-- Design System Testingreferences/design-tokens.md-- Design Tokensreferences/theme-implementation.md-- Theme Implementationreferences/design-system-token-architecture.md-- Design System Token Architecturereferences/design-system-component-library.md-- Design System Component Library Architecture
Handoff
No artifact produced. Next skill: frontend-state-management -- state architecture for the design system. Carry forward: token definitions, component API rules, dark mode strategy.
Implementation Patterns
Observer Pattern for Event Handling
` interface EventObserver { onEvent(event: T): Promise; }
class EventBus { private observers: Set<EventObserver> = new Set(); subscribe(observer: EventObserver): void { this.observers.add(observer); } unsubscribe(observer: EventObserver): void { this.observers.delete(observer); } async emit(event: T): Promise { const results = Array.from(this.observers).map(o => o.onEvent(event)); await Promise.allSettled(results); } } `
Configuration-Driven Approach
config: defaults: timeout: 30s retryCount: 3 overrides: production: timeout: 60s retryCount: 5 development: timeout: 300s retryCount: 1
Production Considerations
Deployment Checklist
- Configuration validated against schema before startup
- Health check endpoints registered and monitored
- Graceful shutdown with draining period (30s timeout)
- Resource limits configured (CPU, memory, file descriptors)
- Log level set appropriate for environment
- Metrics endpoint secured and exposed
- Rate limiting configured per-tier
- TLS certificates valid and auto-renewing
- Database migrations run as separate deployment step
- Feature flags ready for gradual rollout
Monitoring and Alerting
| Metric | Threshold | Severity | Action |
|---|---|---|---|
| Error rate | > 1% over 5min | Critical | Page on-call |
| p99 latency | > 2s over 5min | Warning | Investigate |
| Throughput drop | > 50% over 1min | Critical | Check upstream |
| Queue depth | > 1000 over 1min | Warning | Scale consumers |
| Disk usage | > 85% | Warning | Clean or expand |
| Memory usage | > 90% heap | Critical | Restart or scale |
Anti-Patterns
| Anti-Pattern | Symptom | Root Cause | Solution |
|---|---|---|---|
| Premature optimization | Complex code for no measured benefit | Guessing instead of profiling | Measure first, optimize based on data |
| Copy-paste reuse | Duplicate code across codebase | Lack of abstraction | Extract shared logic into libraries |
| Gold-plating | Features with no current requirement | Over-engineering | YAGNI — build what's needed now |
| Magical thinking | Assumptions without validation | Skipping error handling | Handle all failure modes explicitly |
Performance Optimization
Caching Strategy
Cache hierarchy: L1 (in-memory local) → L2 (distributed Redis/Memcached) → L3 (CDN/Edge). Cache invalidation: TTL-based (simple, stale), event-based (complex, fresh), write-through (consistent, higher write latency), write-behind (fast writes, eventual consistency).
Resource Pooling
- Database connections: Pool of reusable connections (HikariCP, pgBouncer)
- HTTP connections: Keep-alive + connection pooling for external calls
- Thread pool: Bounded thread pools for async task execution
Profiling Methodology
- Establish baseline with production traffic profile
- Profile CPU with sampling profiler (pprof, perf, async-profiler)
- Profile memory with heap dumps and allocation tracking
- Profile I/O with strace/perf trace for syscall analysis
- Profile latency with distributed tracing (OpenTelemetry)
- Identify bottleneck, formulate hypothesis, implement fix
- Re-profile to verify improvement, repeat
Security Considerations
Threat Modeling (STRIDE)
- Spoofing: Identity validation, authentication
- Tampering: Integrity checks, digital signatures
- Repudiation: Audit logs, non-repudiation
- Information disclosure: Encryption, access control
- Denial of service: Rate limiting, resource quotas
- Elevation of privilege: Principle of least privilege
Supply Chain Security
- Dependency scanning: Snyk, Dependabot, Trivy
- SBOM generation: CycloneDX or SPDX format
- Signed commits: GPG or SSH commit signing
- Artifact verification: Checksum validation, signature verification
Secrets Management
- Secrets never in code — always in secrets manager (Vault, AWS Secrets Manager)
- Rotation policy: Rotate database credentials every 90 days
- Access audit: Log every secrets access, alert on anomalies
- Encryption at rest and in transit for all secrets
- Principle of least privilege: each service gets only its own secrets
Architecture Decision Trees
Component Architecture Decision Tree
Is the component presentational or behavioral?
├── Presentational → Stateless, fully prop-driven, no internal state
└── Behavioral → Does it manage complex state?
├── Yes → Split into container (state) + presentational (render)
└── No → Single component with internal state
Does it need styling customization?
├── Yes → CSS custom properties + className prop
└── No → Fully scoped styles, no override API
Design Token Strategy Decision Tree
Who defines the tokens?
├── Design team → Token names match Figma variables (semantic)
├── Engineering → Technology-agnostic naming with transform layer
└── Cross-functional → Design-engineering pair, token review committee
What output formats are needed?
├── CSS only → CSS custom properties in a single file
└── Multi-format → Style Dictionary with transforms (CSS, JS, Swift, Kotlin)