Master modern responsive design techniques to create interfaces that adapt seamlessly across all screen sizes and device contexts. - Implementing mobile-first responsive layouts - Using container queries for component-based responsiveness
/* 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) */
/* 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); }