Over half of web traffic is mobile. If your design doesn't work on a phone, it doesn't work. Responsive design isn't a feature, it's the default way to build.
Mobile-First Approach
Start with the smallest screen, then progressively enhance for larger screens. This forces you to prioritize content and interactions because there's no room for clutter on a 375px-wide screen.
/* Mobile base styles — the default */
.component {
padding: 16px;
font-size: 16px;
flex-direction: column;
}
/* Tablet enhancement — add when space allows */
@media (min-width: 768px) {
.component {
padding: 24px;
font-size: 18px;
flex-direction: row;
}
}
/* Desktop enhancement — more space, more features */
@media (min-width: 1024px) {
.component {
padding: 32px;
max-width: 1200px;
margin: 0 auto;
}
}
Why Mobile-First (Not Desktop-First)?
| Mobile-First | Desktop-First |
|---|
| Forces content prioritization | Tempts you to cram everything in |
| Base CSS is lighter (fewer overrides) | Base CSS is heavier (must undo for mobile) |
Uses min-width media queries (progressive) | Uses max-width media queries (regressive) |
| Easier to add features for larger screens | Harder to remove features for smaller screens |
| Better performance on weaker mobile devices | Mobile loads desktop CSS it doesn't need |
Common mistake: Designing a polished desktop layout, then trying to "shrink it" for mobile. This always results in a cramped, unusable mobile experience. Start mobile, then expand.
Standard Breakpoints
/* Common breakpoint system */
--breakpoint-sm: 576px; /* Large phones (landscape) */
--breakpoint-md: 768px; /* Tablets (portrait) */
--breakpoint-lg: 1024px; /* Small laptops / tablets (landscape) */
--breakpoint-xl: 1280px; /* Desktops */
--breakpoint-2xl: 1536px; /* Large monitors */
Breakpoint Decision Guide
| Breakpoint | What Changes | Example |
|---|
| < 576px | Single column. Stack everything. Hamburger nav. | Cards stacked, full-width buttons |
| 576-767px | Still single column but wider. Some side-by-side elements. | Two small cards per row |
| 768-1023px | Two columns possible. Sidebar appears (if needed). Tab bar becomes top nav. | Dashboard gains a sidebar |
| 1024-1279px | Full multi-column layouts. All navigation visible. | Three-column card grid |
| 1280px+ | Max-width container. Generous whitespace. Optional: wider sidebars, richer data tables. | 1200px container, centered |
Important: Let content dictate breakpoints, not device widths. If your card text wraps awkwardly at 820px, add a breakpoint at 820px. Don't force it to wait until 1024px.
/* Content-driven breakpoint */
@media (min-width: 820px) {
.card-grid {
grid-template-columns: repeat(2, 1fr);
}
}
Touch Target Sizes
Fingers are imprecise. Touch targets must be large enough to tap without error.
| Standard | Minimum Size | Recommended | Spacing Between Targets |
|---|
| Apple HIG | 44 × 44 pt | 44 × 44 pt | 8px minimum |
| Google Material | 48 × 48 dp | 48 × 48 dp | 8px minimum |
| WCAG 2.2 (AA) | 24 × 24 px | 44 × 44 px | Target must not overlap |
Expanding Touch Targets Without Changing Visuals
A 24px icon can have a 48px touch target:
.icon-button {
/* Visual size */
width: 24px;
height: 24px;
/* Expanded touch area via padding */
padding: 12px;
margin: -12px; /* Compensate so layout isn't affected */
/* Or use a pseudo-element */
position: relative;
}
.icon-button::before {
content: '';
position: absolute;
inset: -12px; /* Expands hit area by 12px in all directions */
}
Common Touch Target Failures
| Element | Typical Size | Problem | Fix |
|---|
| Text links in body text | ~16px tall | Too small to tap accurately | Add padding or make them buttons on mobile |
| Close button (×) on modals | 16-20px | Frustrating to tap, especially top-right corner | Make at least 44×44px target area |
| Checkbox/radio inputs | Browser default (13px) | Nearly impossible to tap | Use custom styles with 44px minimum area |
| Breadcrumb links | 14px text, tight spacing | Adjacent links hard to distinguish | Increase spacing to 8px+ between items |
| Pagination numbers | Small, cramped | Wrong page selected constantly | Use buttons with 44px+ targets or prev/next only |
Thumb Zone Design
Most mobile users hold their phone with one hand and operate with their thumb. The bottom of the screen is easiest to reach.
┌─────────────────────────┐
│ │
│ ⬛ HARD TO REACH │ Requires stretching or two hands
│ │
│ ⬜ OK │ Reachable but not ideal
│ │ │ │
│ ✅ EASY │ ✅ EASY │ │ Natural thumb range
│ │ │ │
│ │ │ │
│────────┴────────┴───────│
│ ✅ PRIMARY ACTIONS │ Bottom of screen — easiest
└─────────────────────────┘
Thumb Zone Application
| Element | Placement | Why |
|---|
| Primary navigation (tab bar) | Bottom | Always reachable with thumb |
| Primary action button | Bottom or center-bottom | Easy to tap without shifting grip |
| Search bar | Top is conventional, but consider bottom sheet | Top requires stretch on large phones |
| Menu/navigation | Bottom sheet or bottom tab bar | Hamburger menus at top-left are hardest to reach |
| Destructive actions | Top area or behind confirmation | Harder to reach = harder to accidentally trigger |
Bottom Navigation Best Practices
┌─────────────────────────────────────────┐
│ │
│ Content Area │
│ │
├─────────────────────────────────────────┤
│ │
│ 🏠 🔍 ➕ ❤️ 👤 │
│ Home Search New Saved Profile │
│ │
└─────────────────────────────────────────┘
| Rule | Guideline |
|---|
| Item count | 3-5 items maximum. More requires a different pattern. |
| Labels | Always include text labels with icons. Icons alone are ambiguous. |
| Active state | Clear visual distinction for the selected item (color + weight) |
| Badge/notification | Small dot or count badge for pending actions |
| Height | 56-64px including safe area padding on notched devices |
Responsive Navigation Patterns
| Pattern | Best For | Pros | Cons |
|---|
| Bottom tab bar | Apps, 3-5 primary actions | Always visible, thumb-reachable | Limited to 5 items |
| Hamburger menu | Sites with 5+ sections | Compact, widely recognized | Hides navigation, requires tap to reveal |
| Priority+ nav | Sites with variable nav items | Shows most important items, overflows rest | Can feel inconsistent as items appear/disappear |
| Off-canvas/drawer | Deep navigation trees | Room for nested items, familiar | Hides content, requires explicit open |
| Bottom sheet | Contextual actions, filters | Thumb-friendly, can show lots of options | Can feel heavy if overused |
| Full-screen nav | Simple sites (5-8 pages) | Bold, easy to read, large targets | Covers all content |
Implementing Priority+ Navigation
/* Show what fits, overflow the rest */
.nav {
display: flex;
overflow: hidden;
}
.nav-item { white-space: nowrap; }
.nav-overflow {
margin-left: auto;
/* "More" button that opens a dropdown with hidden items */
}
Responsive Content Strategies
What to Do With Content at Each Breakpoint
| Strategy | Description | Example |
|---|
| Reflow | Rearrange layout, keep all content | 3-column → 2-column → 1-column |
| Resize | Scale elements proportionally | Smaller images, fluid typography |
| Reposition | Move elements to different locations | Sidebar becomes bottom section |
| Reveal/Hide | Show/hide based on screen size | Hide secondary stats on mobile |
| Replace | Swap components for mobile equivalents | Data table → stacked cards |
Data Tables on Mobile
Tables are one of the hardest responsive challenges. Options:
Option 1: Horizontal scroll (simplest)
┌──────────────────────────────┐
│ Name | Email | Role →│ (scrollable)
│ Jane | jane@... | Admin →│
└──────────────────────────────┘
Option 2: Stacked cards (most mobile-friendly)
┌──────────────────────────────┐
│ Jane Doe │
│ Email: jane@example.com │
│ Role: Admin │
│ Status: Active │
├──────────────────────────────┤
│ John Smith │
│ Email: john@example.com │
│ Role: Editor │
│ Status: Active │
└──────────────────────────────┘
Option 3: Priority columns (show key columns, hide others)
┌──────────────────────────────┐
│ Name | Role │ (Email, Status hidden behind tap)
│ Jane Doe | Admin │
│ John Smith | Editor │
└──────────────────────────────┘
Fluid Typography
Use clamp() for smooth scaling between breakpoints:
h1 {
font-size: clamp(2rem, 5vw + 1rem, 4rem);
/* min: 32px, preferred: 5vw + 16px, max: 64px */
}
h2 {
font-size: clamp(1.5rem, 3vw + 0.5rem, 2.5rem);
}
p {
font-size: clamp(1rem, 1vw + 0.75rem, 1.25rem);
/* Body text stays between 16px and 20px */
}
Important: Body text shouldn't scale much. 16-18px on mobile, 16-20px on desktop. Headings can scale significantly.
Container Queries
Style components based on their container size, not the viewport. This makes components truly reusable. They adapt to wherever they're placed.
/* Define the container */
.card-container {
container-type: inline-size;
}
/* Style based on container width */
@container (min-width: 400px) {
.card {
display: flex;
flex-direction: row;
gap: 24px;
}
}
@container (max-width: 399px) {
.card {
display: flex;
flex-direction: column;
gap: 16px;
}
}
| Use Case | Use This |
|---|
| Page-level layout changes (sidebar, header) | Media queries |
| Component-level layout changes (card, widget) | Container queries |
| Typography scaling | Media queries or clamp() |
| Component adapting to different placements | Container queries |
Mobile-Specific Design Considerations
| Technique | Implementation | Why |
|---|
| Use correct input types | type="email", type="tel", type="number" | Shows the right keyboard |
| Prevent zoom on focus | font-size: 16px minimum on inputs | iOS zooms if input text < 16px |
Use inputmode | inputmode="numeric" for codes, inputmode="decimal" for amounts | Fine-grained keyboard control |
Use autocomplete | autocomplete="email", autocomplete="tel" | Auto-fills from saved data |
| Minimize typing | Use select menus, toggles, date pickers | Typing on mobile is slow and error-prone |
| Technique | Impact |
|---|
| Lazy load images below the fold | Faster initial load, less data usage |
Serve smaller images via srcset | 2-5× less bandwidth on mobile |
Use loading="lazy" on images | Native lazy loading, no JavaScript |
| Reduce JavaScript bundle | Weak mobile CPUs struggle with heavy JS |
| Use system fonts | Zero font-loading delay |
| Preconnect to required origins | Saves DNS/TLS time on slow connections |
<!-- Responsive images -->
<img
srcset="photo-400.webp 400w,
photo-800.webp 800w,
photo-1200.webp 1200w"
sizes="(max-width: 600px) 100vw,
(max-width: 1024px) 50vw,
33vw"
src="photo-800.webp"
alt="Description"
loading="lazy"
>
Offline and Connectivity
- Design for slow/intermittent connections. Show cached content when possible
- Provide offline feedback: "You're offline. Changes will sync when reconnected."
- Don't block the entire UI when one request fails
- Queue actions for retry rather than showing an error
Testing Responsive Designs
| Method | What It Catches | Limitation |
|---|
| Browser DevTools (responsive mode) | Layout issues, breakpoint problems | Doesn't test real device performance |
| BrowserStack / Sauce Labs | Cross-browser, cross-device rendering | Slower, requires account |
| Real device testing | Touch interactions, performance, gestures | Expensive to maintain device lab |
| Chrome Lighthouse | Performance, accessibility scores | Simulated only |
| Users testing on their devices | Real-world usability issues | Harder to organize |
Minimum test matrix:
| Device Type | Example | Screen Width |
|---|
| Small phone | iPhone SE | 375px |
| Standard phone | iPhone 15 / Pixel 8 | 390-412px |
| Large phone | iPhone 15 Pro Max | 430px |
| Tablet portrait | iPad | 768px |
| Tablet landscape | iPad | 1024px |
| Laptop | MacBook Air | 1280-1440px |
| Desktop | External monitor | 1920px |
Common Mistakes
| Mistake | Impact | Fix |
|---|
| Hover-dependent interactions | Touch devices have no hover state | Ensure all interactions work with tap only |
| Fixed-position elements covering content | Nav bar covers text, footer blocks buttons | Test scrolling thoroughly, use safe-area padding |
| Not testing landscape orientation | Content breaks, elements overlap | Test both orientations on phones and tablets |
| Input font size < 16px | iOS Safari zooms the whole page on focus | Set input font-size to 16px minimum |
| Relying on screen width for device detection | Tablets can have phone-sized viewports | Use feature detection (@media (hover: hover)) |
| Not accounting for safe areas (notch, home indicator) | Content hidden behind device hardware | Use env(safe-area-inset-*) padding |
/* Safe area padding for notched devices */
.bottom-nav {
padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px));
}
.header {
padding-top: calc(16px + env(safe-area-inset-top, 0px));
}
Key Takeaways
- Design mobile-first, then enhance for larger screens. Use
min-width media queries. - Touch targets: 44×44px minimum. Use padding to expand hit areas beyond visual boundaries.
- Place primary actions in the thumb zone (bottom of screen on mobile).
- Use
clamp() for fluid typography. Body text barely scales; headings scale significantly. - Container queries make components responsive to their container, not just the viewport.
- Test on real devices. Browser DevTools miss touch interactions, performance, and safe areas.
- Tables on mobile: use horizontal scroll, stacked cards, or priority columns. Don't shrink the table.
- Prevent iOS zoom: set input font-size to at least 16px.