Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:49:21 +08:00
commit 29665c13f5
11 changed files with 5611 additions and 0 deletions

View File

@@ -0,0 +1,622 @@
# WCAG 2.1 AA Accessibility Checklist
Comprehensive checklist for ensuring your frontend meets WCAG 2.1 Level AA compliance.
## Perceivable
Information and user interface components must be presentable to users in ways they can perceive.
### 1.1 Text Alternatives
**1.1.1 Non-text Content (Level A)**
- [ ] All images have appropriate alt text
- [ ] Decorative images use empty alt (`alt=""`)
- [ ] Complex images have detailed descriptions
- [ ] Icons have text alternatives or aria-label
- [ ] Charts/graphs have text descriptions
- [ ] CAPTCHAs have alternative forms
```html
<!-- Good examples -->
<img src="logo.png" alt="Company Name">
<img src="decorative.png" alt="" role="presentation">
<button aria-label="Close dialog"><span aria-hidden="true">×</span></button>
```
### 1.2 Time-based Media
**1.2.1 Audio-only and Video-only (Level A)**
- [ ] Audio-only content has transcripts
- [ ] Video-only content has transcripts or audio description
**1.2.2 Captions (Level A)**
- [ ] All pre-recorded videos have captions
- [ ] Captions are synchronized and accurate
**1.2.3 Audio Description or Media Alternative (Level A)**
- [ ] Videos have audio descriptions or text alternative
**1.2.4 Captions (Live) (Level AA)**
- [ ] Live videos have captions
**1.2.5 Audio Description (Level AA)**
- [ ] All pre-recorded videos have audio descriptions
```html
<video controls>
<source src="video.mp4" type="video/mp4">
<track kind="captions" src="captions.vtt" srclang="en" label="English">
<track kind="descriptions" src="descriptions.vtt" srclang="en" label="English descriptions">
</video>
```
### 1.3 Adaptable
**1.3.1 Info and Relationships (Level A)**
- [ ] Semantic HTML used correctly (headings, lists, tables)
- [ ] Form labels properly associated with inputs
- [ ] Related form controls are grouped
- [ ] Visual presentation matches code structure
- [ ] ARIA roles used when needed
```html
<!-- Semantic structure -->
<main>
<h1>Main Heading</h1>
<section>
<h2>Section Heading</h2>
<p>Content</p>
</section>
</main>
<!-- Proper form labels -->
<label for="email">Email address</label>
<input type="email" id="email" name="email">
<!-- Grouped controls -->
<fieldset>
<legend>Contact preferences</legend>
<label><input type="checkbox" name="email"> Email</label>
<label><input type="checkbox" name="phone"> Phone</label>
</fieldset>
```
**1.3.2 Meaningful Sequence (Level A)**
- [ ] Reading order is logical
- [ ] Tab order follows visual flow
- [ ] CSS positioning doesn't disrupt reading order
**1.3.3 Sensory Characteristics (Level A)**
- [ ] Instructions don't rely solely on shape
- [ ] Instructions don't rely solely on size
- [ ] Instructions don't rely solely on location
- [ ] Instructions don't rely solely on sound
```html
<!-- ❌ Bad -->
<p>Click the blue button on the right</p>
<!-- ✅ Good -->
<p>Click the "Submit" button to continue</p>
```
**1.3.4 Orientation (Level AA)**
- [ ] Content works in portrait and landscape
- [ ] No orientation restrictions unless essential
**1.3.5 Identify Input Purpose (Level AA)**
- [ ] Input fields use autocomplete attribute when appropriate
```html
<input type="email" name="email" autocomplete="email">
<input type="tel" name="phone" autocomplete="tel">
<input type="text" name="address" autocomplete="street-address">
```
### 1.4 Distinguishable
**1.4.1 Use of Color (Level A)**
- [ ] Color not used as only visual means of conveying information
- [ ] Color not used as only way to distinguish interactive elements
- [ ] Links are distinguishable without color alone
```css
/* ✅ Good - underline + color */
a {
color: blue;
text-decoration: underline;
}
/* Or use icons, borders, etc. */
.error {
color: red;
border-left: 4px solid red;
padding-left: 12px;
}
.error::before {
content: "⚠ ";
}
```
**1.4.2 Audio Control (Level A)**
- [ ] Auto-playing audio can be paused
- [ ] Auto-playing audio stops after 3 seconds
- [ ] Volume controls available
**1.4.3 Contrast (Minimum) (Level AA)**
- [ ] Normal text: 4.5:1 contrast ratio
- [ ] Large text (18pt+): 3:1 contrast ratio
- [ ] UI components: 3:1 contrast ratio
- [ ] Graphical objects: 3:1 contrast ratio
```css
/* Check with contrast checkers */
.text {
color: #595959; /* 7:1 on white ✅ */
background: #FFFFFF;
}
.button {
color: #FFFFFF;
background: #0066FF; /* 4.6:1 ✅ */
border: 2px solid #0052CC; /* 3:1 ✅ */
}
```
**1.4.4 Resize Text (Level AA)**
- [ ] Text can be resized to 200% without loss of content
- [ ] No horizontal scrolling at 200% zoom
- [ ] Use relative units (rem, em)
```css
/* ✅ Good */
body {
font-size: 1rem; /* Respects user preferences */
}
h1 {
font-size: 2.5rem; /* Scales with body */
}
/* ❌ Avoid */
.text {
font-size: 14px; /* Fixed size */
}
```
**1.4.5 Images of Text (Level AA)**
- [ ] Text is text, not images
- [ ] Exception: logos, essential presentations
**1.4.10 Reflow (Level AA)**
- [ ] Content reflows at 320px viewport width
- [ ] No horizontal scrolling (except tables, diagrams)
- [ ] Responsive design implemented
```css
/* Mobile-first responsive */
.container {
width: 100%;
max-width: 1200px;
padding: 1rem;
}
@media (min-width: 768px) {
.container {
padding: 2rem;
}
}
```
**1.4.11 Non-text Contrast (Level AA)**
- [ ] UI components: 3:1 contrast against background
- [ ] Graphical objects: 3:1 contrast
- [ ] Focus indicators: 3:1 contrast
**1.4.12 Text Spacing (Level AA)**
- [ ] Content adapts to increased text spacing
- [ ] Line height: at least 1.5x font size
- [ ] Paragraph spacing: at least 2x font size
- [ ] Letter spacing: at least 0.12x font size
- [ ] Word spacing: at least 0.16x font size
```css
/* Ensure content doesn't break */
body {
line-height: 1.5;
}
p {
margin-bottom: 2em;
}
```
**1.4.13 Content on Hover or Focus (Level AA)**
- [ ] Additional content (tooltips, dropdowns) is dismissible
- [ ] Hoverable content stays visible when hovering over it
- [ ] Content remains visible until dismissed or no longer relevant
```css
/* Tooltip stays visible when hovering over it */
.tooltip:hover .tooltip-content,
.tooltip .tooltip-content:hover {
display: block;
}
```
## Operable
User interface components and navigation must be operable.
### 2.1 Keyboard Accessible
**2.1.1 Keyboard (Level A)**
- [ ] All functionality available via keyboard
- [ ] No keyboard traps
- [ ] Logical tab order
- [ ] Custom controls are keyboard accessible
```html
<!-- Custom button needs tabindex and keyboard handlers -->
<div role="button" tabindex="0"
onclick="handleClick()"
onkeydown="if(event.key==='Enter'||event.key===' ') handleClick()">
Click me
</div>
```
**2.1.2 No Keyboard Trap (Level A)**
- [ ] Focus can move away from all components
- [ ] Instructions provided if non-standard exit method
**2.1.4 Character Key Shortcuts (Level A)**
- [ ] Single-key shortcuts can be turned off
- [ ] Or remapped by user
- [ ] Or only active when component has focus
### 2.2 Enough Time
**2.2.1 Timing Adjustable (Level A)**
- [ ] Time limits can be turned off, adjusted, or extended
- [ ] User warned before time expires
- [ ] At least 20 seconds to extend
**2.2.2 Pause, Stop, Hide (Level A)**
- [ ] Moving content can be paused
- [ ] Auto-updating content can be paused/stopped
- [ ] Blinking content can be stopped
```html
<!-- Provide controls -->
<div class="carousel">
<button aria-label="Pause carousel"></button>
<button aria-label="Play carousel"></button>
</div>
```
### 2.3 Seizures and Physical Reactions
**2.3.1 Three Flashes or Below Threshold (Level A)**
- [ ] No content flashes more than 3 times per second
- [ ] Or flashes are below general flash/red flash thresholds
### 2.4 Navigable
**2.4.1 Bypass Blocks (Level A)**
- [ ] Skip navigation link provided
- [ ] Landmark regions defined
- [ ] Headings structure content
```html
<!-- Skip link (visually hidden until focused) -->
<a href="#main" class="skip-link">Skip to main content</a>
<header>
<nav aria-label="Main navigation">...</nav>
</header>
<main id="main">
<h1>Page Title</h1>
</main>
```
```css
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: #fff;
padding: 8px;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
```
**2.4.2 Page Titled (Level A)**
- [ ] Page has descriptive title
- [ ] Title identifies page content
```html
<title>Contact Us - Company Name</title>
```
**2.4.3 Focus Order (Level A)**
- [ ] Focus order is logical and intuitive
- [ ] Matches visual order
- [ ] No positive tabindex values
```css
/* ❌ Avoid */
.element { tabindex: 5; }
/* ✅ Use */
.element { tabindex: 0; } /* In natural order */
.element { tabindex: -1; } /* Programmatic focus only */
```
**2.4.4 Link Purpose (Level A)**
- [ ] Link text describes destination
- [ ] Context is clear
- [ ] Avoid "click here" or "read more"
```html
<!-- ❌ Bad -->
<a href="/report.pdf">Click here</a>
<!-- ✅ Good -->
<a href="/report.pdf">Download 2024 Annual Report (PDF, 2MB)</a>
```
**2.4.5 Multiple Ways (Level AA)**
- [ ] Multiple ways to find pages (menu, search, sitemap)
- [ ] Exception: pages that are steps in a process
**2.4.6 Headings and Labels (Level AA)**
- [ ] Headings describe content
- [ ] Labels describe purpose
- [ ] Headings and labels are clear
**2.4.7 Focus Visible (Level AA)**
- [ ] Keyboard focus indicator is visible
- [ ] Sufficient contrast (3:1)
- [ ] Clearly indicates focused element
```css
/* ✅ Strong focus indicator */
*:focus-visible {
outline: 2px solid #0066FF;
outline-offset: 2px;
}
/* Or custom focus styles */
button:focus-visible {
box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.5);
}
```
### 2.5 Input Modalities
**2.5.1 Pointer Gestures (Level A)**
- [ ] Complex gestures have single-pointer alternative
- [ ] Path-based gestures have simple alternative
**2.5.2 Pointer Cancellation (Level A)**
- [ ] Actions triggered on up-event (not down)
- [ ] Or can be aborted/undone
**2.5.3 Label in Name (Level A)**
- [ ] Visible label matches accessible name
- [ ] Accessible name starts with visible text
```html
<!-- ✅ Good - matches -->
<button aria-label="Submit form">Submit</button>
<!-- ❌ Bad - doesn't match -->
<button aria-label="Send">Submit</button>
```
**2.5.4 Motion Actuation (Level A)**
- [ ] Device motion triggers have UI alternative
- [ ] Can disable motion actuation
## Understandable
Information and user interface operation must be understandable.
### 3.1 Readable
**3.1.1 Language of Page (Level A)**
- [ ] Page language is identified
```html
<html lang="en">
```
**3.1.2 Language of Parts (Level AA)**
- [ ] Language changes are marked
```html
<p>The French phrase <span lang="fr">c'est la vie</span> means "that's life".</p>
```
### 3.2 Predictable
**3.2.1 On Focus (Level A)**
- [ ] Focusing an element doesn't trigger context change
- [ ] No automatic form submission on focus
**3.2.2 On Input (Level A)**
- [ ] Changing settings doesn't automatically cause context change
- [ ] User is warned of automatic changes
**3.2.3 Consistent Navigation (Level AA)**
- [ ] Navigation order is consistent across pages
- [ ] Repeated navigation in same order
**3.2.4 Consistent Identification (Level AA)**
- [ ] Components with same functionality are identified consistently
- [ ] Icons mean the same thing throughout
### 3.3 Input Assistance
**3.3.1 Error Identification (Level A)**
- [ ] Errors are identified in text
- [ ] Error is described to user
```html
<input type="email" aria-invalid="true" aria-describedby="email-error">
<span id="email-error" role="alert">Please enter a valid email address</span>
```
**3.3.2 Labels or Instructions (Level A)**
- [ ] Labels provided for input
- [ ] Instructions provided when needed
```html
<label for="password">
Password (must be at least 8 characters)
</label>
<input type="password" id="password" required minlength="8">
```
**3.3.3 Error Suggestion (Level AA)**
- [ ] Errors suggest how to fix
- [ ] Specific, actionable feedback
```html
<span role="alert">
Password must contain at least one uppercase letter,
one number, and be at least 8 characters long.
</span>
```
**3.3.4 Error Prevention (Level AA)**
- [ ] Legal/financial transactions are reversible
- [ ] Data is checked and confirmed before submission
- [ ] User can review and correct before submitting
```html
<!-- Confirmation step -->
<div role="region" aria-labelledby="review-heading">
<h2 id="review-heading">Review Your Order</h2>
<!-- Show all details -->
<button>Edit Order</button>
<button>Confirm Purchase</button>
</div>
```
## Robust
Content must be robust enough to be interpreted by a wide variety of user agents, including assistive technologies.
### 4.1 Compatible
**4.1.1 Parsing (Level A)** *[Obsolete in WCAG 2.2]*
- [ ] Valid HTML (no duplicate IDs, proper nesting)
**4.1.2 Name, Role, Value (Level A)**
- [ ] All UI components have accessible name
- [ ] Roles are appropriate
- [ ] States communicated to assistive tech
```html
<!-- Custom checkbox -->
<div role="checkbox"
aria-checked="false"
aria-labelledby="label-id"
tabindex="0">
</div>
<span id="label-id">Accept terms</span>
<!-- Button states -->
<button aria-pressed="false" aria-label="Mute">🔊</button>
<button aria-pressed="true" aria-label="Mute">🔇</button>
```
**4.1.3 Status Messages (Level AA)**
- [ ] Status messages can be perceived by assistive tech
- [ ] Use aria-live, role="status", role="alert"
```html
<!-- Success message -->
<div role="status" aria-live="polite">
Form submitted successfully!
</div>
<!-- Error message -->
<div role="alert" aria-live="assertive">
Error: Connection lost. Please try again.
</div>
<!-- Loading state -->
<div aria-live="polite" aria-busy="true">
Loading content...
</div>
```
## Testing Checklist
### Automated Testing
- [ ] Run axe DevTools
- [ ] Run Lighthouse accessibility audit
- [ ] Run WAVE browser extension
- [ ] HTML validator (W3C)
- [ ] Color contrast checker
### Manual Testing
- [ ] Keyboard-only navigation
- [ ] Screen reader testing (NVDA, JAWS, VoiceOver)
- [ ] Zoom to 200% (text resize)
- [ ] Test with browser zoom (page zoom)
- [ ] Test in high contrast mode
- [ ] Test with dark mode
- [ ] Test responsive breakpoints
### Screen Reader Testing
- [ ] NVDA (Windows, free)
- [ ] JAWS (Windows, paid)
- [ ] VoiceOver (Mac/iOS, built-in)
- [ ] TalkBack (Android, built-in)
- [ ] ORCA (Linux, free)
### Browser Testing
- [ ] Chrome + screen reader
- [ ] Firefox + screen reader
- [ ] Safari + VoiceOver
- [ ] Edge + screen reader
## Quick Reference
### Critical Items (Must Fix)
1. ✅ Images have alt text
2. ✅ Form inputs have labels
3. ✅ Sufficient color contrast (4.5:1 text, 3:1 UI)
4. ✅ Keyboard accessible (all functionality)
5. ✅ Focus indicators visible
6. ✅ No keyboard traps
7. ✅ Semantic HTML (headings, landmarks)
8. ✅ ARIA used correctly
9. ✅ Page has title
10. ✅ Language identified
### Tools
- [axe DevTools](https://www.deque.com/axe/devtools/)
- [Lighthouse](https://developers.google.com/web/tools/lighthouse)
- [WAVE](https://wave.webaim.org/)
- [Color Contrast Analyzer](https://www.tpgi.com/color-contrast-checker/)
- [NVDA Screen Reader](https://www.nvaccess.org/)
### Resources
- [WCAG 2.1 Quick Reference](https://www.w3.org/WAI/WCAG21/quickref/)
- [WebAIM](https://webaim.org/)
- [The A11Y Project](https://www.a11yproject.com/)
- [MDN Accessibility](https://developer.mozilla.org/en-US/docs/Web/Accessibility)
---
**"Accessibility is not a feature, it's a fundamental right."**

View File

@@ -0,0 +1,937 @@
# Component Library Architecture
Complete guide to building, organizing, and maintaining a scalable component library.
## Component Architecture Principles
### 1. Atomic Design
Organize components hierarchically from smallest to largest.
**Hierarchy:**
```
Atoms → Molecules → Organisms → Templates → Pages
```
**Example Structure:**
```
components/
├── atoms/
│ ├── Button/
│ ├── Input/
│ ├── Icon/
│ └── Label/
├── molecules/
│ ├── FormField/
│ ├── SearchBar/
│ └── Card/
├── organisms/
│ ├── Header/
│ ├── LoginForm/
│ └── ProductCard/
├── templates/
│ ├── DashboardLayout/
│ └── AuthLayout/
└── pages/
├── HomePage/
└── ProfilePage/
```
### 2. Component Types
**Presentational (Dumb) Components:**
- Focus on how things look
- No state management
- Receive data via props
- Highly reusable
```tsx
// Presentational Button
interface ButtonProps {
variant?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
children: React.ReactNode;
onClick?: () => void;
}
export const Button: React.FC<ButtonProps> = ({
variant = 'primary',
size = 'md',
children,
onClick,
}) => {
return (
<button
className={`btn btn--${variant} btn--${size}`}
onClick={onClick}
>
{children}
</button>
);
};
```
**Container (Smart) Components:**
- Focus on how things work
- Manage state
- Connect to data sources
- Use presentational components
```tsx
// Container Component
export const UserProfileContainer: React.FC = () => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUser().then(data => {
setUser(data);
setLoading(false);
});
}, []);
if (loading) return <LoadingSpinner />;
if (!user) return <ErrorMessage />;
return <UserProfile user={user} />;
};
```
### 3. Component Composition
**Build complex components from simple ones.**
```tsx
// Atoms
const Avatar: React.FC<AvatarProps> = ({ src, alt }) => (
<img className="avatar" src={src} alt={alt} />
);
const Badge: React.FC<BadgeProps> = ({ children }) => (
<span className="badge">{children}</span>
);
// Molecule (composed of atoms)
const UserBadge: React.FC<UserBadgeProps> = ({ user }) => (
<div className="user-badge">
<Avatar src={user.avatar} alt={user.name} />
<span className="user-badge__name">{user.name}</span>
<Badge>{user.role}</Badge>
</div>
);
// Organism (composed of molecules)
const UserList: React.FC<UserListProps> = ({ users }) => (
<ul className="user-list">
{users.map(user => (
<li key={user.id}>
<UserBadge user={user} />
</li>
))}
</ul>
);
```
## File Structure
### Standard Component Structure
```
Button/
├── Button.tsx # Main component
├── Button.test.tsx # Tests
├── Button.stories.tsx # Storybook stories
├── Button.module.css # Styles (CSS Modules)
├── Button.types.ts # TypeScript types
└── index.ts # Barrel export
```
### Example Files
**Button/Button.tsx**
```tsx
import React from 'react';
import styles from './Button.module.css';
import { ButtonProps } from './Button.types';
export const Button: React.FC<ButtonProps> = ({
variant = 'primary',
size = 'md',
disabled = false,
loading = false,
children,
onClick,
...props
}) => {
const className = [
styles.button,
styles[`button--${variant}`],
styles[`button--${size}`],
disabled && styles['button--disabled'],
loading && styles['button--loading'],
].filter(Boolean).join(' ');
return (
<button
className={className}
disabled={disabled || loading}
onClick={onClick}
aria-busy={loading}
{...props}
>
{loading && <span className={styles.spinner} />}
{children}
</button>
);
};
```
**Button/Button.types.ts**
```tsx
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
/** Visual style variant */
variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
/** Size variant */
size?: 'sm' | 'md' | 'lg';
/** Disabled state */
disabled?: boolean;
/** Loading state */
loading?: boolean;
/** Button content */
children: React.ReactNode;
/** Click handler */
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
}
```
**Button/Button.test.tsx**
```tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';
describe('Button', () => {
it('renders children correctly', () => {
render(<Button>Click me</Button>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
it('handles click events', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByText('Click me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('applies variant classes', () => {
render(<Button variant="secondary">Click me</Button>);
const button = screen.getByText('Click me');
expect(button).toHaveClass('button--secondary');
});
it('disables interaction when loading', () => {
const handleClick = jest.fn();
render(<Button loading onClick={handleClick}>Click me</Button>);
const button = screen.getByText('Click me');
expect(button).toBeDisabled();
expect(button).toHaveAttribute('aria-busy', 'true');
});
});
```
**Button/Button.stories.tsx**
```tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
title: 'Components/Button',
component: Button,
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'ghost', '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 Secondary: Story = {
args: {
variant: 'secondary',
children: 'Secondary Button',
},
};
export const Loading: Story = {
args: {
loading: true,
children: 'Loading...',
},
};
export const Disabled: Story = {
args: {
disabled: true,
children: 'Disabled Button',
},
};
```
**Button/index.ts**
```tsx
export { Button } from './Button';
export type { ButtonProps } from './Button.types';
```
## Common Components
### 1. Button Component
```tsx
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
loading?: boolean;
icon?: React.ReactNode;
iconPosition?: 'left' | 'right';
children: React.ReactNode;
onClick?: () => void;
}
```
### 2. Input Component
```tsx
interface InputProps {
type?: 'text' | 'email' | 'password' | 'number' | 'tel';
label?: string;
placeholder?: string;
value?: string;
error?: string;
helpText?: string;
required?: boolean;
disabled?: boolean;
onChange?: (value: string) => void;
}
export const Input: React.FC<InputProps> = ({
type = 'text',
label,
placeholder,
value,
error,
helpText,
required,
disabled,
onChange,
}) => {
const id = useId();
const errorId = `${id}-error`;
const helpId = `${id}-help`;
return (
<div className="input-wrapper">
{label && (
<label htmlFor={id} className="input-label">
{label}
{required && <span aria-label="required">*</span>}
</label>
)}
<input
id={id}
type={type}
className={`input ${error ? 'input--error' : ''}`}
placeholder={placeholder}
value={value}
disabled={disabled}
required={required}
aria-invalid={!!error}
aria-describedby={error ? errorId : helpText ? helpId : undefined}
onChange={(e) => onChange?.(e.target.value)}
/>
{error && (
<span id={errorId} className="input-error" role="alert">
{error}
</span>
)}
{helpText && !error && (
<span id={helpId} className="input-help">
{helpText}
</span>
)}
</div>
);
};
```
### 3. Card Component
```tsx
interface CardProps {
variant?: 'default' | 'outlined' | 'elevated';
padding?: 'none' | 'sm' | 'md' | 'lg';
clickable?: boolean;
children: React.ReactNode;
onClick?: () => void;
}
export const Card: React.FC<CardProps> = ({
variant = 'default',
padding = 'md',
clickable = false,
children,
onClick,
}) => {
const Component = clickable ? 'button' : 'div';
return (
<Component
className={`card card--${variant} card--padding-${padding}`}
onClick={onClick}
role={clickable ? 'button' : undefined}
tabIndex={clickable ? 0 : undefined}
>
{children}
</Component>
);
};
```
### 4. Modal Component
```tsx
interface ModalProps {
open: boolean;
title?: string;
size?: 'sm' | 'md' | 'lg' | 'full';
children: React.ReactNode;
onClose: () => void;
}
export const Modal: React.FC<ModalProps> = ({
open,
title,
size = 'md',
children,
onClose,
}) => {
useEffect(() => {
if (open) {
// Trap focus in modal
document.body.style.overflow = 'hidden';
return () => {
document.body.style.overflow = '';
};
}
}, [open]);
if (!open) return null;
return createPortal(
<div className="modal-overlay" onClick={onClose}>
<div
className={`modal modal--${size}`}
onClick={(e) => e.stopPropagation()}
role="dialog"
aria-modal="true"
aria-labelledby={title ? 'modal-title' : undefined}
>
{title && (
<div className="modal__header">
<h2 id="modal-title">{title}</h2>
<button
className="modal__close"
onClick={onClose}
aria-label="Close dialog"
>
×
</button>
</div>
)}
<div className="modal__body">{children}</div>
</div>
</div>,
document.body
);
};
```
### 5. Dropdown Component
```tsx
interface DropdownProps {
trigger: React.ReactNode;
children: React.ReactNode;
align?: 'left' | 'right';
}
export const Dropdown: React.FC<DropdownProps> = ({
trigger,
children,
align = 'left',
}) => {
const [open, setOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node)
) {
setOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
return (
<div className="dropdown" ref={dropdownRef}>
<button
className="dropdown__trigger"
onClick={() => setOpen(!open)}
aria-expanded={open}
aria-haspopup="true"
>
{trigger}
</button>
{open && (
<div className={`dropdown__menu dropdown__menu--${align}`} role="menu">
{children}
</div>
)}
</div>
);
};
```
## Component Patterns
### 1. Compound Components
**Allow components to work together while sharing implicit state.**
```tsx
// Context for shared state
const AccordionContext = createContext<{
activeIndex: number | null;
setActiveIndex: (index: number | null) => void;
} | null>(null);
// Parent component
export const Accordion: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [activeIndex, setActiveIndex] = useState<number | null>(null);
return (
<AccordionContext.Provider value={{ activeIndex, setActiveIndex }}>
<div className="accordion">{children}</div>
</AccordionContext.Provider>
);
};
// Child component
export const AccordionItem: React.FC<{
index: number;
title: string;
children: React.ReactNode;
}> = ({ index, title, children }) => {
const context = useContext(AccordionContext);
if (!context) throw new Error('AccordionItem must be used within Accordion');
const { activeIndex, setActiveIndex } = context;
const isOpen = activeIndex === index;
return (
<div className="accordion-item">
<button
className="accordion-item__header"
onClick={() => setActiveIndex(isOpen ? null : index)}
aria-expanded={isOpen}
>
{title}
</button>
{isOpen && <div className="accordion-item__content">{children}</div>}
</div>
);
};
// Usage
<Accordion>
<AccordionItem index={0} title="Section 1">Content 1</AccordionItem>
<AccordionItem index={1} title="Section 2">Content 2</AccordionItem>
</Accordion>
```
### 2. Render Props
**Pass rendering logic as a prop.**
```tsx
interface DataFetcherProps<T> {
url: string;
children: (data: {
data: T | null;
loading: boolean;
error: Error | null;
}) => React.ReactNode;
}
export function DataFetcher<T>({ url, children }: DataFetcherProps<T>) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, [url]);
return <>{children({ data, loading, error })}</>;
}
// Usage
<DataFetcher<User> url="/api/user">
{({ data, loading, error }) => {
if (loading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
if (!data) return null;
return <UserProfile user={data} />;
}}
</DataFetcher>
```
### 3. Custom Hooks Pattern
**Extract reusable logic into custom hooks.**
```tsx
// useToggle hook
export function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => setValue(v => !v), []);
const setTrue = useCallback(() => setValue(true), []);
const setFalse = useCallback(() => setValue(false), []);
return { value, toggle, setTrue, setFalse };
}
// Usage in component
export const Modal: React.FC<ModalProps> = ({ children }) => {
const { value: isOpen, setTrue: open, setFalse: close } = useToggle();
return (
<>
<button onClick={open}>Open Modal</button>
{isOpen && (
<div className="modal">
{children}
<button onClick={close}>Close</button>
</div>
)}
</>
);
};
```
## Styling Strategies
### 1. CSS Modules
```css
/* Button.module.css */
.button {
display: inline-flex;
align-items: center;
padding: var(--space-3) var(--space-4);
border-radius: var(--radius-md);
}
.button--primary {
background: var(--color-primary);
color: var(--color-white);
}
.button--secondary {
background: transparent;
color: var(--color-primary);
border: 1px solid var(--color-primary);
}
```
```tsx
import styles from './Button.module.css';
export const Button = ({ variant }) => (
<button className={`${styles.button} ${styles[`button--${variant}`]}`}>
Click me
</button>
);
```
### 2. CSS-in-JS (Styled Components)
```tsx
import styled from 'styled-components';
const StyledButton = styled.button<{ variant: string }>`
display: inline-flex;
align-items: center;
padding: ${({ theme }) => theme.space[3]} ${({ theme }) => theme.space[4]};
background: ${({ theme, variant }) =>
variant === 'primary' ? theme.colors.primary : 'transparent'};
color: ${({ theme, variant }) =>
variant === 'primary' ? theme.colors.white : theme.colors.primary};
`;
export const Button = ({ variant, children }) => (
<StyledButton variant={variant}>{children}</StyledButton>
);
```
### 3. Tailwind CSS
```tsx
import clsx from 'clsx';
export const Button: React.FC<ButtonProps> = ({ variant, size, children }) => {
return (
<button
className={clsx(
'inline-flex items-center rounded-md font-medium',
{
'bg-blue-600 text-white hover:bg-blue-700': variant === 'primary',
'bg-transparent text-blue-600 border border-blue-600': variant === 'secondary',
},
{
'px-3 py-2 text-sm': size === 'sm',
'px-4 py-2 text-base': size === 'md',
'px-6 py-3 text-lg': size === 'lg',
}
)}
>
{children}
</button>
);
};
```
## Documentation
### Component Documentation Template
```tsx
/**
* Button component for triggering actions and navigation.
*
* @example
* ```tsx
* <Button variant="primary" onClick={handleClick}>
* Click me
* </Button>
* ```
*
* @see {@link https://design-system.example.com/button | Design System Docs}
*/
export const Button: React.FC<ButtonProps> = ({ ... }) => {
// Implementation
};
```
### Storybook Documentation
```tsx
import type { Meta } 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.
## Usage
\`\`\`tsx
import { Button } from '@/components/Button';
<Button variant="primary">Click me</Button>
\`\`\`
## Accessibility
- Keyboard accessible
- Screen reader friendly
- WCAG 2.1 AA compliant
`,
},
},
},
};
```
## Testing Strategies
### Unit Tests
```tsx
describe('Button', () => {
it('renders with correct variant', () => {
render(<Button variant="primary">Test</Button>);
expect(screen.getByRole('button')).toHaveClass('button--primary');
});
it('calls onClick when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Test</Button>);
fireEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
});
```
### Accessibility Tests
```tsx
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
it('has no accessibility violations', async () => {
const { container } = render(<Button>Test</Button>);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
```
### Visual Regression Tests
```tsx
// Using Chromatic or Percy
it('matches snapshot', () => {
const { container } = render(<Button variant="primary">Test</Button>);
expect(container).toMatchSnapshot();
});
```
## Performance Optimization
### Code Splitting
```tsx
// Lazy load heavy components
const HeavyComponent = lazy(() => import('./HeavyComponent'));
export const App = () => (
<Suspense fallback={<LoadingSpinner />}>
<HeavyComponent />
</Suspense>
);
```
### Memoization
```tsx
// Memoize expensive components
export const ExpensiveComponent = memo(({ data }) => {
// Expensive rendering logic
return <div>{processData(data)}</div>;
});
// Memoize callbacks
const handleClick = useCallback(() => {
// Handle click
}, [dependencies]);
// Memoize values
const expensiveValue = useMemo(() => {
return computeExpensiveValue(data);
}, [data]);
```
## Version Control
### Semantic Versioning
```
MAJOR.MINOR.PATCH
1.0.0 → Initial release
1.1.0 → New feature (backwards compatible)
1.1.1 → Bug fix (backwards compatible)
2.0.0 → Breaking change
```
### Changelog
```markdown
# Changelog
## [2.0.0] - 2024-01-15
### Breaking Changes
- Removed `type` prop from Button (use `variant` instead)
### Added
- New `loading` state for Button
- Icon support in Button component
### Fixed
- Button focus indicator contrast ratio
## [1.1.0] - 2024-01-01
### Added
- New Input component
- Card component variants
```
## Resources
- [React Component Patterns](https://kentcdodds.com/blog/compound-components-with-react-hooks)
- [Design Systems Handbook](https://www.designbetter.co/design-systems-handbook)
- [Atomic Design](https://bradfrost.com/blog/post/atomic-web-design/)
- [Storybook Best Practices](https://storybook.js.org/docs/react/writing-docs/introduction)
---
**"Good components are reusable, accessible, and well-documented."**

View File

@@ -0,0 +1,608 @@
# Design Tokens Reference
Complete guide to implementing and using design tokens in your design system.
## What Are Design Tokens?
Design tokens are the visual design atoms of your design system — specifically, they are named entities that store visual design attributes. They're used in place of hard-coded values to maintain a scalable and consistent visual system.
## Benefits
**Consistency**
- Single source of truth for design values
- Ensures brand consistency across platforms
- Easy to maintain and update
**Scalability**
- Change values in one place, update everywhere
- Support multiple themes (light/dark)
- Platform-agnostic (web, iOS, Android)
**Developer-Designer Communication**
- Shared vocabulary between teams
- Design decisions are codified
- Reduces ambiguity
## Token Categories
### 1. Color Tokens
```css
:root {
/* Brand Colors */
--color-primary: #0066FF;
--color-primary-hover: #0052CC;
--color-primary-active: #003D99;
--color-primary-subtle: #E6F0FF;
--color-secondary: #6B7280;
--color-secondary-hover: #4B5563;
/* Semantic Colors */
--color-success: #10B981;
--color-warning: #F59E0B;
--color-error: #EF4444;
--color-info: #3B82F6;
/* Neutral Palette */
--color-white: #FFFFFF;
--color-black: #000000;
--color-gray-50: #F9FAFB;
--color-gray-100: #F3F4F6;
/* ... through gray-900 */
/* Surface Colors */
--color-surface: var(--color-white);
--color-background: var(--color-white);
/* Text Colors */
--color-text: var(--color-gray-900);
--color-text-secondary: var(--color-gray-600);
--color-text-tertiary: var(--color-gray-400);
/* Border Colors */
--color-border: var(--color-gray-200);
--color-border-hover: var(--color-gray-300);
/* Focus */
--color-focus: var(--color-primary);
}
```
**Naming Convention:**
```
--color-{category}-{variant}-{state}
Examples:
--color-primary (base brand color)
--color-primary-hover (interactive state)
--color-success-subtle (background usage)
--color-text-secondary (content hierarchy)
```
### 2. Typography Tokens
```css
:root {
/* Font Families */
--font-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--font-heading: var(--font-base);
--font-mono: "SF Mono", Monaco, "Cascadia Code", monospace;
/* Font Sizes - Fluid Typography */
--text-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
--text-sm: clamp(0.875rem, 0.825rem + 0.25vw, 1rem);
--text-base: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
--text-lg: clamp(1.125rem, 1.05rem + 0.375vw, 1.25rem);
--text-xl: clamp(1.25rem, 1.15rem + 0.5vw, 1.5rem);
--text-2xl: clamp(1.5rem, 1.35rem + 0.75vw, 1.875rem);
--text-3xl: clamp(1.875rem, 1.65rem + 1.125vw, 2.25rem);
--text-4xl: clamp(2.25rem, 1.95rem + 1.5vw, 3rem);
--text-5xl: clamp(3rem, 2.55rem + 2.25vw, 3.75rem);
/* Font Weights */
--font-light: 300;
--font-normal: 400;
--font-medium: 500;
--font-semibold: 600;
--font-bold: 700;
/* Line Heights */
--leading-tight: 1.25;
--leading-normal: 1.5;
--leading-relaxed: 1.625;
--leading-loose: 2;
/* Letter Spacing */
--tracking-tight: -0.025em;
--tracking-normal: 0;
--tracking-wide: 0.025em;
}
```
**Usage:**
```css
.heading-1 {
font-family: var(--font-heading);
font-size: var(--text-4xl);
font-weight: var(--font-bold);
line-height: var(--leading-tight);
letter-spacing: var(--tracking-tight);
}
.body-text {
font-family: var(--font-base);
font-size: var(--text-base);
font-weight: var(--font-normal);
line-height: var(--leading-normal);
}
```
### 3. Spacing Tokens
```css
:root {
/* Spacing Scale (4px base) */
--space-0: 0;
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-3: 0.75rem; /* 12px */
--space-4: 1rem; /* 16px */
--space-5: 1.25rem; /* 20px */
--space-6: 1.5rem; /* 24px */
--space-8: 2rem; /* 32px */
--space-10: 2.5rem; /* 40px */
--space-12: 3rem; /* 48px */
--space-16: 4rem; /* 64px */
--space-20: 5rem; /* 80px */
--space-24: 6rem; /* 96px */
}
```
**Usage:**
```css
.card {
padding: var(--space-4);
margin-bottom: var(--space-6);
gap: var(--space-3);
}
.section {
padding-block: var(--space-12);
}
```
### 4. Shadow Tokens
```css
:root {
--shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
--shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
/* Special Shadows */
--shadow-inner: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06);
--shadow-focus: 0 0 0 3px rgba(0, 102, 255, 0.5);
}
```
**Usage:**
```css
.card {
box-shadow: var(--shadow-md);
}
.card:hover {
box-shadow: var(--shadow-lg);
}
button:focus-visible {
box-shadow: var(--shadow-focus);
}
```
### 5. Border Tokens
```css
:root {
/* Border Radius */
--radius-sm: 0.125rem; /* 2px */
--radius-md: 0.375rem; /* 6px */
--radius-lg: 0.5rem; /* 8px */
--radius-xl: 0.75rem; /* 12px */
--radius-2xl: 1rem; /* 16px */
--radius-full: 9999px; /* Pills/circles */
/* Border Widths */
--border-1: 1px;
--border-2: 2px;
--border-4: 4px;
}
```
**Usage:**
```css
.button {
border-radius: var(--radius-md);
border: var(--border-1) solid var(--color-border);
}
.avatar {
border-radius: var(--radius-full);
}
```
### 6. Z-Index Tokens
```css
:root {
/* Layer Management */
--z-dropdown: 1000;
--z-sticky: 1020;
--z-fixed: 1030;
--z-modal-backdrop: 1040;
--z-modal: 1050;
--z-popover: 1060;
--z-tooltip: 1070;
}
```
**Usage:**
```css
.modal-backdrop {
z-index: var(--z-modal-backdrop);
}
.modal {
z-index: var(--z-modal);
}
```
### 7. Animation/Transition Tokens
```css
:root {
/* Duration */
--duration-instant: 0ms;
--duration-fast: 150ms;
--duration-base: 250ms;
--duration-slow: 350ms;
--duration-slower: 500ms;
/* Easing */
--ease-linear: linear;
--ease-in: cubic-bezier(0.4, 0, 1, 1);
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
```
**Usage:**
```css
.button {
transition: all var(--duration-base) var(--ease-out);
}
.modal {
animation: slideIn var(--duration-slow) var(--ease-out);
}
```
## Semantic vs Reference Tokens
### Reference Tokens (Raw Values)
```css
:root {
--blue-500: #0066FF;
--gray-100: #F3F4F6;
--spacing-4: 1rem;
}
```
### Semantic Tokens (Meaningful Names)
```css
:root {
--color-primary: var(--blue-500);
--color-surface: var(--gray-100);
--button-padding: var(--spacing-4);
}
```
**Always use semantic tokens in components:**
```css
/* ❌ Don't */
.button {
background: var(--blue-500);
padding: var(--spacing-4);
}
/* ✅ Do */
.button {
background: var(--color-primary);
padding: var(--button-padding);
}
```
## Theming with Tokens
### Light/Dark Mode
```css
:root {
/* Light theme (default) */
--color-surface: #FFFFFF;
--color-text: #111827;
--color-border: #E5E7EB;
}
@media (prefers-color-scheme: dark) {
:root {
--color-surface: #1F2937;
--color-text: #F9FAFB;
--color-border: #4B5563;
}
}
/* Or class-based theming */
[data-theme="dark"] {
--color-surface: #1F2937;
--color-text: #F9FAFB;
--color-border: #4B5563;
}
```
### Brand Theming
```css
:root {
--color-primary: var(--brand-blue);
}
[data-brand="tech"] {
--brand-blue: #0066FF;
}
[data-brand="health"] {
--brand-blue: #10B981;
}
[data-brand="finance"] {
--brand-blue: #6366F1;
}
```
## Component-Specific Tokens
```css
:root {
/* Button */
--button-padding-sm: var(--space-2) var(--space-3);
--button-padding-md: var(--space-3) var(--space-4);
--button-padding-lg: var(--space-4) var(--space-6);
--button-radius: var(--radius-md);
--button-font-weight: var(--font-medium);
/* Input */
--input-height-sm: 36px;
--input-height-md: 44px;
--input-height-lg: 52px;
--input-border-color: var(--color-border);
--input-focus-color: var(--color-primary);
/* Card */
--card-padding: var(--space-6);
--card-radius: var(--radius-lg);
--card-shadow: var(--shadow-md);
}
```
## Platform-Specific Tokens
### CSS Custom Properties (Web)
```css
:root {
--color-primary: #0066FF;
}
.button {
background: var(--color-primary);
}
```
### JSON (React Native, iOS, Android)
```json
{
"color": {
"primary": "#0066FF",
"surface": "#FFFFFF",
"text": "#111827"
},
"spacing": {
"4": 16,
"6": 24,
"8": 32
}
}
```
### JavaScript/TypeScript
```typescript
export const tokens = {
color: {
primary: '#0066FF',
surface: '#FFFFFF',
text: '#111827',
},
spacing: {
4: '1rem',
6: '1.5rem',
8: '2rem',
},
} as const;
// Usage
const Button = styled.button`
background: ${tokens.color.primary};
padding: ${tokens.spacing.4};
`;
```
## Best Practices
### Naming Conventions
**Do:**
- Use descriptive, semantic names
- Follow consistent patterns
- Group related tokens
```css
/* ✅ Good */
--color-primary
--color-primary-hover
--color-primary-subtle
--button-padding-md
--shadow-card
```
**Don't:**
- Use arbitrary or cryptic names
- Mix naming conventions
- Use values in names
```css
/* ❌ Bad */
--blue
--primary-color-hover-state
--padding16px
--btn-pad
```
### Organization
Group tokens by category:
```
tokens/
├── colors.css
├── typography.css
├── spacing.css
├── shadows.css
├── borders.css
├── zindex.css
└── index.css (imports all)
```
### Documentation
Document token usage:
```css
/**
* Primary brand color
* Used for: primary buttons, links, focus states
* Contrast ratio: 4.6:1 (WCAG AA compliant)
*/
--color-primary: #0066FF;
```
### Accessibility
Ensure proper contrast:
```css
:root {
--color-primary: #0066FF;
--color-text-on-primary: #FFFFFF;
/* Contrast ratio: 4.5:1 ✅ */
}
```
### Performance
Use CSS variables efficiently:
```css
/* ✅ Define once, use everywhere */
:root {
--color-primary: #0066FF;
}
/* ❌ Don't redefine in every selector */
.button { --color-primary: #0066FF; }
.link { --color-primary: #0066FF; }
```
## Migration Strategy
### Step 1: Audit Current Values
```bash
# Find all color values
grep -r "#[0-9A-Fa-f]\{6\}" src/
# Find all spacing values
grep -r "padding:\|margin:" src/
```
### Step 2: Create Token Definitions
```css
:root {
/* Extract unique values */
--color-primary: #0066FF;
--space-4: 1rem;
}
```
### Step 3: Replace Hard-Coded Values
```css
/* Before */
.button {
background: #0066FF;
padding: 1rem;
}
/* After */
.button {
background: var(--color-primary);
padding: var(--space-4);
}
```
### Step 4: Test & Validate
- Visual regression testing
- Accessibility testing
- Cross-browser testing
- Dark mode verification
## Tools
**Design Token Tools:**
- Style Dictionary (transforms tokens to multiple formats)
- Theo (Salesforce's token transformer)
- Design Tokens CLI
**Browser DevTools:**
- Chrome: Inspect computed custom properties
- Firefox: CSS Variables viewer
**Example Script:**
```javascript
// Extract all CSS custom properties
const properties = Array.from(document.styleSheets)
.flatMap(sheet => Array.from(sheet.cssRules))
.filter(rule => rule.style)
.flatMap(rule => Array.from(rule.style))
.filter(prop => prop.startsWith('--'));
console.log([...new Set(properties)]);
```
## Resources
- [Design Tokens W3C Spec](https://design-tokens.github.io/community-group/format/)
- [Style Dictionary](https://amzn.github.io/style-dictionary/)
- [Design Tokens in Figma](https://www.figma.com/community/plugin/888356646278934516/Design-Tokens)
---
**"Design tokens are the translation layer between design decisions and code."**

View File

@@ -0,0 +1,839 @@
# Responsive Design Patterns
Modern responsive design patterns and techniques for creating flexible, accessible layouts that work across all devices.
## Mobile-First Approach
Start with mobile design and enhance for larger screens.
### Why Mobile-First?
**Benefits:**
- Forces focus on essential content
- Better performance (smaller base CSS)
- Progressive enhancement mindset
- Easier to add features than remove them
**Basic Pattern:**
```css
/* Base (mobile) styles - no media query */
.container {
width: 100%;
padding: 1rem;
}
/* Tablet and up */
@media (min-width: 768px) {
.container {
padding: 2rem;
}
}
/* Desktop and up */
@media (min-width: 1024px) {
.container {
max-width: 1200px;
margin: 0 auto;
padding: 3rem;
}
}
```
## Breakpoints
### Standard Breakpoints
```css
:root {
/* Mobile first - these are MIN widths */
--breakpoint-sm: 640px; /* Small tablets, large phones */
--breakpoint-md: 768px; /* Tablets */
--breakpoint-lg: 1024px; /* Laptops, desktops */
--breakpoint-xl: 1280px; /* Large desktops */
--breakpoint-2xl: 1536px; /* Extra large screens */
}
/* Usage */
@media (min-width: 768px) {
/* Tablet and up */
}
@media (min-width: 1024px) {
/* Desktop and up */
}
```
### Custom Breakpoints
```css
/* Content-based breakpoints */
@media (min-width: 400px) {
/* When content needs it, not arbitrary device size */
}
/* Prefer rem-based breakpoints for accessibility */
@media (min-width: 48rem) { /* 768px at 16px base */
/* Scales with user's font size preferences */
}
```
### Container Queries (Modern)
```css
/* Respond to container size, not viewport */
.card-container {
container-type: inline-size;
container-name: card;
}
@container card (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 200px 1fr;
}
}
```
## Responsive Typography
### Fluid Typography
```css
/* Scales smoothly between min and max */
h1 {
font-size: clamp(2rem, 5vw + 1rem, 4rem);
/* Min: 2rem (32px) */
/* Preferred: 5vw + 1rem */
/* Max: 4rem (64px) */
}
/* More examples */
.text-sm {
font-size: clamp(0.875rem, 0.85rem + 0.125vw, 1rem);
}
.text-base {
font-size: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
}
.text-lg {
font-size: clamp(1.125rem, 1.05rem + 0.375vw, 1.25rem);
}
```
### Responsive Line Height
```css
body {
/* Tighter on mobile, looser on desktop */
line-height: 1.5;
}
@media (min-width: 768px) {
body {
line-height: 1.6;
}
}
```
## Layout Patterns
### 1. Stack Layout
**Everything stacks vertically on mobile, side-by-side on larger screens.**
```css
.stack {
display: flex;
flex-direction: column;
gap: 1rem;
}
@media (min-width: 768px) {
.stack {
flex-direction: row;
}
}
```
### 2. Sidebar Layout
**Sidebar stacks on mobile, side-by-side on desktop.**
```css
.sidebar-layout {
display: grid;
gap: 2rem;
}
@media (min-width: 768px) {
.sidebar-layout {
grid-template-columns: 250px 1fr;
}
}
/* Flexible sidebar */
@media (min-width: 768px) {
.sidebar-layout--flexible {
grid-template-columns: minmax(200px, 300px) 1fr;
}
}
```
### 3. Grid Layout
**Responsive column count.**
```css
.grid {
display: grid;
gap: 1rem;
/* Mobile: 1 column */
grid-template-columns: 1fr;
}
@media (min-width: 640px) {
.grid {
/* Tablet: 2 columns */
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 1024px) {
.grid {
/* Desktop: 3-4 columns */
grid-template-columns: repeat(4, 1fr);
}
}
/* Auto-responsive grid (no media queries!) */
.grid-auto {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
/* Creates as many columns as fit, minimum 250px each */
}
```
### 4. Holy Grail Layout
**Classic three-column layout that adapts to mobile.**
```css
.holy-grail {
display: grid;
gap: 1rem;
min-height: 100vh;
/* Mobile: stack everything */
grid-template:
"header" auto
"main" 1fr
"sidebar1" auto
"sidebar2" auto
"footer" auto
/ 1fr;
}
@media (min-width: 768px) {
.holy-grail {
/* Desktop: traditional layout */
grid-template:
"header header header" auto
"sidebar1 main sidebar2" 1fr
"footer footer footer" auto
/ 200px 1fr 200px;
}
}
.header { grid-area: header; }
.sidebar-1 { grid-area: sidebar1; }
.main { grid-area: main; }
.sidebar-2 { grid-area: sidebar2; }
.footer { grid-area: footer; }
```
### 5. Card Layout
**Responsive cards that adapt their internal layout.**
```css
.card {
display: grid;
gap: 1rem;
/* Mobile: stack image and content */
grid-template:
"image" auto
"content" 1fr
/ 1fr;
}
@media (min-width: 640px) {
.card {
/* Tablet+: side-by-side */
grid-template:
"image content" 1fr
/ 200px 1fr;
}
}
.card__image { grid-area: image; }
.card__content { grid-area: content; }
```
### 6. Switcher Pattern
**Switch between horizontal and vertical based on available space.**
```css
.switcher {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.switcher > * {
/* Grow to fill, but switch to vertical when < 400px */
flex-grow: 1;
flex-basis: calc((400px - 100%) * 999);
/* Clever calc that breaks at threshold */
}
```
### 7. Pancake Stack
**Header, main, footer layout that adapts height.**
```css
.pancake {
display: grid;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
.header { /* auto height */ }
.main { /* fills available space */ }
.footer { /* auto height */ }
```
## Responsive Images
### 1. Flexible Images
```css
img {
max-width: 100%;
height: auto;
display: block;
}
```
### 2. Art Direction (Different Images per Breakpoint)
```html
<picture>
<source media="(min-width: 1024px)" srcset="large.jpg">
<source media="(min-width: 768px)" srcset="medium.jpg">
<img src="small.jpg" alt="Description">
</picture>
```
### 3. Resolution Switching (Same Image, Different Sizes)
```html
<img
srcset="
small.jpg 400w,
medium.jpg 800w,
large.jpg 1200w
"
sizes="
(min-width: 1024px) 800px,
(min-width: 768px) 600px,
100vw
"
src="medium.jpg"
alt="Description"
>
```
### 4. Background Images
```css
.hero {
background-image: url('small.jpg');
background-size: cover;
background-position: center;
}
@media (min-width: 768px) {
.hero {
background-image: url('medium.jpg');
}
}
@media (min-width: 1024px) {
.hero {
background-image: url('large.jpg');
}
}
@media (min-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) {
.hero {
background-image: url('large@2x.jpg');
}
}
```
### 5. Aspect Ratio
```css
/* Modern aspect ratio */
.image-container {
aspect-ratio: 16 / 9;
overflow: hidden;
}
.image-container img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* Fallback for older browsers */
.image-container-fallback {
position: relative;
padding-bottom: 56.25%; /* 16:9 aspect ratio */
}
.image-container-fallback img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
```
## Responsive Navigation
### 1. Mobile Menu (Hamburger)
```html
<nav class="nav">
<div class="nav__brand">Logo</div>
<button class="nav__toggle" aria-expanded="false" aria-controls="nav-menu">
<span class="sr-only">Menu</span>
<span class="hamburger"></span>
</button>
<ul class="nav__menu" id="nav-menu">
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
```
```css
.nav {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
}
/* Mobile: hidden menu */
.nav__menu {
display: none;
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
flex-direction: column;
padding: 1rem;
}
.nav__menu[aria-expanded="true"] {
display: flex;
}
.nav__toggle {
display: block;
}
/* Desktop: visible menu */
@media (min-width: 768px) {
.nav__menu {
display: flex;
position: static;
flex-direction: row;
gap: 2rem;
}
.nav__toggle {
display: none;
}
}
```
### 2. Priority+ Navigation
**Show important items, collapse others into "More" menu.**
```css
.priority-nav {
display: flex;
gap: 1rem;
}
.priority-nav__item {
flex-shrink: 0; /* Don't shrink */
}
.priority-nav__more {
margin-left: auto; /* Push to end */
}
/* Hide items that don't fit */
@media (max-width: 768px) {
.priority-nav__item:nth-child(n+4) {
display: none; /* Hide items 4+ */
}
}
```
## Responsive Tables
### 1. Horizontal Scroll
```css
.table-container {
overflow-x: auto;
-webkit-overflow-scrolling: touch; /* Smooth scrolling on iOS */
}
table {
min-width: 600px;
width: 100%;
}
```
### 2. Stacked Table (Card View)
```html
<table class="responsive-table">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<tr>
<td data-label="Name">John Doe</td>
<td data-label="Email">john@example.com</td>
<td data-label="Role">Developer</td>
</tr>
</tbody>
</table>
```
```css
/* Desktop: normal table */
@media (min-width: 768px) {
.responsive-table {
display: table;
}
}
/* Mobile: stacked cards */
@media (max-width: 767px) {
.responsive-table,
.responsive-table thead,
.responsive-table tbody,
.responsive-table tr,
.responsive-table th,
.responsive-table td {
display: block;
}
.responsive-table thead {
display: none; /* Hide table header */
}
.responsive-table tr {
margin-bottom: 1rem;
border: 1px solid #ddd;
border-radius: 0.5rem;
padding: 1rem;
}
.responsive-table td {
display: flex;
justify-content: space-between;
padding: 0.5rem 0;
border-bottom: 1px solid #eee;
}
.responsive-table td:last-child {
border-bottom: none;
}
.responsive-table td::before {
content: attr(data-label);
font-weight: bold;
margin-right: 1rem;
}
}
```
## Responsive Forms
### 1. Single Column to Multi-Column
```css
.form {
display: grid;
gap: 1rem;
/* Mobile: single column */
grid-template-columns: 1fr;
}
@media (min-width: 768px) {
.form {
/* Desktop: two columns */
grid-template-columns: repeat(2, 1fr);
}
.form__field--full {
/* Some fields span both columns */
grid-column: 1 / -1;
}
}
```
### 2. Touch-Friendly Inputs
```css
input,
button,
select,
textarea {
/* Minimum 44x44px touch target */
min-height: 44px;
padding: 0.75rem 1rem;
font-size: 1rem; /* Prevents zoom on iOS */
}
@media (min-width: 768px) {
input,
button,
select,
textarea {
/* Can be smaller on desktop */
min-height: 40px;
font-size: 0.875rem;
}
}
```
## Responsive Utilities
### Show/Hide at Breakpoints
```css
/* Hide on mobile */
.hidden-mobile {
display: none;
}
@media (min-width: 768px) {
.hidden-mobile {
display: block;
}
}
/* Show only on mobile */
.visible-mobile {
display: block;
}
@media (min-width: 768px) {
.visible-mobile {
display: none;
}
}
/* Hide on desktop */
@media (min-width: 1024px) {
.hidden-desktop {
display: none;
}
}
```
### Responsive Spacing
```css
.section {
/* Mobile: smaller padding */
padding: 2rem 1rem;
}
@media (min-width: 768px) {
.section {
/* Tablet: medium padding */
padding: 4rem 2rem;
}
}
@media (min-width: 1024px) {
.section {
/* Desktop: larger padding */
padding: 6rem 3rem;
}
}
/* Or use fluid spacing */
.section-fluid {
padding: clamp(2rem, 5vw, 6rem) clamp(1rem, 3vw, 3rem);
}
```
## Testing Responsive Design
### Browser DevTools
```javascript
// Common viewport sizes to test
const viewports = [
{ width: 375, height: 667, name: 'iPhone SE' },
{ width: 390, height: 844, name: 'iPhone 12 Pro' },
{ width: 428, height: 926, name: 'iPhone 14 Pro Max' },
{ width: 768, height: 1024, name: 'iPad' },
{ width: 1024, height: 768, name: 'iPad Landscape' },
{ width: 1280, height: 720, name: 'Desktop' },
{ width: 1920, height: 1080, name: 'Full HD' },
];
```
### Responsive Testing Checklist
- [ ] Test all breakpoints
- [ ] Test between breakpoints (awkward sizes)
- [ ] Test portrait and landscape
- [ ] Test zoom levels (100%, 200%, 400%)
- [ ] Test with real devices when possible
- [ ] Test touch interactions on mobile
- [ ] Test with different font sizes
- [ ] Test with slow network (images, fonts)
## Performance Considerations
### Lazy Loading
```html
<!-- Lazy load images below the fold -->
<img src="image.jpg" loading="lazy" alt="Description">
<!-- Eager load above-the-fold images -->
<img src="hero.jpg" loading="eager" alt="Hero image">
```
### Conditional Loading
```javascript
// Load component only on larger screens
if (window.matchMedia('(min-width: 768px)').matches) {
import('./DesktopComponent.js').then(module => {
// Initialize desktop component
});
}
```
### Font Loading
```css
@font-face {
font-family: 'CustomFont';
src: url('font.woff2') format('woff2');
font-display: swap; /* Show fallback while loading */
}
```
## Modern CSS Features
### 1. CSS Grid Auto-Fill
```css
.grid {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
/* Automatically creates columns, minimum 250px */
}
```
### 2. Flexbox Gap
```css
.flex-container {
display: flex;
flex-wrap: wrap;
gap: 1rem; /* No more margin hacks! */
}
```
### 3. Container Queries
```css
.card {
container-type: inline-size;
}
@container (min-width: 400px) {
.card__title {
font-size: 1.5rem;
}
}
```
### 4. Aspect Ratio
```css
.video-container {
aspect-ratio: 16 / 9;
}
```
### 5. Logical Properties
```css
/* Better for RTL/LTR support */
.element {
margin-block-start: 1rem; /* margin-top */
margin-block-end: 1rem; /* margin-bottom */
margin-inline-start: 1rem; /* margin-left in LTR, margin-right in RTL */
margin-inline-end: 1rem; /* margin-right in LTR, margin-left in RTL */
}
```
## Resources
- [MDN: Responsive Design](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Responsive_Design)
- [This is Responsive](https://bradfrost.github.io/this-is-responsive/)
- [Responsive Design Patterns](https://responsivedesign.is/patterns/)
- [CSS-Tricks: Complete Guide to Grid](https://css-tricks.com/snippets/css/complete-guide-grid/)
- [CSS-Tricks: Complete Guide to Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/)
---
**"The best design is the one that works everywhere, for everyone."**