Search across all documentation pages
Breakpoints, container queries, and mobile-first patterns in Tailwind CSS v4.
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.
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"
What this demonstrates:
lggrid-cols-1 to grid-cols-4)max-lg:hidden for hiding elements below a breakpointsticky sidebar on desktop@media (min-width: ...) — md:flex means @media (width >= 48rem) { display: flex }max-* variants compile to @media (width < ...) — useful for mobile-only styles@container on the parent and @sm:, @md:, etc. on children@3xs(12rem) @2xs(16rem) @xs(20rem) @sm(24rem) @md(28rem) @lg(32rem) @xl(36rem) @2xl(42rem) etc.rem by default, not pxCustom 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=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
// 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
Dynamic class names are not detected — sm: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 show — hidden 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.
| Alternative | Use When | Don't Use When |
|---|---|---|
CSS @media directly | You need complex media queries (hover, prefers-reduced-motion) | Standard breakpoints suffice |
CSS @container directly | You need container query features beyond Tailwind's support | Tailwind's @container utilities cover your needs |
useMediaQuery hook | You need JS-level responsive logic (different components) | CSS-only layout changes suffice |
Responsive images (srcset) | You need different image files per breakpoint | You only need layout changes |
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.
sm = 40rem, md = 48rem, lg = 64rem, xl = 80rem, 2xl = 96remrem, not px.md and xl?<div className="hidden md:block xl:hidden">Tablet only</div>max-lg:hidden and lg:hidden?max-lg:hidden — hidden below lg (viewport < 64rem)lg:hidden — hidden at lg and above (viewport >= 64rem)@container to a parent element@sm:, @md:, @lg: etc. on children@theme {
--breakpoint-xs: 30rem;
--breakpoint-3xl: 120rem;
}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.
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.
@sm:flex do nothing. What is missing?The parent element needs className="@container". Without it, there is no container to query against.
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.
Use the print: variant: print:hidden, print:text-black. This compiles to @media print.
<div className="@container/sidebar">
<div className="@md/sidebar:block">Named containers let you query a specific ancestor when multiple @container elements are nested.