PatternsDesign Systems
Responsive Design Tokens
Build responsive systems with design tokens and breakpoints
Responsive Design Tokens
Responsive design tokens adapt your UI to different screen sizes, creating consistent experiences across devices.
Breakpoint Tokens
// tokens/breakpoints.ts
export const breakpoints = {
xs: '320px', // Mobile portrait
sm: '640px', // Mobile landscape
md: '768px', // Tablet portrait
lg: '1024px', // Tablet landscape / Desktop
xl: '1280px', // Desktop
'2xl': '1536px', // Large desktop
};
// Numeric values for JS
export const breakpointValues = {
xs: 320,
sm: 640,
md: 768,
lg: 1024,
xl: 1280,
'2xl': 1536,
};Responsive Spacing
// tokens/spacing.ts
export const spacing = {
// Base scale
0: '0',
1: '0.25rem', // 4px
2: '0.5rem', // 8px
3: '0.75rem', // 12px
4: '1rem', // 16px
6: '1.5rem', // 24px
8: '2rem', // 32px
12: '3rem', // 48px
16: '4rem', // 64px
// Responsive spacing
responsive: {
xs: {
gutter: '1rem', // 16px
section: '2rem', // 32px
},
sm: {
gutter: '1.5rem', // 24px
section: '3rem', // 48px
},
md: {
gutter: '2rem', // 32px
section: '4rem', // 64px
},
lg: {
gutter: '2.5rem', // 40px
section: '5rem', // 80px
},
xl: {
gutter: '3rem', // 48px
section: '6rem', // 96px
},
},
};Responsive Typography
// tokens/typography.ts
export const typography = {
fontSize: {
// Mobile-first base sizes
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
'5xl': '3rem', // 48px
},
// Responsive heading sizes
headings: {
h1: {
mobile: '1.875rem', // 30px
tablet: '2.25rem', // 36px
desktop: '3rem', // 48px
},
h2: {
mobile: '1.5rem', // 24px
tablet: '1.875rem', // 30px
desktop: '2.25rem', // 36px
},
h3: {
mobile: '1.25rem', // 20px
tablet: '1.5rem', // 24px
desktop: '1.875rem', // 30px
},
},
};Container Tokens
// tokens/containers.ts
export const containers = {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px',
// Content widths
prose: '65ch', // Optimal reading width
narrow: '768px',
wide: '1280px',
full: '100%',
};CSS Implementation
CSS Variables + Media Queries
/* tokens.css */
:root {
/* Mobile-first (320px+) */
--spacing-gutter: 1rem;
--spacing-section: 2rem;
--font-size-h1: 1.875rem;
}
/* Tablet (768px+) */
@media (min-width: 768px) {
:root {
--spacing-gutter: 2rem;
--spacing-section: 4rem;
--font-size-h1: 2.25rem;
}
}
/* Desktop (1024px+) */
@media (min-width: 1024px) {
:root {
--spacing-gutter: 2.5rem;
--spacing-section: 5rem;
--font-size-h1: 3rem;
}
}
/* Components use variables */
.section {
padding: var(--spacing-section) var(--spacing-gutter);
}
.h1 {
font-size: var(--font-size-h1);
}Tailwind Configuration
// tailwind.config.js
import { breakpoints, spacing, typography } from './tokens';
export default {
theme: {
screens: breakpoints,
extend: {
spacing: spacing,
fontSize: {
...typography.fontSize,
// Responsive font sizes
'h1-mobile': typography.headings.h1.mobile,
'h1-desktop': typography.headings.h1.desktop,
},
maxWidth: {
'prose': '65ch',
},
},
},
};// Usage with Tailwind responsive utilities
<h1 className="text-h1-mobile lg:text-h1-desktop">
Responsive Heading
</h1>
<div className="px-4 md:px-8 lg:px-12">
Responsive padding
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
Responsive grid
</div>React Hook for Breakpoints
// hooks/useBreakpoint.ts
import { useState, useEffect } from 'react';
import { breakpointValues } from '@/tokens/breakpoints';
type Breakpoint = keyof typeof breakpointValues;
export function useBreakpoint() {
const [breakpoint, setBreakpoint] = useState<Breakpoint>('xs');
useEffect(() => {
const handleResize = () => {
const width = window.innerWidth;
if (width >= breakpointValues['2xl']) {
setBreakpoint('2xl');
} else if (width >= breakpointValues.xl) {
setBreakpoint('xl');
} else if (width >= breakpointValues.lg) {
setBreakpoint('lg');
} else if (width >= breakpointValues.md) {
setBreakpoint('md');
} else if (width >= breakpointValues.sm) {
setBreakpoint('sm');
} else {
setBreakpoint('xs');
}
};
handleResize(); // Initial
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return {
breakpoint,
isMobile: breakpoint === 'xs' || breakpoint === 'sm',
isTablet: breakpoint === 'md',
isDesktop: breakpoint === 'lg' || breakpoint === 'xl' || breakpoint === '2xl',
};
}
// Usage
function Navigation() {
const { isMobile } = useBreakpoint();
return isMobile ? <MobileNav /> : <DesktopNav />;
}Media Query Hook
// hooks/useMediaQuery.ts
import { useState, useEffect } from 'react';
export function useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState(false);
useEffect(() => {
const mediaQuery = window.matchMedia(query);
setMatches(mediaQuery.matches);
const handler = (event: MediaQueryListEvent) => {
setMatches(event.matches);
};
mediaQuery.addEventListener('change', handler);
return () => mediaQuery.removeEventListener('change', handler);
}, [query]);
return matches;
}
// Usage
function Component() {
const isLargeScreen = useMediaQuery('(min-width: 1024px)');
const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');
return (
<div>
{isLargeScreen && <LargeScreenFeature />}
{!prefersReducedMotion && <Animation />}
</div>
);
}Responsive Component Props
// Type-safe responsive props
type ResponsiveValue<T> = T | {
xs?: T;
sm?: T;
md?: T;
lg?: T;
xl?: T;
'2xl'?: T;
};
interface BoxProps {
padding?: ResponsiveValue<number>;
gap?: ResponsiveValue<number>;
}
function Box({ padding, gap }: BoxProps) {
// Handle responsive values
const getPadding = () => {
if (typeof padding === 'number') {
return `${padding * 4}px`;
}
// Generate responsive classes
return undefined;
};
return <div className="box" style={{ padding: getPadding() }} />;
}
// Usage
<Box padding={4} /> // 16px all screens
<Box padding={{ xs: 2, md: 4, lg: 6 }} /> // ResponsiveFluid Typography
/* Scales between viewport widths */
.h1 {
font-size: clamp(
1.875rem, /* Min: 30px */
1.875rem + 1.125 * (100vw - 20rem) / 44, /* Scale */
3rem /* Max: 48px */
);
}
/* Simpler version */
.h1 {
font-size: clamp(1.875rem, 5vw, 3rem);
}// Generate fluid values
function fluidSize(minPx: number, maxPx: number, minVw: number = 320, maxVw: number = 1280) {
const minRem = minPx / 16;
const maxRem = maxPx / 16;
return `clamp(${minRem}rem, ${minRem}rem + ${maxRem - minRem} * (100vw - ${minVw}px) / ${maxVw - minVw}, ${maxRem}rem)`;
}
// Usage
const fluidH1 = fluidSize(30, 48); // 30px to 48pxContainer Queries (Modern)
/* Container query tokens */
.card-container {
container-type: inline-size;
container-name: card;
}
/* Card adapts to container, not viewport */
@container card (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 1fr 2fr;
}
}
@container card (min-width: 600px) {
.card {
grid-template-columns: 1fr 1fr 1fr;
}
}Testing Responsive Designs
// Test at different breakpoints
import { render } from '@testing-library/react';
describe('ResponsiveComponent', () => {
it('renders mobile layout', () => {
global.innerWidth = 375;
global.dispatchEvent(new Event('resize'));
render(<ResponsiveComponent />);
expect(screen.getByTestId('mobile-layout')).toBeInTheDocument();
});
it('renders desktop layout', () => {
global.innerWidth = 1024;
global.dispatchEvent(new Event('resize'));
render(<ResponsiveComponent />);
expect(screen.getByTestId('desktop-layout')).toBeInTheDocument();
});
});Best Practices
- Mobile-First: Start with mobile, enhance for larger screens
- Fluid Between Breakpoints: Use clamp() for smooth scaling
- Token-Based: All breakpoints from tokens
- Semantic Breakpoints: Name by usage, not device
- Test All Sizes: Don't forget tablet landscape
- Container Queries: Use when appropriate (modern browsers)
- Performance: Avoid excessive media queries
- Touch Targets: 44px minimum on mobile
Common Pitfalls
❌ Desktop-first: Hard to scale down
✅ Mobile-first approach
❌ Too many breakpoints: Hard to maintain
✅ 3-5 key breakpoints
❌ Device-specific: iPhone, iPad names
✅ Generic: mobile, tablet, desktop
❌ Hardcoded values: 768px everywhere
✅ Reference breakpoint tokens
Responsive design tokens create consistent, maintainable responsive experiences across your entire application!