Architecting Large-Scale Design Systems Across Multiple Product Teams
February 22, 20262 min read6 views
Architecting Large-Scale Design Systems Across Multiple Product Teams
Versioning strategy, breaking changes, release governance, adoption measurement, and design token architecture. System design for the system that designs your systems.
The Scale Challenge
Design systems at scale face unique engineering challenges:
┌─────────────────────────────────────────────────────────────────┐
│ Design System Scale Challenges │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Small Scale (1-3 teams): │
│ └── Single repo, sync updates, direct communication │
│ │
│ Medium Scale (5-15 teams): │
│ ├── Versioning becomes critical │
│ ├── Breaking changes cause pain │
│ ├── Adoption varies wildly │
│ └── Documentation gaps emerge │
│ │
│ Large Scale (20+ teams): │
│ ├── Multiple versions in production simultaneously │
│ ├── Governance process required │
│ ├── Platform team needed │
│ ├── Migration tooling essential │
│ ├── Metrics-driven decisions │
│ └── Breaking changes = months of migration work │
│ │
│ Enterprise Scale (100+ teams): │
│ ├── Multiple design systems may coexist │
│ ├── Regional/product variations │
│ ├── Legacy system support forever │
│ ├── Organizational politics │
│ └── Budget justification required │
│ │
└─────────────────────────────────────────────────────────────────┘
Architecture Overview
┌─────────────────────────────────────────────────────────────────┐
│ Design System Architecture │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Design Tokens │ │
│ │ (Colors, Typography, Spacing, Shadows, Motion) │ │
│ │ Source of truth: Figma Variables / Style Dictionary │ │
│ └─────────────────────────┬───────────────────────────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ CSS │ │ JS │ │ iOS │ │
│ │ Tokens │ │ Tokens │ │ Tokens │ │
│ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │
│ │ │ │ │
│ ▼ ▼ │ │
│ ┌─────────────────────────────────┐ │ │
│ │ Component Library │ │ │
│ │ (React components + Storybook) │ │ │
│ └─────────────────┬───────────────┘ │ │
│ │ │ │
│ ┌──────────────┼──────────────┐ │ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ Web │ │ Web │ │ Web │ │ iOS │ │
│ │App A │ │App B │ │App C │ │ App │ │
│ └──────┘ └──────┘ └──────┘ └──────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Design Token Architecture
Token Hierarchy
// tokens/schema.ts
/**
* Token hierarchy:
* 1. Primitive tokens - Raw values
* 2. Semantic tokens - Meaning-based aliases
* 3. Component tokens - Component-specific values
*/
// Level 1: Primitives (never use directly in components)
const primitives = {
colors: {
blue: {
50: '#eff6ff',
100: '#dbeafe',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
900: '#1e3a8a',
},
gray: {
50: '#f9fafb',
100: '#f3f4f6',
500: '#6b7280',
700: '#374151',
900: '#111827',
},
// ... more colors
},
spacing: {
0: '0',
1: '0.25rem',
2: '0.5rem',
3: '0.75rem',
4: '1rem',
6: '1.5rem',
8: '2rem',
12: '3rem',
16: '4rem',
},
typography: {
fontFamilies: {
sans: 'Inter, system-ui, sans-serif',
mono: 'JetBrains Mono, monospace',
},
fontSizes: {
xs: '0.75rem',
sm: '0.875rem',
base: '1rem',
lg: '1.125rem',
xl: '1.25rem',
'2xl': '1.5rem',
'3xl': '1.875rem',
},
lineHeights: {
tight: '1.25',
normal: '1.5',
relaxed: '1.75',
},
},
};
// Level 2: Semantic tokens (use in components)
const semanticTokens = {
color: {
// Backgrounds
background: {
primary: { $value: '{colors.white}', $description: 'Primary background' },
secondary: { $value: '{colors.gray.50}', $description: 'Secondary background' },
inverse: { $value: '{colors.gray.900}', $description: 'Inverse background' },
},
// Text
text: {
primary: { $value: '{colors.gray.900}', $description: 'Primary text' },
secondary: { $value: '{colors.gray.500}', $description: 'Secondary text' },
inverse: { $value: '{colors.white}', $description: 'Text on dark backgrounds' },
link: { $value: '{colors.blue.600}', $description: 'Link text' },
},
// Interactive
interactive: {
primary: { $value: '{colors.blue.600}', $description: 'Primary actions' },
primaryHover: { $value: '{colors.blue.700}', $description: 'Primary hover state' },
secondary: { $value: '{colors.gray.100}', $description: 'Secondary actions' },
},
// Feedback
feedback: {
success: { $value: '{colors.green.600}', $description: 'Success states' },
warning: { $value: '{colors.amber.500}', $description: 'Warning states' },
error: { $value: '{colors.red.600}', $description: 'Error states' },
info: { $value: '{colors.blue.500}', $description: 'Info states' },
},
// Border
border: {
default: { $value: '{colors.gray.200}', $description: 'Default borders' },
strong: { $value: '{colors.gray.300}', $description: 'Emphasized borders' },
focus: { $value: '{colors.blue.500}', $description: 'Focus ring color' },
},
},
spacing: {
component: {
xs: { $value: '{spacing.1}' },
sm: { $value: '{spacing.2}' },
md: { $value: '{spacing.4}' },
lg: { $value: '{spacing.6}' },
xl: { $value: '{spacing.8}' },
},
layout: {
gutter: { $value: '{spacing.4}' },
section: { $value: '{spacing.16}' },
},
},
};
// Level 3: Component tokens
const componentTokens = {
button: {
primary: {
background: { $value: '{color.interactive.primary}' },
backgroundHover: { $value: '{color.interactive.primaryHover}' },
text: { $value: '{color.text.inverse}' },
paddingX: { $value: '{spacing.component.lg}' },
paddingY: { $value: '{spacing.component.sm}' },
borderRadius: { $value: '{borderRadius.md}' },
},
secondary: {
background: { $value: '{color.interactive.secondary}' },
text: { $value: '{color.text.primary}' },
border: { $value: '{color.border.default}' },
},
},
input: {
background: { $value: '{color.background.primary}' },
border: { $value: '{color.border.default}' },
borderFocus: { $value: '{color.border.focus}' },
text: { $value: '{color.text.primary}' },
placeholder: { $value: '{color.text.secondary}' },
paddingX: { $value: '{spacing.component.md}' },
paddingY: { $value: '{spacing.component.sm}' },
},
};
Token Build Pipeline
// build-tokens.ts
import StyleDictionary from 'style-dictionary';
const config: StyleDictionary.Config = {
source: ['tokens/**/*.json'],
platforms: {
// CSS Custom Properties
css: {
transformGroup: 'css',
buildPath: 'dist/css/',
files: [
{
destination: 'tokens.css',
format: 'css/variables',
options: {
outputReferences: true, // Preserve references
},
},
],
},
// JavaScript/TypeScript
js: {
transformGroup: 'js',
buildPath: 'dist/js/',
files: [
{
destination: 'tokens.js',
format: 'javascript/es6',
},
{
destination: 'tokens.d.ts',
format: 'typescript/es6-declarations',
},
],
},
// JSON for Figma sync
json: {
transformGroup: 'js',
buildPath: 'dist/json/',
files: [
{
destination: 'tokens.json',
format: 'json/nested',
},
],
},
// iOS Swift
ios: {
transformGroup: 'ios-swift',
buildPath: 'dist/ios/',
files: [
{
destination: 'DesignTokens.swift',
format: 'ios-swift/class.swift',
className: 'DesignTokens',
},
],
},
// Android Compose
android: {
transformGroup: 'compose',
buildPath: 'dist/android/',
files: [
{
destination: 'DesignTokens.kt',
format: 'compose/object',
},
],
},
},
};
// Custom transforms for responsive tokens
StyleDictionary.registerTransform({
name: 'size/responsive',
type: 'value',
matcher: (token) => token.attributes?.responsive,
transformer: (token) => {
// Generate clamp() for fluid typography
const { min, max, minVw, maxVw } = token.original.value;
const slope = (max - min) / (maxVw - minVw);
const intercept = min - slope * minVw;
return `clamp(${min}rem, ${intercept}rem + ${slope * 100}vw, ${max}rem)`;
},
});
StyleDictionary.extend(config).buildAllPlatforms();
Versioning Strategy
Semantic Versioning for Design Systems
// version-policy.ts
/**
* Versioning Rules for Design Systems
*
* MAJOR (X.0.0): Breaking changes
* - Removing a component
* - Renaming a component
* - Changing component API (required props, prop types)
* - Removing design tokens
* - Changing token values significantly
*
* MINOR (0.X.0): New features, backward compatible
* - New components
* - New component variants
* - New design tokens
* - New optional props
*
* PATCH (0.0.X): Bug fixes, backward compatible
* - Bug fixes
* - Documentation updates
* - Minor visual tweaks (< 2px)
* - Performance improvements
*/
interface VersionBump {
type: 'major' | 'minor' | 'patch';
changes: Change[];
migrationGuide?: string;
breakingChanges?: BreakingChange[];
}
interface Change {
component: string;
description: string;
type: 'added' | 'changed' | 'deprecated' | 'removed' | 'fixed';
}
interface BreakingChange {
component: string;
before: string;
after: string;
codemod?: string; // Path to automated fix
manualSteps?: string[];
}
// Automated version bump determination
function determineVersionBump(changes: Change[]): 'major' | 'minor' | 'patch' {
const hasBreaking = changes.some(
(c) => c.type === 'removed' || isBreakingChange(c)
);
if (hasBreaking) return 'major';
const hasNew = changes.some(
(c) => c.type === 'added'
);
if (hasNew) return 'minor';
return 'patch';
}
function isBreakingChange(change: Change): boolean {
// Analyze change description for breaking patterns
const breakingPatterns = [
/removed? prop/i,
/renamed? (to|from)/i,
/changed? (type|signature)/i,
/required? prop/i,
/removed? (support|variant)/i,
];
return breakingPatterns.some((p) => p.test(change.description));
}
Multi-Version Support
// Version coexistence strategy
// Package structure
// @company/design-system@7.x.x - Current major
// @company/design-system@6.x.x - Previous major (maintenance mode)
// @company/design-system-v5 - Legacy (security fixes only)
// package.json - Consumer can use multiple versions
{
"dependencies": {
"@company/design-system": "^7.0.0",
"@company/design-system-v6": "npm:@company/design-system@6.x.x"
}
}
// Component aliasing during migration
// components/Button.tsx
import { Button as ButtonV7 } from '@company/design-system';
import { Button as ButtonV6 } from '@company/design-system-v6';
export function Button(props: ButtonProps) {
const { useV7 = true, ...rest } = props;
// Feature flag for gradual migration
if (useV7) {
return <ButtonV7 {...rest} />;
}
return <ButtonV6 {...rest} />;
}
// CSS token coexistence
// Each version prefixes its tokens
:root {
/* V7 tokens (current) */
--ds-color-primary: #3b82f6;
--ds-spacing-md: 1rem;
/* V6 tokens (legacy, prefixed) */
--ds-v6-color-primary: #2563eb;
--ds-v6-spacing-md: 16px;
}
Breaking Change Management
Deprecation Workflow
// deprecation-system.ts
interface DeprecationConfig {
component: string;
version: string; // When deprecated
removalVersion: string; // When removed
replacement?: string;
migrationGuide: string;
codemod?: string;
}
const deprecations: DeprecationConfig[] = [
{
component: 'Button',
prop: 'type',
version: '7.0.0',
removalVersion: '8.0.0',
replacement: 'variant',
migrationGuide: 'https://docs/migration/button-type-to-variant',
codemod: 'npx @company/ds-codemod button-type-to-variant',
},
];
// Runtime deprecation warning
function useDeprecationWarning(
componentName: string,
props: Record<string, unknown>
): void {
useEffect(() => {
if (process.env.NODE_ENV === 'production') return;
const componentDeprecations = deprecations.filter(
(d) => d.component === componentName
);
for (const dep of componentDeprecations) {
if (dep.prop && props[dep.prop] !== undefined) {
console.warn(
`[Design System] ${componentName}: "${dep.prop}" is deprecated ` +
`and will be removed in v${dep.removalVersion}. ` +
`Use "${dep.replacement}" instead. ` +
`Migration guide: ${dep.migrationGuide}`
);
}
}
}, [componentName, props]);
}
// Usage in component
function Button({ type, variant, ...props }: ButtonProps) {
useDeprecationWarning('Button', { type });
// Support both during transition
const resolvedVariant = variant ?? type;
return <button className={`btn-${resolvedVariant}`} {...props} />;
}
Automated Codemods
// codemods/button-type-to-variant.ts
import { API, FileInfo, Options } from 'jscodeshift';
export default function transformer(
file: FileInfo,
api: API,
options: Options
): string | null {
const j = api.jscodeshift;
const root = j(file.source);
// Find all Button components
root
.findJSXElements('Button')
.forEach((path) => {
const typeAttr = path.node.openingElement.attributes?.find(
(attr) =>
attr.type === 'JSXAttribute' &&
attr.name.name === 'type'
);
if (typeAttr && typeAttr.type === 'JSXAttribute') {
// Rename 'type' to 'variant'
typeAttr.name.name = 'variant';
}
});
return root.toSource({ quote: 'single' });
}
// Run with: npx jscodeshift -t codemods/button-type-to-variant.ts src/
Migration Dashboard
// Track migration progress across teams
interface MigrationStatus {
team: string;
repository: string;
currentVersion: string;
targetVersion: string;
deprecatedUsages: DeprecatedUsage[];
migrationProgress: number; // 0-100
estimatedEffort: string; // "2 hours", "1 day", etc.
blockers: string[];
}
async function scanRepository(repoPath: string): Promise<MigrationStatus> {
// Parse all files for design system usage
const files = await glob(`${repoPath}/**/*.{tsx,jsx}`, {
ignore: ['**/node_modules/**'],
});
const deprecatedUsages: DeprecatedUsage[] = [];
for (const file of files) {
const content = await fs.readFile(file, 'utf-8');
const ast = parse(content, { plugins: ['jsx', 'typescript'] });
// Check for deprecated props/components
traverse(ast, {
JSXElement(path) {
const elementName = path.node.openingElement.name;
if (elementName.type === 'JSXIdentifier') {
const deprecation = findDeprecation(
elementName.name,
path.node.openingElement.attributes
);
if (deprecation) {
deprecatedUsages.push({
file,
line: path.node.loc?.start.line ?? 0,
...deprecation,
});
}
}
},
});
}
return {
repository: repoPath,
deprecatedUsages,
migrationProgress: calculateProgress(deprecatedUsages),
estimatedEffort: estimateEffort(deprecatedUsages),
};
}
Release Governance
RFC Process
# RFC: Component Name
## Summary
One paragraph explanation of the change.
## Motivation
Why are we doing this? What problem does it solve?
## Detailed Design
### API
\`\`\`typescript
interface ComponentProps {
// Prop definitions with JSDoc
}
\`\`\`
### Visual Design
[Figma link]
### Accessibility
- ARIA patterns used
- Keyboard interactions
- Screen reader considerations
### Theming
How it uses design tokens.
## Migration
If this replaces existing functionality, how do teams migrate?
## Alternatives Considered
What other approaches were considered?
## Adoption Strategy
How will this be rolled out?
## Open Questions
What needs to be resolved?
Review Process
// governance/review-process.ts
interface RFCReview {
rfcId: string;
status: 'draft' | 'review' | 'approved' | 'rejected' | 'implemented';
reviewers: {
design: string[];
engineering: string[];
accessibility: string[];
};
approvals: {
design: boolean;
engineering: boolean;
accessibility: boolean;
};
comments: Comment[];
blockers: string[];
}
const APPROVAL_REQUIREMENTS = {
design: 1, // At least 1 design approval
engineering: 2, // At least 2 engineering approvals
accessibility: 1, // At least 1 a11y approval
};
function canMerge(review: RFCReview): boolean {
const designApprovals = review.reviewers.design.filter(
(r) => review.approvals.design
).length;
const engApprovals = review.reviewers.engineering.filter(
(r) => review.approvals.engineering
).length;
const a11yApprovals = review.reviewers.accessibility.filter(
(r) => review.approvals.accessibility
).length;
return (
designApprovals >= APPROVAL_REQUIREMENTS.design &&
engApprovals >= APPROVAL_REQUIREMENTS.engineering &&
a11yApprovals >= APPROVAL_REQUIREMENTS.accessibility &&
review.blockers.length === 0
);
}
Release Cadence
┌─────────────────────────────────────────────────────────────────┐
│ Release Schedule │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Release Type │ Frequency │ Contents │
│ ────────────────┼──────────────┼───────────────────────────── │
│ Patch │ As needed │ Bug fixes, docs │
│ Minor │ Bi-weekly │ New features, components │
│ Major │ Quarterly │ Breaking changes │
│ │
│ Timeline for Major Release: │
│ │
│ Week 1-4: RFC submission and review │
│ Week 5-8: Implementation │
│ Week 9-10: Alpha release (internal testing) │
│ Week 11: Beta release (volunteer teams) │
│ Week 12: Release candidate │
│ Week 13: General availability │
│ Week 14+: Migration support period │
│ │
│ Support Policy: │
│ ├── Current major: Full support │
│ ├── Previous major: Bug fixes only (6 months) │
│ └── Older: Security fixes only │
│ │
└─────────────────────────────────────────────────────────────────┘
Adoption Measurement
Telemetry
// telemetry/component-usage.ts
interface UsageEvent {
component: string;
version: string;
team: string;
repository: string;
props: Record<string, unknown>;
timestamp: number;
}
// Client-side tracking (dev/staging only)
function trackComponentUsage(
component: string,
props: Record<string, unknown>
): void {
if (process.env.NODE_ENV === 'production') return;
const event: UsageEvent = {
component,
version: DESIGN_SYSTEM_VERSION,
team: process.env.TEAM_ID ?? 'unknown',
repository: process.env.REPOSITORY ?? 'unknown',
props: sanitizeProps(props),
timestamp: Date.now(),
};
// Send to analytics
fetch('/api/ds-telemetry', {
method: 'POST',
body: JSON.stringify(event),
keepalive: true,
});
}
// HOC for automatic tracking
function withUsageTracking<P extends object>(
Component: React.ComponentType<P>,
componentName: string
): React.ComponentType<P> {
return function TrackedComponent(props: P) {
useEffect(() => {
trackComponentUsage(componentName, props);
}, [props]);
return <Component {...props} />;
};
}
// Build-time analysis
async function analyzeAdoption(): Promise<AdoptionReport> {
const allRepos = await getOrganizationRepos();
const results: RepoAnalysis[] = [];
for (const repo of allRepos) {
const analysis = await analyzeRepo(repo);
results.push(analysis);
}
return {
totalComponents: countUniqueComponents(results),
adoptionByTeam: groupByTeam(results),
versionDistribution: getVersionDistribution(results),
deprecatedUsage: findDeprecatedUsage(results),
customOverrides: findCustomOverrides(results),
};
}
interface AdoptionReport {
// Overall adoption
totalComponents: number;
dsComponentUsage: number;
customComponentUsage: number;
adoptionRate: number; // dsComponents / total
// By team
adoptionByTeam: {
team: string;
adoptionRate: number;
blockers: string[];
}[];
// Version distribution
versionDistribution: {
version: string;
repositories: number;
percentage: number;
}[];
// Health indicators
deprecatedUsage: {
component: string;
usageCount: number;
teams: string[];
}[];
}
Metrics Dashboard
// Key metrics to track
interface DesignSystemMetrics {
// Adoption
adoption: {
overallRate: number; // % of UI using DS components
teamAdoption: Map<string, number>;
newProjectAdoption: number; // % of new projects using DS
componentCoverage: number; // % of components in DS vs custom
};
// Quality
quality: {
accessibilityScore: number; // Automated a11y audit score
performanceScore: number; // Lighthouse component score
bugCount: number; // Open bugs
avgBugResolutionTime: number; // Days
};
// Developer Experience
developerExperience: {
npmDownloads: number; // Weekly downloads
documentationViews: number; // Weekly page views
storybookViews: number; // Weekly Storybook sessions
supportTickets: number; // Weekly tickets
avgResponseTime: number; // Hours
};
// Migration
migration: {
teamsOnLatest: number; // % on current major
avgMigrationTime: number; // Days to migrate
blockedTeams: number; // Teams blocked on migration
deprecatedUsageCount: number; // Total deprecated usages
};
}
// Automated reporting
async function generateWeeklyReport(): Promise<void> {
const metrics = await collectMetrics();
await sendSlackReport({
channel: '#design-system',
blocks: [
{
type: 'header',
text: `Design System Weekly Report - Week of ${formatDate(new Date())}`,
},
{
type: 'section',
fields: [
{ title: 'Adoption Rate', value: `${metrics.adoption.overallRate}%` },
{ title: 'Teams on Latest', value: `${metrics.migration.teamsOnLatest}%` },
{ title: 'Open Bugs', value: String(metrics.quality.bugCount) },
{ title: 'Support Tickets', value: String(metrics.developerExperience.supportTickets) },
],
},
{
type: 'section',
text: generateInsights(metrics),
},
],
});
}
Component Development Workflow
// Component template and standards
// components/Button/Button.tsx
import { forwardRef } from 'react';
import { clsx } from 'clsx';
import { tokens } from '@company/design-tokens';
import type { ButtonProps } from './Button.types';
import styles from './Button.module.css';
/**
* Button component for triggering actions.
*
* @example
* ```tsx
* <Button variant="primary" onClick={handleClick}>
* Click me
* </Button>
* ```
*
* @see https://design-system.company.com/components/button
*/
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
function Button(
{
variant = 'primary',
size = 'md',
isLoading = false,
isDisabled = false,
leftIcon,
rightIcon,
children,
className,
...props
},
ref
) {
return (
<button
ref={ref}
className={clsx(
styles.button,
styles[`button--${variant}`],
styles[`button--${size}`],
isLoading && styles['button--loading'],
className
)}
disabled={isDisabled || isLoading}
aria-busy={isLoading}
{...props}
>
{isLoading && <Spinner className={styles.spinner} />}
{leftIcon && <span className={styles.leftIcon}>{leftIcon}</span>}
<span className={styles.label}>{children}</span>
{rightIcon && <span className={styles.rightIcon}>{rightIcon}</span>}
</button>
);
}
);
// components/Button/Button.types.ts
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
/** Visual style variant */
variant?: 'primary' | 'secondary' | 'tertiary' | 'danger';
/** Size of the button */
size?: 'sm' | 'md' | 'lg';
/** Shows loading spinner */
isLoading?: boolean;
/** Disables the button */
isDisabled?: boolean;
/** Icon to show before label */
leftIcon?: React.ReactNode;
/** Icon to show after label */
rightIcon?: React.ReactNode;
}
// components/Button/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { axe } from 'jest-axe';
import { Button } from './Button';
describe('Button', () => {
it('renders with default props', () => {
render(<Button>Click me</Button>);
expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument();
});
it('handles click events', () => {
const onClick = jest.fn();
render(<Button onClick={onClick}>Click me</Button>);
fireEvent.click(screen.getByRole('button'));
expect(onClick).toHaveBeenCalledTimes(1);
});
it('shows loading state', () => {
render(<Button isLoading>Click me</Button>);
expect(screen.getByRole('button')).toHaveAttribute('aria-busy', 'true');
expect(screen.getByRole('button')).toBeDisabled();
});
it('has no accessibility violations', async () => {
const { container } = render(<Button>Click me</Button>);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
// components/Button/Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
title: 'Components/Button',
component: Button,
parameters: {
docs: {
description: {
component: 'Button component for triggering actions.',
},
},
},
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'tertiary', 'danger'],
},
size: {
control: 'select',
options: ['sm', 'md', 'lg'],
},
},
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Primary: Story = {
args: {
variant: 'primary',
children: 'Primary Button',
},
};
export const AllVariants: Story = {
render: () => (
<div style={{ display: 'flex', gap: '1rem' }}>
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="tertiary">Tertiary</Button>
<Button variant="danger">Danger</Button>
</div>
),
};
Summary
Design system architecture at scale requires:
| Aspect | Strategy | Implementation |
|---|---|---|
| Tokens | 3-tier hierarchy | Style Dictionary pipeline |
| Versioning | Semantic + deprecation | Multi-version support |
| Breaking Changes | Codemods + migration guides | Automated transforms |
| Governance | RFC process | Review requirements |
| Adoption | Telemetry + analysis | Build-time scanning |
| Quality | Automated testing | Unit, visual, a11y |
Key principles:
- Treat it like a product — Design systems serve internal customers
- Version carefully — Breaking changes affect dozens of teams
- Automate migrations — Codemods reduce migration burden
- Measure everything — Data-driven decisions on priorities
- Support transitions — Multi-version coexistence during migrations
- Govern changes — RFC process prevents API churn
The goal: teams can adopt confidently, upgrade predictably, and build consistently.
What did you think?