React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

tailwindresponsivebreakpointscontainer-queriesmobile-first

Responsive Design

Breakpoints, container queries, and mobile-first patterns in Tailwind CSS v4.

Recipe

Quick-reference recipe card — copy-paste ready.

// Mobile-first breakpoints (min-width)
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
 
// Default breakpoints: sm(40rem) md(48rem) lg(64rem) xl(80rem) 2xl(96rem)
 
// Max-width variant
<div className="max-lg:hidden">  {/* hidden below lg */}
 
// Range
<div className="md:max-xl:flex">  {/* flex only between md and xl */}
 
// Container queries
<div className="@container">
  <div className="@sm:flex @lg:grid @lg:grid-cols-2">
    {/* responds to container width, not viewport */}
  </div>
</div>
 
// Named containers
<div className="@container/sidebar">
  <div className="@md/sidebar:block">

When to reach for this: When your layout or component needs to adapt across screen sizes or container sizes.

Working Example

export function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex min-h-screen flex-col lg:flex-row">
      {/* Sidebar — full width on mobile, fixed width on desktop */}
      <aside className="border-b bg-gray-50 p-4 lg:w-64 lg:shrink-0 lg:border-b-0 lg:border-r">
        <nav className="flex gap-2 overflow-x-auto lg:flex-col lg:gap-1 lg:overflow-visible">
          <a href="#" className="whitespace-nowrap rounded px-3 py-2 text-sm hover:bg-gray-200 lg:whitespace-normal">
            Dashboard
          </a>
          <a href="#" className="whitespace-nowrap rounded px-3 py-2 text-sm hover:bg-gray-200 lg:whitespace-normal">
            Analytics
          </a>
          <a href="#" className="whitespace-nowrap rounded px-3 py-2 text-sm hover:bg-gray-200 lg:whitespace-normal">
            Settings
          </a>
        </nav>
      </aside>
 
      {/* Main content */}
      <main className="flex-1 p-4 md:p-6 lg:p-8">
        {/* Stats grid — responsive columns */}
        <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
          <StatCard label="Revenue" value="$12,345" />
          <StatCard label="Users" value="1,234" />
          <StatCard label="Orders" value="567" />
          <StatCard label="Conversion" value="3.2%" />
        </div>
 
        {/* Content area with container queries */}
        <div className="mt-6 grid gap-6 lg:grid-cols-[1fr_300px]">
          <section className="@container">
            <div className="@md:grid @md:grid-cols-2 @md:gap-4 space-y-4 @md:space-y-0">
              {children}
            </div>
          </section>
 
          <aside className="max-lg:hidden">
            <div className="sticky top-4 rounded border p-4">
              <h3 className="font-medium">Activity Feed</h3>
            </div>
          </aside>
        </div>
      </main>
    </div>
  );
}
 
function StatCard({ label, value }: { label: string; value: string }) {
  return (
    <div className="rounded-lg border bg-white p-4 shadow-sm">
      <p className="text-sm text-gray-500">{label}</p>
      <p className="mt-1 text-2xl font-bold">{value}</p>
    </div>
  );
}

What this demonstrates:

  • Mobile-first layout that shifts from stacked to sidebar on lg
  • Horizontal scroll nav on mobile, vertical on desktop
  • Responsive grid columns (grid-cols-1 to grid-cols-4)
  • Container queries for content-area responsive layout
  • max-lg:hidden for hiding elements below a breakpoint
  • sticky sidebar on desktop

Deep Dive

How It Works

  • Tailwind uses a mobile-first approach: unprefixed utilities apply to all sizes, breakpoint prefixes apply at that size and up
  • Breakpoints compile to @media (min-width: ...)md:flex means @media (width >= 48rem) { display: flex }
  • max-* variants compile to @media (width < ...) — useful for mobile-only styles
  • Container queries use @container on the parent and @sm:, @md:, etc. on children
  • Container query breakpoints: @3xs(12rem) @2xs(16rem) @xs(20rem) @sm(24rem) @md(28rem) @lg(32rem) @xl(36rem) @2xl(42rem) etc.
  • v4 breakpoints are defined in rem by default, not px

Variations

Custom breakpoints:

@theme {
  --breakpoint-xs: 30rem;      /* 480px */
  --breakpoint-3xl: 120rem;    /* 1920px */
}

Container query with custom sizes:

@theme {
  --container-4xs: 8rem;
}

Responsive typography:

<h1 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl">
  Responsive Heading
</h1>
 
{/* Or use clamp for fluid sizing */}
<h1 className="text-[clamp(1.5rem,4vw,3rem)]">
  Fluid Heading
</h1>

Responsive show/hide:

{/* Show on mobile only */}
<div className="md:hidden">Mobile nav</div>
 
{/* Show on desktop only */}
<div className="hidden md:block">Desktop nav</div>
 
{/* Show between md and xl */}
<div className="hidden md:block xl:hidden">Tablet only</div>

Container query card:

function AdaptiveCard({ children }: { children: React.ReactNode }) {
  return (
    <div className="@container">
      <div className="flex flex-col @sm:flex-row @sm:items-center gap-4 rounded border p-4">
        <div className="size-16 shrink-0 rounded bg-gray-200 @sm:size-20" />
        <div className="flex-1">{children}</div>
      </div>
    </div>
  );
}

TypeScript Notes

// Responsive variants are class names — no TS impact
// But you can type breakpoint-dependent props:
interface LayoutProps {
  columns?: {
    default: number;
    sm?: number;
    md?: number;
    lg?: number;
  };
}
 
function Grid({ columns }: LayoutProps) {
  const colClasses = {
    1: "grid-cols-1",
    2: "grid-cols-2",
    3: "grid-cols-3",
    4: "grid-cols-4",
  } as const;
 
  return (
    <div className={cn(
      "grid gap-4",
      colClasses[columns.default as keyof typeof colClasses],
      columns.sm && `sm:${colClasses[columns.sm as keyof typeof colClasses]}`,
    )}>
      {/* Note: dynamic class construction does not work with Tailwind — see Gotchas */}
    </div>
  );
}

Gotchas

  • Dynamic class names are not detectedsm:grid-cols-${n} is never found by Tailwind's scanner. Fix: Use complete static class names or safelist them.

  • max-* is <, not <=max-md:hidden means hidden when viewport is strictly below md. At exactly 48rem, md: takes over.

  • Container queries need @container parent — Without it, @sm:flex does nothing. Fix: Always add @container to the element whose width you want to query.

  • hidden at mobile + breakpoint showhidden md:flex works, but hidden md:block shows as block. Make sure the display value matches what you want.

  • Print breakpoint — Use print: for print-specific styles: print:hidden, print:text-black. Often forgotten but important for printable pages.

Alternatives

AlternativeUse WhenDon't Use When
CSS @media directlyYou need complex media queries (hover, prefers-reduced-motion)Standard breakpoints suffice
CSS @container directlyYou need container query features beyond Tailwind's supportTailwind's @container utilities cover your needs
useMediaQuery hookYou need JS-level responsive logic (different components)CSS-only layout changes suffice
Responsive images (srcset)You need different image files per breakpointYou only need layout changes

FAQs

What does "mobile-first" mean in Tailwind's breakpoint system?

Unprefixed utilities apply to all screen sizes. Breakpoint prefixes like md: apply at that size and up (min-width). You style mobile first, then layer on larger-screen overrides.

What are the default breakpoint values in Tailwind v4?
  • sm = 40rem, md = 48rem, lg = 64rem, xl = 80rem, 2xl = 96rem
  • They are defined in rem, not px.
How do you show an element only between md and xl?
<div className="hidden md:block xl:hidden">Tablet only</div>
What is the difference between max-lg:hidden and lg:hidden?
  • max-lg:hidden — hidden below lg (viewport < 64rem)
  • lg:hidden — hidden at lg and above (viewport >= 64rem)
How do container queries work in Tailwind v4?
  • Add @container to a parent element
  • Use @sm:, @md:, @lg: etc. on children
  • Children respond to the container's width, not the viewport
How do you define a custom breakpoint in v4?
@theme {
  --breakpoint-xs: 30rem;
  --breakpoint-3xl: 120rem;
}
What is the fluid typography approach with clamp()?
<h1 className="text-[clamp(1.5rem,4vw,3rem)]">Fluid Heading</h1>

The font size scales smoothly between 1.5rem and 3rem based on viewport width.

Gotcha: You wrote sm:grid-cols-${n} but the class is not applied. Why?

Tailwind's scanner cannot detect dynamic class names built with template literals. Use complete static class names or a lookup map instead.

Gotcha: Container query classes like @sm:flex do nothing. What is missing?

The parent element needs className="@container". Without it, there is no container to query against.

How would you type breakpoint-dependent props in TypeScript?
interface LayoutProps {
  columns?: {
    default: number;
    sm?: number;
    md?: number;
    lg?: number;
  };
}

Then use a static lookup map to convert numbers to class strings — never interpolate dynamically.

How do you apply styles only for print?

Use the print: variant: print:hidden, print:text-black. This compiles to @media print.

What is a named container and when would you use one?
<div className="@container/sidebar">
  <div className="@md/sidebar:block">

Named containers let you query a specific ancestor when multiple @container elements are nested.