Skip to content
Performance

Responsive Design Strategy

Breakpoints, mobile-first, and testing methodology — design for every screen as first-class.

stellae.design

10 min read

TL;DR

  • Design mobile-first: start with the smallest screen, then enhance with min-width media queries
  • Use CSS Grid auto-fill/auto-fit and Flexbox wrap for intrinsically responsive layouts
  • Fluid typography with clamp() eliminates most font-size breakpoints
  • Container queries let components respond to their parent's width, not the viewport

Mobile-First Is Not a Suggestion

Mobile-first isn't about designing for phones first — it's about progressive enhancement. Start with the most constrained environment and add complexity for larger screens.

In CSS, this means using min-width media queries (adding styles as screens grow) rather than max-width (removing styles as screens shrink).

css
/* ✅ Mobile-first: base is mobile, enhance upward */
.grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
}

@media (min-width: 640px) {
  .grid { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 1024px) {
  .grid { grid-template-columns: repeat(3, 1fr); }
}

/* ❌ Desktop-first: base is desktop, degrade downward */
.grid {
  grid-template-columns: repeat(3, 1fr);
}

@media (max-width: 1023px) {
  .grid { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 639px) {
  .grid { grid-template-columns: 1fr; }
}

Breakpoint Strategy

Don't tie breakpoints to specific devices. Phones change size every year. Instead, use content-driven breakpoints:

css
:root {
  /* Content-driven breakpoints */
  --bp-sm: 640px;   /* ~40rem — single column feels cramped */
  --bp-md: 768px;   /* ~48rem — room for two columns */
  --bp-lg: 1024px;  /* ~64rem — room for sidebar + content */
  --bp-xl: 1280px;  /* ~80rem — wide layouts */
}

Test by resizing your browser and adding breakpoints where the layout breaks, not at 375px because "that's an iPhone."

Intrinsic Design: Beyond Breakpoints

The best responsive code uses as few media queries as possible. CSS Grid and Flexbox can handle most responsive behavior intrinsically:

css
/* Responsive card grid — ZERO media queries */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
  gap: 1.5rem;
}

/* Responsive sidebar — collapses naturally */
.layout {
  display: flex;
  flex-wrap: wrap;
  gap: 2rem;
}

.sidebar {
  flex: 1 1 250px;   /* grows from 250px, wraps when too narrow */
  max-width: 350px;
}

.main {
  flex: 999 1 0%;    /* takes most space, allows sidebar to wrap */
  min-width: 60%;
}

Fluid Typography

Instead of changing font-size at every breakpoint, use clamp() for smooth scaling:

css
/* Fluid type scale */
:root {
  --text-sm:   clamp(0.875rem, 0.8rem + 0.2vw, 1rem);
  --text-base: clamp(1rem, 0.9rem + 0.3vw, 1.125rem);
  --text-lg:   clamp(1.25rem, 1rem + 0.6vw, 1.5rem);
  --text-xl:   clamp(1.5rem, 1.1rem + 1vw, 2rem);
  --text-2xl:  clamp(2rem, 1.3rem + 1.8vw, 3rem);
  --text-3xl:  clamp(2.5rem, 1.5rem + 2.5vw, 4rem);
}

h1 { font-size: var(--text-3xl); }
h2 { font-size: var(--text-2xl); }
h3 { font-size: var(--text-xl); }
p  { font-size: var(--text-base); }

Container Queries

Media queries respond to the viewport. Container queries respond to the parent element's size. This is game-changing for reusable components:

css
.card-container {
  container-type: inline-size;
  container-name: card;
}

.card {
  display: grid;
  gap: 1rem;
}

/* When the CONTAINER (not viewport) is wide enough */
@container card (min-width: 400px) {
  .card {
    grid-template-columns: 150px 1fr;
  }
}

@container card (min-width: 600px) {
  .card {
    grid-template-columns: 200px 1fr auto;
  }
}

Container queries have excellent browser support (all modern browsers since early 2023). Use them.

Responsive Images

html
<!-- Art direction: different crops for different sizes -->
<picture>
  <source media="(min-width: 1024px)" srcset="hero-wide.webp">
  <source media="(min-width: 640px)" srcset="hero-medium.webp">
  <img src="hero-mobile.webp" alt="Hero image" loading="lazy">
</picture>

<!-- Resolution switching: same image, different sizes -->
<img
  src="photo-400.webp"
  srcset="photo-400.webp 400w, photo-800.webp 800w, photo-1200.webp 1200w"
  sizes="(min-width: 1024px) 33vw, (min-width: 640px) 50vw, 100vw"
  alt="Product photo"
  loading="lazy"
>

Best Practices

  • Do design mobile-first and use min-width media queries
  • Do use intrinsic sizing (auto-fill, minmax, flex-wrap) before reaching for media queries
  • Do use clamp() for fluid typography and spacing
  • Do test on real devices, not just browser resize — touch targets, font rendering, and performance differ
  • Don't target specific device widths — use content-driven breakpoints
  • Don't hide content on mobile that exists on desktop — rethink the hierarchy instead
  • Don't forget <meta name="viewport" content="width=device-width, initial-scale=1"> — without it, nothing works

Common Mistakes

  • Designing desktop-first then trying to "make it fit" on mobile — always harder than mobile-first
  • Too many breakpoints — if you have 8+ breakpoints, your layout probably isn't intrinsically responsive enough
  • Fixed widths in flexible containers — use max-width or min() instead of width
  • Forgetting landscape phones — a phone in landscape is ~640-900px wide, which triggers "tablet" breakpoints

Tools to Explore

  • Responsive Tester — preview your site across device sizes
  • Grid Generator — build responsive grid layouts
  • Flexbox Playground — experiment with wrapping and responsive flex layouts
  • Fluid Calculator — generate fluid type and spacing scales
Performanceperformance

Was this article helpful?