Over half of web traffic is mobile. If your design doesn't work on a phone, it doesn't work. Responsive design is not a feature. It is 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. The result is always cramped and unusable. 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 x 44 pt | 44 x 44 pt | 8px minimum |
| Google Material | 48 x 48 dp | 48 x 48 dp | 8px minimum |
| WCAG 2.2 (AA) | 24 x 24 px | 44 x 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 reach
└─────────────────────────┘
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 │
│ [home] [search] [plus] [heart] [user] │
│ │
└─────────────────────────────────────────┘
| 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 to 2-column to 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 becomes 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 should not 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 you place them.
/* 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 or 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: 44x44px 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.