Design System Patterns
Master design system architecture to create consistent, maintainable, and scalable UI foundations across web and mobile applications.
When to Use This Skill
-
Creating design tokens for colors, typography, spacing, and shadows
-
Implementing light/dark theme switching with CSS custom properties
-
Building multi-brand theming systems
-
Architecting component libraries with consistent APIs
-
Establishing design-to-code workflows with Figma tokens
-
Creating semantic token hierarchies (primitive, semantic, component)
-
Setting up design system documentation and guidelines
Core Capabilities
- Design Tokens
-
Primitive tokens (raw values: colors, sizes, fonts)
-
Semantic tokens (contextual meaning: text-primary, surface-elevated)
-
Component tokens (specific usage: button-bg, card-border)
-
Token naming conventions and organization
-
Multi-platform token generation (CSS, iOS, Android)
- Theming Infrastructure
-
CSS custom properties architecture
-
Theme context providers in React
-
Dynamic theme switching
-
System preference detection (prefers-color-scheme)
-
Persistent theme storage
-
Reduced motion and high contrast modes
- Component Architecture
-
Compound component patterns
-
Polymorphic components (as prop)
-
Variant and size systems
-
Slot-based composition
-
Headless UI patterns
-
Style props and responsive variants
- Token Pipeline
-
Figma to code synchronization
-
Style Dictionary configuration
-
Token transformation and formatting
-
CI/CD integration for token updates
Quick Start
// Design tokens with CSS custom properties const tokens = { colors: { // Primitive tokens gray: { 50: "#fafafa", 100: "#f5f5f5", 900: "#171717", }, blue: { 500: "#3b82f6", 600: "#2563eb", }, }, // Semantic tokens (reference primitives) semantic: { light: { "text-primary": "var(--color-gray-900)", "text-secondary": "var(--color-gray-600)", "surface-default": "var(--color-white)", "surface-elevated": "var(--color-gray-50)", "border-default": "var(--color-gray-200)", "interactive-primary": "var(--color-blue-500)", }, dark: { "text-primary": "var(--color-gray-50)", "text-secondary": "var(--color-gray-400)", "surface-default": "var(--color-gray-900)", "surface-elevated": "var(--color-gray-800)", "border-default": "var(--color-gray-700)", "interactive-primary": "var(--color-blue-400)", }, }, };
Key Patterns
Pattern 1: Token Hierarchy
/* Layer 1: Primitive tokens (raw values) */ :root { --color-blue-500: #3b82f6; --color-blue-600: #2563eb; --color-gray-50: #fafafa; --color-gray-900: #171717;
--space-1: 0.25rem; --space-2: 0.5rem; --space-4: 1rem;
--font-size-sm: 0.875rem; --font-size-base: 1rem; --font-size-lg: 1.125rem;
--radius-sm: 0.25rem; --radius-md: 0.5rem; --radius-lg: 1rem; }
/* Layer 2: Semantic tokens (meaning) */ :root { --text-primary: var(--color-gray-900); --text-secondary: var(--color-gray-600); --surface-default: white; --interactive-primary: var(--color-blue-500); --interactive-primary-hover: var(--color-blue-600); }
/* Layer 3: Component tokens (specific usage) */ :root { --button-bg: var(--interactive-primary); --button-bg-hover: var(--interactive-primary-hover); --button-text: white; --button-radius: var(--radius-md); --button-padding-x: var(--space-4); --button-padding-y: var(--space-2); }
Pattern 2: Theme Switching with React
import { createContext, useContext, useEffect, useState } from "react";
type Theme = "light" | "dark" | "system";
interface ThemeContextValue { theme: Theme; resolvedTheme: "light" | "dark"; setTheme: (theme: Theme) => void; }
const ThemeContext = createContext<ThemeContextValue | null>(null);
export function ThemeProvider({ children }: { children: React.ReactNode }) { const [theme, setTheme] = useState<Theme>(() => { if (typeof window !== "undefined") { return (localStorage.getItem("theme") as Theme) || "system"; } return "system"; });
const [resolvedTheme, setResolvedTheme] = useState<"light" | "dark">("light");
useEffect(() => { const root = document.documentElement;
const applyTheme = (isDark: boolean) => {
root.classList.remove("light", "dark");
root.classList.add(isDark ? "dark" : "light");
setResolvedTheme(isDark ? "dark" : "light");
};
if (theme === "system") {
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
applyTheme(mediaQuery.matches);
const handler = (e: MediaQueryListEvent) => applyTheme(e.matches);
mediaQuery.addEventListener("change", handler);
return () => mediaQuery.removeEventListener("change", handler);
} else {
applyTheme(theme === "dark");
}
}, [theme]);
useEffect(() => { localStorage.setItem("theme", theme); }, [theme]);
return ( <ThemeContext.Provider value={{ theme, resolvedTheme, setTheme }}> {children} </ThemeContext.Provider> ); }
export const useTheme = () => { const context = useContext(ThemeContext); if (!context) throw new Error("useTheme must be used within ThemeProvider"); return context; };
Pattern 3: Variant System with CVA
import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils";
const buttonVariants = cva( // Base styles "inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", }, size: { sm: "h-9 px-3 text-sm", md: "h-10 px-4 text-sm", lg: "h-11 px-8 text-base", icon: "h-10 w-10", }, }, defaultVariants: { variant: "default", size: "md", }, }, );
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> { asChild?: boolean; }
export function Button({ className, variant, size, ...props }: ButtonProps) { return ( <button className={cn(buttonVariants({ variant, size, className }))} {...props} /> ); }
Pattern 4: Style Dictionary Configuration
// style-dictionary.config.js module.exports = { source: ["tokens/**/*.json"], platforms: { css: { transformGroup: "css", buildPath: "dist/css/", files: [ { destination: "variables.css", format: "css/variables", options: { outputReferences: true, // Preserve token references }, }, ], }, scss: { transformGroup: "scss", buildPath: "dist/scss/", files: [ { destination: "_variables.scss", format: "scss/variables", }, ], }, ios: { transformGroup: "ios-swift", buildPath: "dist/ios/", files: [ { destination: "DesignTokens.swift", format: "ios-swift/class.swift", className: "DesignTokens", }, ], }, android: { transformGroup: "android", buildPath: "dist/android/", files: [ { destination: "colors.xml", format: "android/colors", filter: { attributes: { category: "color" } }, }, ], }, }, };
Best Practices
-
Name Tokens by Purpose: Use semantic names (text-primary) not visual descriptions (dark-gray)
-
Maintain Token Hierarchy: Primitives > Semantic > Component tokens
-
Document Token Usage: Include usage guidelines with token definitions
-
Version Tokens: Treat token changes as API changes with semver
-
Test Theme Combinations: Verify all themes work with all components
-
Automate Token Pipeline: CI/CD for Figma-to-code synchronization
-
Provide Migration Paths: Deprecate tokens gradually with clear alternatives
Common Issues
-
Token Sprawl: Too many tokens without clear hierarchy
-
Inconsistent Naming: Mixed conventions (camelCase vs kebab-case)
-
Missing Dark Mode: Tokens that don't adapt to theme changes
-
Hardcoded Values: Using raw values instead of tokens
-
Circular References: Tokens referencing each other in loops
-
Platform Gaps: Tokens missing for some platforms (web but not mobile)