Command Palette

Search for a command to run...

Responsive Design in React: Breakpoints, Fluid Grids, and Mobile‑First System

Responsive Design in React: Breakpoints, Fluid Grids, and Mobile‑First System

Build responsive UIs the modern way: mobile‑first CSS, Tailwind screens, fluid grids, container queries, clamp‑based type, and accessibility‑minded touch targets—without overusing JS.

4 min read·Responsive Design

Responsive design is CSS first. React gives you composition; CSS gives you layout. This guide shows how to combine mobile‑first utilities, fluid grids, container queries, and clamp‑based typography—with minimal JavaScript—to ship a system that handles small phones to large desktops gracefully.

You’ll get: Tailwind screen config, grid recipes, container query patterns, a hit‑target checklist, and a small hook for the few places you truly need JS media queries.


Table of Contents

  1. Mobile‑first mindset
  2. Tailwind screens & tokens
  3. Fluid grids with CSS Grid
  4. Container queries for components
  5. Fluid typography with clamp()
  6. Touch targets & inputs
  7. Conditional rendering without layout shift
  8. A tiny useBreakpoint hook (when needed)
  9. Testing across viewports & zoom
  10. Reference snippets
  11. FAQ

Mobile‑first mindset

  • Start styles at base; add enhancements at breakpoints (sm, md, lg, xl).
  • Avoid JS for layout; reserve it for behavior, not positioning.
  • Respect reduced motion and prefers-color-scheme with CSS.

Tailwind screens & tokens

// tailwind.config.ts (excerpt)
export default {
  theme: {
    screens: {
      sm: "640px",
      md: "768px",
      lg: "1024px",
      xl: "1280px",
      "2xl": "1536px",
    },
    extend: {
      spacing: {
        18: "4.5rem",
      },
    },
  },
};

Use semantic spacing/typography tokens and map to CSS variables for theming.


Fluid grids with CSS Grid

Let the browser pack items intelligently:

// ResponsiveCardGrid.tsx
export function ResponsiveCardGrid({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div
      className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3
                 [--min:16rem] [--gap:1rem]"
      style={{
        gridTemplateColumns: "repeat(auto-fit, minmax(var(--min), 1fr))",
        gap: "var(--gap)",
      }}
    >
      {children}
    </div>
  );
}

For equal height cards, use align-content: start and internal flex layouts.


Container queries for components

Use container queries so components adapt to their parent width, not just the viewport.

/* component.css */
.card {
  container-type: inline-size;
}
@container (min-width: 420px) {
  .card--media {
    display: grid;
    grid-template-columns: 180px 1fr;
    gap: 1rem;
  }
}
// Card.tsx
export function Card({ children }: { children: React.ReactNode }) {
  return <article className="card rounded-md border p-4">{children}</article>;
}

Set container-type on the parent that defines the component’s layout boundary.


Fluid typography with clamp()

:root {
  /* 320 → 1280 px viewport */
  --fs-title: clamp(1.25rem, 0.9rem + 1.2vw, 2rem);
  --fs-body: clamp(0.95rem, 0.8rem + 0.4vw, 1.125rem);
}
.h-title {
  font-size: var(--fs-title);
  line-height: 1.2;
}
.p-body {
  font-size: var(--fs-body);
  line-height: 1.55;
}

Bind these to Tailwind via theme extensions if desired.


Touch targets & inputs

  • Minimum 44×44px hit targets.
  • Spacing between interactive controls to prevent accidental taps.
  • Use inputmode/type for on‑screen keyboards (e.g., type="email", inputmode="numeric").
  • Keep focus styles visible; use :focus-visible not outline: none.

Conditional rendering without layout shift

Reserve space for items that appear at larger breakpoints to avoid CLS.

<div className="hidden md:block md:w-64">{/* sidebar content */}</div>

Skeletons with fixed dimensions help maintain stability.


A tiny useBreakpoint hook (when needed)

CSS should do most of the work. For logic (e.g., switch chart type), use a matchMedia hook.

import { useEffect, useState } from "react";
export function useBreakpoint(query = "(min-width: 1024px)") {
  const [match, setMatch] = useState<boolean | null>(null);
  useEffect(() => {
    const m = window.matchMedia(query);
    const onChange = () => setMatch(m.matches);
    onChange();
    m.addEventListener("change", onChange);
    return () => m.removeEventListener("change", onChange);
  }, [query]);
  return match;
}

Testing across viewports & zoom

  • Test with 200% zoom for reflow issues.
  • Snapshot key screens at mobile/tablet/desktop with Playwright.
  • Emulate reduced motion and dark mode to catch theme/animation bugs.
await page.setViewportSize({ width: 360, height: 800 });
await expect(page).toHaveScreenshot("home-mobile.png");

Reference snippets

Aspect‑ratio media

<div className="aspect-[16/9] overflow-hidden rounded">
  <img src="/thumb.jpg" alt="Preview" className="h-full w-full object-cover" />
</div>

Responsive nav

<nav className="flex items-center justify-between gap-3">
  <div className="text-lg font-semibold">Brand</div>
  <button className="md:hidden rounded border px-3 py-2">Menu</button>
  <ul className="hidden items-center gap-4 md:flex">
    <li>
      <a className="hover:underline" href="/features">
        Features
      </a>
    </li>
    <li>
      <a className="hover:underline" href="/pricing">
        Pricing
      </a>
    </li>
  </ul>
</nav>

FAQ

Should I use JS for breakpoints?
Only for behavioral switches. Layout should remain CSS‑driven with media/container queries.

Grid or flex for cards?
Use Grid for two‑dimensional layouts (rows/cols) and Flex for one‑dimensional flows.

Do I still need Bootstrap‑style containers?
Use a max‑width wrapper with padding, but let grids be fluid and content‑aware.


Next steps

  • Define screens and fluid type tokens.
  • Add container queries to your key components.
  • Snapshot three breakpoints with Playwright to prevent regressions.

Read more like this

Responsive Design in React: Breakpoints, Fluid Grids, and Mobile‑First System | Ruixen UI