Responsive Design
Master modern responsive design techniques to create interfaces that adapt seamlessly across all screen sizes and device contexts.
When to Use This Skill
-
Implementing mobile-first responsive layouts
-
Using container queries for component-based responsiveness
-
Creating fluid typography and spacing scales
-
Building complex layouts with CSS Grid and Flexbox
-
Designing breakpoint strategies for design systems
-
Implementing responsive images and media
-
Creating adaptive navigation patterns
-
Building responsive tables and data displays
Core Capabilities
- Container Queries
-
Component-level responsiveness independent of viewport
-
Container query units (cqi, cqw, cqh)
-
Style queries for conditional styling
-
Fallbacks for browser support
- Fluid Typography & Spacing
-
CSS clamp() for fluid scaling
-
Viewport-relative units (vw, vh, dvh)
-
Fluid type scales with min/max bounds
-
Responsive spacing systems
- Layout Patterns
-
CSS Grid for 2D layouts
-
Flexbox for 1D distribution
-
Intrinsic layouts (content-based sizing)
-
Subgrid for nested grid alignment
- Breakpoint Strategy
-
Mobile-first media queries
-
Content-based breakpoints
-
Design token integration
-
Feature queries (@supports)
Quick Reference
Modern Breakpoint Scale
/* Mobile-first breakpoints / / Base: Mobile (< 640px) / @media (min-width: 640px) { / sm: Landscape phones, small tablets / } @media (min-width: 768px) { / md: Tablets / } @media (min-width: 1024px) { / lg: Laptops, small desktops / } @media (min-width: 1280px) { / xl: Desktops / } @media (min-width: 1536px) { / 2xl: Large desktops */ }
/* Tailwind CSS equivalent / / sm: @media (min-width: 640px) / / md: @media (min-width: 768px) / / lg: @media (min-width: 1024px) / / xl: @media (min-width: 1280px) / / 2xl: @media (min-width: 1536px) */
Key Patterns
Pattern 1: Container Queries
/* Define a containment context */ .card-container { container-type: inline-size; container-name: card; }
/* Query the container, not the viewport */ @container card (min-width: 400px) { .card { display: grid; grid-template-columns: 200px 1fr; gap: 1rem; }
.card-image { aspect-ratio: 1; } }
@container card (min-width: 600px) { .card { grid-template-columns: 250px 1fr; }
.card-title { font-size: 1.5rem; } }
/* Container query units / .card-title { / 5% of container width, clamped between 1rem and 2rem */ font-size: clamp(1rem, 5cqi, 2rem); }
// React component with container queries function ResponsiveCard({ title, image, description }) { return ( <div className="@container"> <article className="flex flex-col @md:flex-row @md:gap-4"> <img src={image} alt="" className="w-full @md:w-48 @lg:w-64 aspect-video @md:aspect-square object-cover" /> <div className="p-4 @md:p-0"> <h2 className="text-lg @md:text-xl @lg:text-2xl font-semibold"> {title} </h2> <p className="mt-2 text-muted-foreground @md:line-clamp-3"> {description} </p> </div> </article> </div> ); }
Pattern 2: Fluid Typography
/* Fluid type scale using clamp() / :root { / Min size, preferred (fluid), max size */ --text-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem); --text-sm: clamp(0.875rem, 0.8rem + 0.375vw, 1rem); --text-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem); --text-lg: clamp(1.125rem, 1rem + 0.625vw, 1.25rem); --text-xl: clamp(1.25rem, 1rem + 1.25vw, 1.5rem); --text-2xl: clamp(1.5rem, 1.25rem + 1.25vw, 2rem); --text-3xl: clamp(1.875rem, 1.5rem + 1.875vw, 2.5rem); --text-4xl: clamp(2.25rem, 1.75rem + 2.5vw, 3.5rem); }
/* Usage */ h1 { font-size: var(--text-4xl); } h2 { font-size: var(--text-3xl); } h3 { font-size: var(--text-2xl); } p { font-size: var(--text-base); }
/* Fluid spacing scale */ :root { --space-xs: clamp(0.25rem, 0.2rem + 0.25vw, 0.5rem); --space-sm: clamp(0.5rem, 0.4rem + 0.5vw, 0.75rem); --space-md: clamp(1rem, 0.8rem + 1vw, 1.5rem); --space-lg: clamp(1.5rem, 1.2rem + 1.5vw, 2.5rem); --space-xl: clamp(2rem, 1.5rem + 2.5vw, 4rem); }
// Utility function for fluid values function fluidValue( minSize: number, maxSize: number, minWidth = 320, maxWidth = 1280, ) { const slope = (maxSize - minSize) / (maxWidth - minWidth); const yAxisIntersection = -minWidth * slope + minSize;
return clamp(${minSize}rem, ${yAxisIntersection.toFixed(4)}rem + ${(slope * 100).toFixed(4)}vw, ${maxSize}rem);
}
// Generate fluid type scale const fluidTypeScale = { sm: fluidValue(0.875, 1), base: fluidValue(1, 1.125), lg: fluidValue(1.25, 1.5), xl: fluidValue(1.5, 2), "2xl": fluidValue(2, 3), };
Pattern 3: CSS Grid Responsive Layout
/* Auto-fit grid - items wrap automatically */ .grid-auto { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); gap: 1.5rem; }
/* Auto-fill grid - maintains empty columns */ .grid-auto-fill { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 1rem; }
/* Responsive grid with named areas */ .page-layout { display: grid; grid-template-areas: "header" "main" "sidebar" "footer"; gap: 1rem; }
@media (min-width: 768px) { .page-layout { grid-template-columns: 1fr 300px; grid-template-areas: "header header" "main sidebar" "footer footer"; } }
@media (min-width: 1024px) { .page-layout { grid-template-columns: 250px 1fr 300px; grid-template-areas: "header header header" "nav main sidebar" "footer footer footer"; } }
.header { grid-area: header; } .main { grid-area: main; } .sidebar { grid-area: sidebar; } .footer { grid-area: footer; }
// Responsive grid component
function ResponsiveGrid({ children, minItemWidth = "250px", gap = "1.5rem" }) {
return (
<div
className="grid"
style={{
gridTemplateColumns: repeat(auto-fit, minmax(min(${minItemWidth}, 100%), 1fr)),
gap,
}}
>
{children}
</div>
);
}
// Usage with Tailwind function ProductGrid({ products }) { return ( <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 md:gap-6"> {products.map((product) => ( <ProductCard key={product.id} product={product} /> ))} </div> ); }
Pattern 4: Responsive Navigation
function ResponsiveNav({ items }) { const [isOpen, setIsOpen] = useState(false);
return ( <nav className="relative"> {/* Mobile menu button */} <button className="lg:hidden p-2" onClick={() => setIsOpen(!isOpen)} aria-expanded={isOpen} aria-controls="nav-menu" > <span className="sr-only">Toggle navigation</span> {isOpen ? <X /> : <Menu />} </button>
{/* Navigation links */}
<ul
id="nav-menu"
className={cn(
// Base: hidden on mobile
"absolute top-full left-0 right-0 bg-background border-b",
"flex flex-col",
// Mobile: slide down
isOpen ? "flex" : "hidden",
// Desktop: always visible, horizontal
"lg:static lg:flex lg:flex-row lg:border-0 lg:bg-transparent",
)}
>
{items.map((item) => (
<li key={item.href}>
<a
href={item.href}
className={cn(
"block px-4 py-3",
"lg:px-3 lg:py-2",
"hover:bg-muted lg:hover:bg-transparent lg:hover:text-primary",
)}
>
{item.label}
</a>
</li>
))}
</ul>
</nav>
); }
Pattern 5: Responsive Images
// Responsive image with art direction function ResponsiveHero() { return ( <picture> {/* Art direction: different crops for different screens */} <source media="(min-width: 1024px)" srcSet="/hero-wide.webp" type="image/webp" /> <source media="(min-width: 768px)" srcSet="/hero-medium.webp" type="image/webp" /> <source srcSet="/hero-mobile.webp" type="image/webp" />
{/* Fallback */}
<img
src="/hero-mobile.jpg"
alt="Hero image description"
className="w-full h-auto"
loading="eager"
fetchpriority="high"
/>
</picture>
); }
// Responsive image with srcset for resolution switching
function ProductImage({ product }) {
return (
<img
src={product.image}
srcSet={ ${product.image}?w=400 400w, ${product.image}?w=800 800w, ${product.image}?w=1200 1200w }
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
alt={product.name}
className="w-full h-auto object-cover"
loading="lazy"
/>
);
}
Pattern 6: Responsive Tables
// Responsive table with horizontal scroll function ResponsiveTable({ data, columns }) { return ( <div className="w-full overflow-x-auto"> <table className="w-full min-w-[600px]"> <thead> <tr> {columns.map((col) => ( <th key={col.key} className="text-left p-3"> {col.label} </th> ))} </tr> </thead> <tbody> {data.map((row, i) => ( <tr key={i} className="border-t"> {columns.map((col) => ( <td key={col.key} className="p-3"> {row[col.key]} </td> ))} </tr> ))} </tbody> </table> </div> ); }
// Card-based table for mobile function ResponsiveDataTable({ data, columns }) { return ( <> {/* Desktop table /} <table className="hidden md:table w-full"> {/ ... standard table */} </table>
{/* Mobile cards */}
<div className="md:hidden space-y-4">
{data.map((row, i) => (
<div key={i} className="border rounded-lg p-4 space-y-2">
{columns.map((col) => (
<div key={col.key} className="flex justify-between">
<span className="font-medium text-muted-foreground">
{col.label}
</span>
<span>{row[col.key]}</span>
</div>
))}
</div>
))}
</div>
</>
); }
Viewport Units
/* Standard viewport units / .full-height { height: 100vh; / May cause issues on mobile */ }
/* Dynamic viewport units (recommended for mobile) / .full-height-dynamic { height: 100dvh; / Accounts for mobile browser UI */ }
/* Small viewport (minimum) */ .min-full-height { min-height: 100svh; }
/* Large viewport (maximum) */ .max-full-height { max-height: 100lvh; }
/* Viewport-relative font sizing / .hero-title { / 5vw with min/max bounds */ font-size: clamp(2rem, 5vw, 4rem); }
Best Practices
-
Mobile-First: Start with mobile styles, enhance for larger screens
-
Content Breakpoints: Set breakpoints based on content, not devices
-
Fluid Over Fixed: Use fluid values for typography and spacing
-
Container Queries: Use for component-level responsiveness
-
Test Real Devices: Simulators don't catch all issues
-
Performance: Optimize images, lazy load off-screen content
-
Touch Targets: Maintain 44x44px minimum on mobile
-
Logical Properties: Use inline/block for internationalization
Common Issues
-
Horizontal Overflow: Content breaking out of viewport
-
Fixed Widths: Using px instead of relative units
-
Viewport Height: 100vh issues on mobile browsers
-
Font Size: Text too small on mobile
-
Touch Targets: Buttons too small to tap accurately
-
Aspect Ratio: Images squishing or stretching
-
Z-Index Stacking: Overlays breaking on different screens