Design Systems & Component Libraries
Build production-grade design systems from scratch. Learn Storybook patterns, component documentation, token system architecture, accessibility in design systems, and versioning component libraries used by senior engineers.
๐๏ธ 1. Building a Design System from Scratch
Ready to build the LEGO set that powers your entire app? ๐งฑ A design system isn't just pretty colors โ it's tokens, components, patterns, and docs all working together in perfect harmony. Think of it as your app's DNA โ consistent, scalable, and beautiful! โจ
๐ด Impact: CRITICAL โ A well-built design system saves hundreds of hours across your team and ensures pixel-perfect consistency
๐ In this section: Design Tokens โข Color System โข Spacing & Typography โข Button Component โข Theme Provider
Design System Layers:
โข Tokens: Colors, spacing, typography, shadows
โข Components: Buttons, inputs, cards, etc.
โข Patterns: Compositions of components
โข Documentation: Usage guidelines and examples
// โ Design System Architecture// 1. Design Tokens (Foundation)// tokens/colors.tsexport const colors = {// Semantic colorsprimary: {50: '#f0f9ff',100: '#e0f2fe',200: '#bae6fd',300: '#7dd3fc',400: '#38bdf8',500: '#0ea5e9',600: '#0284c7',700: '#0369a1',800: '#075985',900: '#0c4a6e',},neutral: {50: '#fafafa',100: '#f5f5f5',200: '#e5e5e5',300: '#d4d4d4',400: '#a3a3a3',500: '#737373',600: '#525252',700: '#404040',800: '#262626',900: '#171717',},semantic: {success: '#10b981',warning: '#f59e0b',error: '#ef4444',info: '#3b82f6',}};// tokens/spacing.tsexport const spacing = {0: '0',1: '0.25rem', // 4px2: '0.5rem', // 8px3: '0.75rem', // 12px4: '1rem', // 16px5: '1.25rem', // 20px6: '1.5rem', // 24px8: '2rem', // 32px10: '2.5rem', // 40px12: '3rem', // 48px16: '4rem', // 64px20: '5rem', // 80px24: '6rem', // 96px};// tokens/typography.tsexport const typography = {fontFamily: {sans: ['Inter', 'system-ui', 'sans-serif'],mono: ['Fira Code', 'monospace'],},fontSize: {xs: '0.75rem', // 12pxsm: '0.875rem', // 14pxbase: '1rem', // 16pxlg: '1.125rem', // 18pxxl: '1.25rem', // 20px'2xl': '1.5rem', // 24px'3xl': '1.875rem', // 30px'4xl': '2.25rem', // 36px},fontWeight: {normal: 400,medium: 500,semibold: 600,bold: 700,},lineHeight: {tight: 1.25,normal: 1.5,relaxed: 1.75,},};// tokens/shadows.tsexport const shadows = {sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',base: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',};// โ Component Structure// components/Button/Button.tsximport React from 'react';import { colors, spacing, typography } from '../../tokens';export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {variant?: 'primary' | 'secondary' | 'outline' | 'ghost';size?: 'sm' | 'md' | 'lg';isLoading?: boolean;leftIcon?: React.ReactNode;rightIcon?: React.ReactNode;}export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(({variant = 'primary',size = 'md',isLoading = false,leftIcon,rightIcon,children,disabled,className,...props}, ref) => {const baseStyles = {display: 'inline-flex',alignItems: 'center',justifyContent: 'center',fontWeight: typography.fontWeight.medium,borderRadius: '0.375rem',transition: 'all 0.2s',cursor: disabled || isLoading ? 'not-allowed' : 'pointer',opacity: disabled ? 0.6 : 1,};const variantStyles = {primary: {backgroundColor: colors.primary[600],color: 'white','&:hover': { backgroundColor: colors.primary[700] },},secondary: {backgroundColor: colors.neutral[200],color: colors.neutral[900],'&:hover': { backgroundColor: colors.neutral[300] },},outline: {border: `1px solid ${colors.neutral[300]}`,backgroundColor: 'transparent',color: colors.neutral[700],'&:hover': { backgroundColor: colors.neutral[50] },},ghost: {backgroundColor: 'transparent',color: colors.neutral[700],'&:hover': { backgroundColor: colors.neutral[100] },},};const sizeStyles = {sm: {padding: `${spacing[2]} ${spacing[3]}`,fontSize: typography.fontSize.sm,},md: {padding: `${spacing[2]} ${spacing[4]}`,fontSize: typography.fontSize.base,},lg: {padding: `${spacing[3]} ${spacing[6]}`,fontSize: typography.fontSize.lg,},};return (<buttonref={ref}disabled={disabled || isLoading}className={className}style={{...baseStyles,...variantStyles[variant],...sizeStyles[size],}}{...props}>{isLoading ? (<Spinner size="sm" />) : (<>{leftIcon && <span style={{ marginRight: spacing[2] }}>{leftIcon}</span>}{children}{rightIcon && <span style={{ marginLeft: spacing[2] }}>{rightIcon}</span>}</>)}</button>);});Button.displayName = 'Button';// โ Design System Index// index.tsexport { Button } from './components/Button';export { Input } from './components/Input';export { Card } from './components/Card';export * from './tokens';// โ TypeScript Theme Providerimport React, { createContext, useContext } from 'react';interface Theme {colors: typeof colors;spacing: typeof spacing;typography: typeof typography;shadows: typeof shadows;}const defaultTheme: Theme = {colors,spacing,typography,shadows,};const ThemeContext = createContext<Theme>(defaultTheme);export const ThemeProvider: React.FC<{ theme?: Partial<Theme>; children: React.ReactNode }> = ({theme,children,}) => {const mergedTheme = { ...defaultTheme, ...theme };return <ThemeContext.Provider value={mergedTheme}>{children}</ThemeContext.Provider>;};export const useTheme = () => useContext(ThemeContext);
๐ 2. Storybook Patterns
Storybook is like a playground for your components! ๐ข Build, test, and document them in isolation before they ever touch your app. Variant stories, responsive previews, accessibility audits โ all in one place. Your designers will love it and your devs will ship faster! ๐
๐ Impact: HIGH โ Storybook accelerates component development and serves as living documentation for your entire team
๐ In this section: Configuration โข Story Types โข ArgTypes & Controls โข Responsive Stories โข A11y Addon โข MDX Docs
// โ Storybook Configuration// .storybook/main.jsmodule.exports = {stories: ['../src/**/*.stories.@(js|jsx|ts|tsx|mdx)'],addons: ['@storybook/addon-essentials','@storybook/addon-a11y', // Accessibility'@storybook/addon-viewport', // Responsive testing'@storybook/addon-docs', // Documentation],framework: '@storybook/react',features: {buildStoriesJson: true,},};// โ Component Story (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: {layout: 'centered',docs: {description: {component: 'Button component with multiple variants and sizes.',},},},tags: ['autodocs'],argTypes: {variant: {control: 'select',options: ['primary', 'secondary', 'outline', 'ghost'],description: 'Button variant style',},size: {control: 'select',options: ['sm', 'md', 'lg'],description: 'Button size',},isLoading: {control: 'boolean',description: 'Show loading state',},disabled: {control: 'boolean',description: 'Disable button',},},};export default meta;type Story = StoryObj<typeof Button>;// โ Default Storyexport const Default: Story = {args: {children: 'Button',variant: 'primary',size: 'md',},};// โ Variant Storiesexport const Primary: Story = {args: {children: 'Primary Button',variant: 'primary',},};export const Secondary: Story = {args: {children: 'Secondary Button',variant: 'secondary',},};export const Outline: Story = {args: {children: 'Outline Button',variant: 'outline',},};// โ Size Storiesexport const Small: Story = {args: {children: 'Small Button',size: 'sm',},};export const Large: Story = {args: {children: 'Large Button',size: 'lg',},};// โ State Storiesexport const Loading: Story = {args: {children: 'Loading Button',isLoading: true,},};export const Disabled: Story = {args: {children: 'Disabled Button',disabled: true,},};// โ Composition Storyexport const WithIcons: Story = {args: {children: 'Button with Icon',leftIcon: <Icon name="plus" />,},};// โ All Variants Showcaseexport const AllVariants: Story = {render: () => (<div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}><Button variant="primary">Primary</Button><Button variant="secondary">Secondary</Button><Button variant="outline">Outline</Button><Button variant="ghost">Ghost</Button></div>),};// โ Responsive Storyexport const Responsive: Story = {parameters: {viewport: {viewports: {mobile: { name: 'Mobile', styles: { width: '375px', height: '667px' } },tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } },desktop: { name: 'Desktop', styles: { width: '1920px', height: '1080px' } },},},},render: () => (<div><Button size="sm">Small on Mobile</Button><Button size="md">Medium on Tablet</Button><Button size="lg">Large on Desktop</Button></div>),};// โ Accessibility Storyexport const Accessibility: Story = {parameters: {a11y: {config: {rules: [{id: 'color-contrast',enabled: true,},],},},},render: () => (<div><Button>Accessible Button</Button><Button aria-label="Close dialog">ร</Button></div>),};// โ Interactive Storyexport const Interactive: Story = {args: {children: 'Click me',onClick: () => alert('Button clicked!'),},};// โ Storybook Decorators// .storybook/decorators.tsximport React from 'react';import { ThemeProvider } from '../src/theme';export const withTheme = (Story) => (<ThemeProvider><Story /></ThemeProvider>);// โ Storybook Parametersexport const parameters = {actions: { argTypesRegex: '^on[A-Z].*' },controls: {matchers: {color: /(background|color)$/i,date: /Date$/,},},backgrounds: {default: 'light',values: [{ name: 'light', value: '#ffffff' },{ name: 'dark', value: '#1a1a1a' },],},};// โ Storybook Addons// Accessibility testingimport { withA11y } from '@storybook/addon-a11y';// Viewport testingimport { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';// Documentationimport { DocsContainer } from '@storybook/addon-docs';// โ Component Documentation (MDX)// Button.mdximport { Meta, Story, Canvas, Controls } from '@storybook/blocks';import { Button } from './Button';<Meta title="Components/Button" component={Button} /># ButtonThe Button component is used to trigger actions.## Usage```tsximport { Button } from '@design-system/components';<Button variant="primary">Click me</Button>```## Variants<Canvas><Story name="All Variants" /></Canvas>## API<Controls of={Button} />
๐ 3. Component Documentation
Good documentation is the difference between a design system people LOVE and one they ignore! ๐ JSDoc comments, usage examples, do's and don'ts, accessibility notes โ give your team everything they need to use your components correctly. Future you will be SO grateful! ๐
๐ต Impact: MEDIUM โ Great docs mean fewer Slack questions and faster onboarding โ your team productivity will skyrocket
๐ In this section: JSDoc Patterns โข Usage Examples โข Best Practices โข Accessibility Docs โข README Templates
// โ Component Documentation Template/*** Button Component** @description* A versatile button component with multiple variants, sizes, and states.* Use buttons to trigger actions in your application.** @example* ```tsx* import { Button } from '@design-system/components';** <Button variant="primary" onClick=\{handleClick\}>* Click me* </Button>* ```** @see {{@link} https://design-system.example.com/components/button | Full Documentation}*/// โ JSDoc Documentationinterface ButtonProps {/*** Button variant style* @default 'primary'* @example* <Button variant="primary">Primary</Button>* <Button variant="secondary">Secondary</Button>*/variant?: 'primary' | 'secondary' | 'outline' | 'ghost';/*** Button size* @default 'md'*/size?: 'sm' | 'md' | 'lg';/*** Show loading state* @default false* @example* <Button isLoading>Loading...</Button>*/isLoading?: boolean;/*** Disable button* @default false*/disabled?: boolean;/*** Icon to display on the left*/leftIcon?: React.ReactNode;/*** Icon to display on the right*/rightIcon?: React.ReactNode;/*** Button content*/children: React.ReactNode;/*** Click handler*/onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;}// โ Usage Examples Documentationconst usageExamples = {basic: {title: 'Basic Usage',code: `<Button>Click me</Button>`,description: 'Simple button with default props'},variants: {title: 'Variants',code: `<Button variant="primary">Primary</Button><Button variant="secondary">Secondary</Button><Button variant="outline">Outline</Button><Button variant="ghost">Ghost</Button>`,description: 'Different button styles for different use cases'},sizes: {title: 'Sizes',code: `<Button size="sm">Small</Button><Button size="md">Medium</Button><Button size="lg">Large</Button>`,description: 'Button sizes for different contexts'},withIcons: {title: 'With Icons',code: `<Button leftIcon={<Icon name="plus" />}>Add Item</Button><Button rightIcon={<Icon name="arrow-right" />}>Continue</Button>`,description: 'Buttons with icons for better UX'},loading: {title: 'Loading State',code: `<Button isLoading>Processing...</Button>`,description: 'Show loading state during async operations'},disabled: {title: 'Disabled State',code: `<Button disabled>Cannot Click</Button>`,description: 'Disable button when action is not available'}};// โ Best Practices Documentationconst bestPractices = {do: ['Use primary variant for main actions','Use secondary for secondary actions','Use outline for less important actions','Show loading state during async operations','Disable button when action is not available','Use appropriate size for context','Include icons for better UX when helpful'],dont: ['Don't use too many button variants on one page','Don't use buttons for navigation (use links)','Don't disable buttons without explanation','Don't use loading state for instant actions','Don't nest buttons inside buttons']};// โ Accessibility Documentationconst accessibility = {keyboard: ['Button is focusable with Tab key','Activate with Enter or Space','Focus indicator is visible'],screenReader: ['Button text is read by screen readers','Loading state is announced','Disabled state is announced'],aria: ['Use aria-label for icon-only buttons','Use aria-busy for loading state','Use aria-disabled for disabled state']};// โ Component README Template// components/Button/README.md```markdown# ButtonA versatile button component with multiple variants, sizes, and states.## Installation```bashnpm install @design-system/components```## Usage```tsximport { Button } from '@design-system/components';<Button variant="primary" onClick={handleClick}>Click me</Button>```## Props| Prop | Type | Default | Description ||------|------|---------|-------------|| variant | 'primary' | 'secondary' | 'outline' | 'ghost' | 'primary' | Button variant style || size | 'sm' | 'md' | 'lg' | 'md' | Button size || isLoading | boolean | false | Show loading state || disabled | boolean | false | Disable button || leftIcon | ReactNode | - | Icon on the left || rightIcon | ReactNode | - | Icon on the right |## Examples### Variants```tsx<Button variant="primary">Primary</Button><Button variant="secondary">Secondary</Button><Button variant="outline">Outline</Button><Button variant="ghost">Ghost</Button>```### Sizes```tsx<Button size="sm">Small</Button><Button size="md">Medium</Button><Button size="lg">Large</Button>```## Accessibility- Keyboard navigable (Tab, Enter, Space)- Screen reader friendly- Focus indicator visible- Loading and disabled states announced## Related Components- [Link](./Link.md) - For navigation- [IconButton](./IconButton.md) - Icon-only button```// โ Auto-generated API Docs// Using TypeDoc or similar/*** @component* @name Button* @category Components* @description Versatile button component* @example* <Button variant="primary">Click me</Button>*/export const Button = ...
๐จ 4. Token System Architecture
Tokens are the atoms of your design system โ the tiniest building blocks that define EVERYTHING! โ๏ธ Colors, spacing, typography, shadows โ all defined once and used everywhere. Change a token and watch the entire app update. It's like having a universal remote for your design! ๐๏ธ
๐ Impact: HIGH โ A solid token architecture makes theming, dark mode, and brand updates trivially easy across your entire app
๐ In this section: Token Format โข CSS/SCSS/JS Output โข Semantic Tokens โข Multi-Platform โข Versioning โข Theme Switching
// โ Design Token Structure// tokens/index.tsexport * from './colors';export * from './spacing';export * from './typography';export * from './shadows';export * from './borders';export * from './zIndex';// โ Token Format (Style Dictionary compatible)// tokens/colors.json{"color": {"primary": {"50": { "value": "#f0f9ff" },"100": { "value": "#e0f2fe" },"500": { "value": "#0ea5e9" },"600": { "value": "#0284c7" },"900": { "value": "#0c4a6e" }},"semantic": {"success": { "value": "{color.primary.500}" },"error": { "value": "#ef4444" }}}}// โ Token Transformers// Convert tokens to different formats// CSS Variablesconst cssVariables = {'--color-primary-500': '#0ea5e9','--spacing-4': '1rem','--font-size-base': '1rem',};// SCSS Variables$color-primary-500: #0ea5e9;$spacing-4: 1rem;// JavaScript/TypeScriptexport const tokens = {color: {primary: {500: '#0ea5e9',},},spacing: {4: '1rem',},};// โ Token Categoriesconst tokenCategories = {// Primitives (Raw values)primitives: {colors: ['#0ea5e9', '#ef4444'],spacing: ['4px', '8px', '16px'],},// Semantic (Meaningful names)semantic: {colors: {primary: 'color.primary.500',error: 'color.semantic.error',},spacing: {small: 'spacing.2',medium: 'spacing.4',large: 'spacing.8',},},// Component-specificcomponents: {button: {padding: 'spacing.2 spacing.4',borderRadius: 'borderRadius.md',backgroundColor: 'color.primary.500',},},};// โ Token Aliases (References)const tokenAliases = {// Alias to another token'color.text.primary': '{color.neutral.900}','color.text.secondary': '{color.neutral.600}','color.background': '{color.neutral.50}',// Computed values'spacing.button.padding.x': '{spacing.4}','spacing.button.padding.y': '{spacing.2}',};// โ Multi-platform Tokens// tokens/web.tsexport const webTokens = {colors: {primary: '#0ea5e9',},spacing: {sm: '0.5rem',md: '1rem',},};// tokens/ios.tsexport const iosTokens = {colors: {primary: UIColor(red: 0.055, green: 0.647, blue: 0.914, alpha: 1.0),},spacing: {sm: 8.0,md: 16.0,},};// tokens/android.xml<resources><color name="primary">#0ea5e9</color><dimen name="spacing_sm">8dp</dimen><dimen name="spacing_md">16dp</dimen></resources>// โ Token Versioning// tokens/v1/colors.json{"version": "1.0.0","tokens": { ... }}// tokens/v2/colors.json{"version": "2.0.0","tokens": { ... },"migrations": {"color.primary": "color.brand.primary"}}// โ Token Validationfunction validateTokens(tokens: TokenSet) {// Check required tokens existconst required = ['color.primary.500', 'spacing.4'];required.forEach(token => {if (!getTokenValue(tokens, token)) {throw new Error(`Missing required token: ${token}`);}});// Validate color formatsObject.values(tokens.color).forEach(color => {if (!isValidColor(color)) {throw new Error(`Invalid color: ${color}`);}});// Check token referencesvalidateTokenReferences(tokens);}// โ Token Generation from Design Files// Figma Plugin or Sketch Plugin// Extract tokens from design files automaticallyinterface DesignToken {name: string;value: string;type: 'color' | 'spacing' | 'typography';description?: string;}function extractTokensFromFigma(): DesignToken[] {// Extract colors, spacing, typography from Figmareturn [{ name: 'color.primary.500', value: '#0ea5e9', type: 'color' },{ name: 'spacing.4', value: '16px', type: 'spacing' },];}// โ Token Documentation// Auto-generate token documentationconst tokenDocs = {'color.primary.500': {value: '#0ea5e9',description: 'Primary brand color',usage: 'Use for primary actions and brand elements',examples: ['Button background', 'Link color'],},};// โ Theme Switching with Tokensconst lightTheme = {'color.background': '#ffffff','color.text': '#000000',};const darkTheme = {'color.background': '#000000','color.text': '#ffffff',};function applyTheme(theme: Theme) {Object.entries(theme).forEach(([token, value]) => {document.documentElement.style.setProperty(`--${token.replace('.', '-')}`,value);});}
โฟ 5. Accessibility in Design Systems
Accessibility baked into your design system means every component is born accessible! ๐ Color contrast, keyboard navigation, screen reader support, ARIA patterns โ handle it once at the system level and every app built with it gets a11y for free. Build for everyone! ๐
๐ด Impact: CRITICAL โ Accessible design systems ensure every product built on top of them is inclusive by default โ no excuses!
๐ In this section: Color Contrast โข ARIA Patterns โข Keyboard Navigation โข Focus Management โข Screen Readers โข a11y Testing
// โ Accessibility Requirements// 1. Color Contrast (WCAG AA minimum)const colorContrast = {// Text on background'text-on-primary': {foreground: '#ffffff',background: '#0ea5e9',ratio: 4.5, // WCAG AApasses: true,},// Large text (18pt+ or 14pt+ bold)'large-text-on-primary': {foreground: '#ffffff',background: '#0ea5e9',ratio: 3.0, // WCAG AA for large textpasses: true,},};// โ Accessible Button Componentinterface AccessibleButtonProps {children: React.ReactNode;'aria-label'?: string;'aria-busy'?: boolean;'aria-disabled'?: boolean;'aria-describedby'?: string;}export const AccessibleButton: React.FC<AccessibleButtonProps> = ({children,'aria-label': ariaLabel,'aria-busy': ariaBusy,'aria-disabled': ariaDisabled,'aria-describedby': ariaDescribedBy,...props}) => {return (<buttonaria-label={ariaLabel}aria-busy={ariaBusy}aria-disabled={ariaDisabled}aria-describedby={ariaDescribedBy}{...props}>{children}</button>);};// โ Focus Managementfunction useFocusManagement() {const ref = useRef<HTMLButtonElement>(null);const focus = () => {ref.current?.focus();};const blur = () => {ref.current?.blur();};return { ref, focus, blur };}// โ Keyboard Navigationfunction handleKeyDown(event: React.KeyboardEvent) {switch (event.key) {case 'Enter':case ' ':event.preventDefault();handleClick();break;case 'Escape':handleClose();break;case 'ArrowDown':event.preventDefault();focusNext();break;case 'ArrowUp':event.preventDefault();focusPrevious();break;}}// โ Screen Reader Supportfunction Announcement({ message }: { message: string }) {return (<divrole="status"aria-live="polite"aria-atomic="true"className="sr-only">{message}</div>);}// โ Accessible Form Componentsinterface AccessibleInputProps {label: string;error?: string;required?: boolean;helperText?: string;}export const AccessibleInput: React.FC<AccessibleInputProps> = ({label,error,required,helperText,id,...props}) => {const inputId = id || `input-${useId()}`;const errorId = `${inputId}-error`;const helperId = `${inputId}-helper`;return (<div><label htmlFor={inputId}>{label}{required && <span aria-label="required">*</span>}</label><inputid={inputId}aria-invalid={!!error}aria-describedby={error ? errorId : helperId}aria-required={required}{...props}/>{error && (<div id={errorId} role="alert" aria-live="polite">{error}</div>)}{helperText && !error && (<div id={helperId}>{helperText}</div>)}</div>);};// โ Accessible Modal/Dialogexport const AccessibleModal: React.FC<ModalProps> = ({isOpen,onClose,title,children,}) => {const titleId = useId();const descriptionId = useId();useEffect(() => {if (isOpen) {// Trap focus inside modalconst firstFocusable = modalRef.current?.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');firstFocusable?.focus();}}, [isOpen]);return (<divrole="dialog"aria-modal="true"aria-labelledby={titleId}aria-describedby={descriptionId}><h2 id={titleId}>{title}</h2><div id={descriptionId}>{children}</div><button onClick={onClose} aria-label="Close dialog">ร</button></div>);};// โ Skip Linksfunction SkipLinks() {return (<a href="#main-content" className="skip-link">Skip to main content</a>);}// โ Accessible Color Systemconst accessibleColors = {// Ensure sufficient contrasttext: {onLight: '#000000', // 21:1 contrastonDark: '#ffffff', // 21:1 contrastonPrimary: '#ffffff', // 4.5:1 contrast},// Don't rely on color alonestatus: {success: { color: '#10b981', icon: 'โ' },error: { color: '#ef4444', icon: 'โ' },warning: { color: '#f59e0b', icon: '!' },},};// โ Accessibility Testing// Using @axe-core/reactimport { axe, toHaveNoViolations } from 'jest-axe';expect.extend(toHaveNoViolations);test('Button has no accessibility violations', async () => {const { container } = render(<Button>Click me</Button>);const results = await axe(container);expect(results).toHaveNoViolations();});// โ ARIA Patternsconst ariaPatterns = {button: {role: 'button',keyboard: ['Enter', 'Space'],aria: ['aria-label', 'aria-pressed', 'aria-expanded'],},dialog: {role: 'dialog',keyboard: ['Escape'],aria: ['aria-modal', 'aria-labelledby', 'aria-describedby'],},menu: {role: 'menu',keyboard: ['Arrow keys', 'Enter', 'Escape'],aria: ['aria-orientation', 'aria-activedescendant'],},};// โ Accessibility Checklistconst accessibilityChecklist = {keyboard: ['All interactive elements are keyboard accessible','Focus order is logical','Focus indicators are visible','No keyboard traps',],screenReader: ['Semantic HTML used','ARIA labels provided where needed','Live regions for dynamic content','Form labels associated',],visual: ['Color contrast meets WCAG AA','Text is resizable','Content works without color','Focus indicators visible',],cognitive: ['Clear error messages','Instructions provided','Consistent navigation','Simple language used',],};
๐ท๏ธ 6. Versioning Component Libraries
Ship updates without breaking the world! ๐ Semantic versioning, changelogs, and migration guides keep your consumers happy and your library trustworthy. Learn the art of deprecation, changesets, and release automation โ because nobody wants a surprise breaking change on a Friday afternoon! ๐
๐ Impact: HIGH โ Proper versioning builds trust with consumers and prevents breaking changes from causing production fires
๐ In this section: SemVer โข Changelogs โข Migration Guides โข Changesets โข Deprecation Strategy โข Release Process
// โ Semantic Versioning (SemVer)// MAJOR.MINOR.PATCH// 1.0.0 โ 1.0.1 (Patch: Bug fixes)// 1.0.1 โ 1.1.0 (Minor: New features, backward compatible)// 1.1.0 โ 2.0.0 (Major: Breaking changes)// package.json{"name": "@design-system/components","version": "2.0.0","main": "dist/index.js","types": "dist/index.d.ts","exports": {".": {"import": "./dist/index.esm.js","require": "./dist/index.cjs.js","types": "./dist/index.d.ts"}}}// โ Versioning Strategyconst versioningStrategy = {major: {when: 'Breaking changes',examples: ['Removed props','Changed component API','Removed components','Changed token names'],migration: 'Required migration guide'},minor: {when: 'New features, backward compatible',examples: ['New components','New props','New variants','New tokens'],migration: 'Optional, recommended'},patch: {when: 'Bug fixes, no API changes',examples: ['Fixed styling bugs','Fixed accessibility issues','Performance improvements','Documentation updates'],migration: 'Not required'}};// โ CHANGELOG.md```markdown# ChangelogAll notable changes to this project will be documented in this file.## [2.0.0] - 2024-01-15### Breaking Changes- **Button**: Removed `size` prop, use `variant` instead- **Input**: Changed `onChange` signature- **Tokens**: Renamed `color.primary` to `color.brand.primary`### Migration GuideSee [MIGRATION.md](./MIGRATION.md#v2.0.0) for detailed migration instructions.## [1.2.0] - 2024-01-01### Added- New `Card` component- `Button` now supports `leftIcon` and `rightIcon` props- New color tokens: `color.success`, `color.warning`### Fixed- Fixed `Input` focus state styling- Fixed accessibility issue in `Modal` component## [1.1.0] - 2023-12-15### Added- `Button` component- `Input` component- Initial token system```// โ Migration Guide// MIGRATION.md```markdown# Migration Guide## Migrating from v1.x to v2.0### Button Component#### Before (v1.x)```tsx<Button size="md" variant="primary">Click me</Button>```#### After (v2.0)```tsx<Button variant="primary">Click me</Button>```The `size` prop has been removed. Use CSS to control size if needed.### Color Tokens#### Before (v1.x)```tsximport { colors } from '@design-system/tokens';colors.primary[500]```#### After (v2.0)```tsximport { colors } from '@design-system/tokens';colors.brand.primary[500]``````// โ Automated Versioning// Using semantic-release or changesets// .changeset/config.json{"$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json","changelog": "@changesets/cli/changelog","commit": false,"fixed": [],"linked": [],"access": "public","baseBranch": "main","updateInternalDependencies": "patch","ignore": []}// โ Changeset Workflow// 1. Create changeset// npx changeset// 2. Commit changeset// git add .changeset && git commit -m "Add changeset"// 3. Version packages// npx changeset version// 4. Publish// npx changeset publish// โ Deprecation Strategyfunction deprecated(component: string, version: string, replacement?: string) {console.warn(`${component} is deprecated since v${version}. ` +(replacement ? `Use ${replacement} instead.` : ''));}// Usageexport const OldButton = deprecated('OldButton', '2.0.0', 'Button')((props) => {deprecated('OldButton', '2.0.0', 'Button');return <Button {...props} />;});// โ TypeScript Breaking Changes// Use type aliases for gradual migrationexport type OldButtonProps = ButtonProps; // Deprecatedexport type ButtonProps = NewButtonProps; // New// โ Version Compatibility Matrixconst compatibilityMatrix = {'2.0.0': {react: '>=18.0.0','react-dom': '>=18.0.0',typescript: '>=5.0.0',},'1.2.0': {react: '>=17.0.0','react-dom': '>=17.0.0',typescript: '>=4.5.0',},};// โ Release Processconst releaseProcess = {1: 'Update version in package.json',2: 'Update CHANGELOG.md',3: 'Create migration guide if major version',4: 'Run tests',5: 'Build package',6: 'Tag release',7: 'Publish to npm',8: 'Create GitHub release',9: 'Announce in team channels',};// โ Pre-release Checklistconst preReleaseChecklist = ['All tests passing','Documentation updated','CHANGELOG.md updated','Migration guide created (if major)','Version number updated','Build succeeds','Type definitions generated','Examples work correctly','Accessibility tested','Browser compatibility tested',];