install
source · Clone the upstream repo
git clone https://github.com/wpank/ai
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/wpank/ai "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/frontend/responsive-design" ~/.claude/skills/wpank-ai-responsive-design && rm -rf "$T"
manifest:
skills/frontend/responsive-design/SKILL.mdsource content
Responsive Design
Modern responsive CSS patterns using container queries, fluid typography, CSS Grid, and mobile-first strategies.
WHAT
Comprehensive responsive design techniques:
- Container queries for component-level responsiveness
- Fluid typography and spacing with
clamp() - CSS Grid and Flexbox layout patterns
- Mobile-first breakpoint strategies
- Responsive images and media
- Viewport units and dynamic sizing
WHEN
- Building layouts that adapt across screen sizes
- Creating reusable components that respond to container size
- Implementing fluid typography scales
- Setting up responsive grid systems
- Handling mobile navigation patterns
- Optimizing images for different devices
KEYWORDS
responsive, container query, media query, breakpoint, mobile-first, fluid typography, clamp, css grid, flexbox, viewport, adaptive, responsive images
Installation
OpenClaw / Moltbot / Clawbot
npx clawhub@latest install responsive-design
Breakpoint Scale (Mobile-First)
/* Base: Mobile (< 640px) - no media query needed */ @media (min-width: 640px) { /* sm: Large phones, small tablets */ } @media (min-width: 768px) { /* md: Tablets */ } @media (min-width: 1024px) { /* lg: Laptops */ } @media (min-width: 1280px) { /* xl: Desktops */ } @media (min-width: 1536px) { /* 2xl: Large screens */ }
Tailwind equivalents:
sm:, md:, lg:, xl:, 2xl:
Container Queries
Component-level responsiveness independent of viewport:
/* Define containment context */ .card-container { container-type: inline-size; container-name: card; } /* Query the container, not viewport */ @container card (min-width: 400px) { .card { display: grid; grid-template-columns: 200px 1fr; } } @container card (min-width: 600px) { .card-title { font-size: 1.5rem; } } /* Container query units */ .card-title { font-size: clamp(1rem, 5cqi, 2rem); /* 5% of container inline-size */ }
Tailwind 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> ) }
Fluid Typography
CSS Custom Properties Scale
:root { --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); } 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); }
Clamp Formula
clamp(MIN, PREFERRED, MAX) MIN: Smallest allowed size PREFERRED: Ideal fluid calculation (often uses vw) MAX: Largest allowed size
CSS Grid Responsive Layouts
Auto-Fit Grid (Items Wrap)
.grid-auto { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); gap: 1.5rem; }
Named Grid 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; }
Tailwind Grid
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> ) }
Responsive Navigation
function ResponsiveNav({ items }) { const [isOpen, setIsOpen] = useState(false) return ( <nav className="relative"> {/* Mobile toggle */} <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( // Mobile: dropdown "absolute top-full left-0 right-0 bg-background border-b", "flex flex-col", isOpen ? "flex" : "hidden", // Desktop: horizontal, always visible "lg:static lg:flex lg:flex-row lg:border-0" )} > {items.map(item => ( <li key={item.href}> <a href={item.href} className="block px-4 py-3 lg:px-3 lg:py-2 hover:bg-muted lg:hover:bg-transparent" > {item.label} </a> </li> ))} </ul> </nav> ) }
Responsive Images
Art Direction with Picture
function ResponsiveHero() { return ( <picture> {/* 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" /> <img src="/hero-mobile.jpg" alt="Hero description" className="w-full h-auto" loading="eager" fetchPriority="high" /> </picture> ) }
Resolution Switching with srcset
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" /> ) }
Responsive Tables
Horizontal Scroll Pattern
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 Layout on Mobile
function ResponsiveDataTable({ data, columns }) { return ( <> {/* Desktop: table */} <table className="hidden md:table w-full"> {/* standard table markup */} </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 - problematic on mobile */ .full-height { height: 100vh; } /* Dynamic viewport units (recommended) */ .full-height-dynamic { height: 100dvh; } /* Accounts for mobile browser UI */ /* Small viewport (minimum when UI shown) */ .min-full-height { min-height: 100svh; } /* Large viewport (maximum when UI hidden) */ .max-full-height { max-height: 100lvh; }
Best Practices
- Mobile-First: Write base styles for mobile, enhance for larger screens
- Content Breakpoints: Set breakpoints where content breaks, not device sizes
- Fluid Over Fixed: Prefer
and relative units over fixedclamp()px - Container Queries: Use for component-level responsiveness
- Touch Targets: Minimum 44×44px tap targets on mobile
- Test Real Devices: Simulators don't catch all issues
- Logical Properties: Use
/inline
for internationalizationblock
Common Issues
| Issue | Cause | Fix |
|---|---|---|
| Horizontal scroll | Fixed widths | Use relative units, |
| 100vh too tall on mobile | Address bar | Use or |
| Tiny tap targets | Desktop design | Min 44px height/width on interactive elements |
| Images breaking layout | Missing constraints | Add |
| Text too small | Fixed font size | Use fluid typography with |
NEVER
- Use
for typography (usepx
)rem - Skip mobile testing on real devices
- Forget touch target sizing (44×44px minimum)
- Use
on mobile without fallback100vh - Nest too many media queries (flattens readability)
- Ignore content-based breakpoints in favor of device-specific ones