Design Systems: Scalable UI Architecture
Medium•
Design systems are the foundation of scalable product development - invest early for long-term ROI.
Quick Navigation: Atomic Design Methodology • Design Tokens • Component API Design • Theming • Documentation & Tooling
Quick Decision Guide
Design System = Tokens + Components + Patterns + Docs
Tokens: { colors, spacing, typography } Primitives: Button, Input, Text (atomic) Components: Card, Modal, Dropdown (composed) Patterns: Forms, dashboards, layouts Tools: Storybook, Figma, versioning
Result: 3-5x faster feature development, consistent UX, easier maintenance.
Atomic Design Methodology
Hierarchy
Atoms → Button, Input, Text, Icon
Molecules → SearchBar (Input + Button), FormField (Label + Input)
Organisms → Header (Logo + Nav + SearchBar), Card
Templates → PageLayout, DashboardTemplate
Pages → Homepage, Dashboard, SettingsExample: Building Up
// 1. Atoms
const Button = ({ children, ...props }) => (
<button className="btn" {...props}>{children}</button>
);
const Input = ({ ...props }) => (
<input className="input" {...props} />
);
// 2. Molecules
const SearchBar = ({ onSearch }) => (
<div className="search-bar">
<Input placeholder="Search..." />
<Button onClick={onSearch}>Search</Button>
</div>
);
// 3. Organisms
const Header = () => (
<header>
<Logo />
<Navigation />
<SearchBar />
<UserMenu />
</header>
);
// 4. Templates
const DashboardTemplate = ({ header, sidebar, content }) => (
<div className="dashboard">
{header}
<div className="layout">
{sidebar}
{content}
</div>
</div>
);Design Tokens
Token Structure
// tokens/colors.js
export const colors = {
// Brand colors
brand: {
primary: '#0070f3',
secondary: '#7928ca',
},
// Semantic colors
semantic: {
success: '#0070f3',
error: '#e00',
warning: '#f5a623',
info: '#0070f3',
},
// Neutral palette
neutral: {
white: '#fff',
black: '#000',
gray: {
50: '#fafafa',
100: '#f5f5f5',
200: '#e5e5e5',
// ... through 900
},
},
// Text colors
text: {
primary: '#000',
secondary: '#666',
tertiary: '#999',
inverse: '#fff',
},
};
// tokens/spacing.js
export const spacing = {
0: '0',
1: '0.25rem', // 4px
2: '0.5rem', // 8px
3: '0.75rem', // 12px
4: '1rem', // 16px
5: '1.25rem', // 20px
6: '1.5rem', // 24px
8: '2rem', // 32px
10: '2.5rem', // 40px
12: '3rem', // 48px
16: '4rem', // 64px
};
// tokens/typography.js
export const typography = {
fontFamily: {
sans: "'Inter', -apple-system, sans-serif",
mono: "'Fira Code', monospace",
},
fontSize: {
xs: '0.75rem', // 12px
sm: '0.875rem', // 14px
base: '1rem', // 16px
lg: '1.125rem', // 18px
xl: '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,
},
};Using Tokens
import { colors, spacing, typography } from '@/tokens';
const Button = styled.button`
background: ${colors.brand.primary};
padding: ${spacing[3]} ${spacing[4]};
font-family: ${typography.fontFamily.sans};
font-weight: ${typography.fontWeight.semibold};
`;Component API Design
Consistent Props
// All components follow similar patterns
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
loading?: boolean;
children: React.ReactNode;
onClick?: () => void;
}
interface InputProps {
variant?: 'default' | 'filled' | 'outline';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
error?: boolean;
helperText?: string;
// ...
}Composition Over Configuration
// ❌ Bad: Too many props
<Modal
title="Confirm"
content="Are you sure?"
primaryButton="Yes"
secondaryButton="No"
onPrimary={handleYes}
onSecondary={handleNo}
/>
// ✅ Good: Composable
<Modal>
<Modal.Header>
<Modal.Title>Confirm</Modal.Title>
</Modal.Header>
<Modal.Body>
Are you sure?
</Modal.Body>
<Modal.Footer>
<Button onClick={handleNo}>No</Button>
<Button variant="primary" onClick={handleYes}>Yes</Button>
</Modal.Footer>
</Modal>Theming
Theme Provider
// ThemeProvider.jsx
import { createContext, useContext } from 'react';
const ThemeContext = createContext();
export const ThemeProvider = ({ theme, children }) => (
<ThemeContext.Provider value={theme}>
{children}
</ThemeContext.Provider>
);
export const useTheme = () => useContext(ThemeContext);
// Usage
const lightTheme = {
colors: { primary: '#0070f3', background: '#fff' }
};
const darkTheme = {
colors: { primary: '#0070f3', background: '#000' }
};
<ThemeProvider theme={isDark ? darkTheme : lightTheme}>
<App />
</ThemeProvider>Theme-Aware Components
const Button = styled.button`
background: ${({ theme }) => theme.colors.primary};
color: ${({ theme }) => theme.colors.text};
`;
// Or with hooks
function Button() {
const theme = useTheme();
return (
<button style={{
background: theme.colors.primary,
color: theme.colors.text
}}>
Click
</button>
);
}Documentation & Tooling
Storybook Setup
// .storybook/main.js
module.exports = {
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-a11y',
],
};
// Button.stories.jsx
export default {
title: 'Primitives/Button',
component: Button,
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'outline'],
},
},
};
export const Primary = {
args: {
variant: 'primary',
children: 'Button',
},
};Versioning
{
"name": "@company/design-system",
"version": "2.3.0",
"peerDependencies": {
"react": "^18.0.0"
}
}Usage in Products
npm install @company/design-system@^2.0.0import { Button, Input, Card } from '@company/design-system';
function App() {
return (
<Card>
<Input placeholder="Email" />
<Button variant="primary">Submit</Button>
</Card>
);
}