Comprehensive mobile-first responsive design patterns with 2025/2026 best practices for Tailwind CSS v4
Provides mobile-first responsive design patterns with fluid typography, container queries, and touch-friendly components.
npx claudepluginhub josiahsiegel/claude-plugin-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Mobile-first design is the industry standard for 2025/2026. With mobile traffic consistently exceeding 60% of global web traffic and Google's mobile-first indexing, starting with mobile ensures optimal user experience and SEO performance.
<!-- CORRECT: Mobile-first (progressive enhancement) -->
<div class="text-sm md:text-base lg:text-lg">
Start small, enhance upward
</div>
<!-- INCORRECT: Desktop-first (graceful degradation) -->
<div class="lg:text-lg md:text-base text-sm">
Starts large, reduces down (more code, more bugs)
</div>
Key Principle: Unprefixed utilities apply to ALL screen sizes. Breakpoint prefixes apply at that size AND ABOVE.
| Prefix | Min-width | Target Devices |
|---|---|---|
| (none) | 0px | All mobile phones (base) |
sm: | 640px (40rem) | Large phones, small tablets |
md: | 768px (48rem) | Tablets (portrait) |
lg: | 1024px (64rem) | Tablets (landscape), laptops |
xl: | 1280px (80rem) | Desktops |
2xl: | 1536px (96rem) | Large desktops |
Best Practice 2025/2026: Let content determine breakpoints, not device dimensions.
@theme {
/* Override defaults based on YOUR content needs */
--breakpoint-sm: 36rem; /* 576px - when your content needs more space */
--breakpoint-md: 48rem; /* 768px */
--breakpoint-lg: 62rem; /* 992px - common content width */
--breakpoint-xl: 75rem; /* 1200px */
--breakpoint-2xl: 90rem; /* 1440px */
/* Add custom breakpoints for specific content needs */
--breakpoint-xs: 20rem; /* 320px - very small devices */
--breakpoint-3xl: 120rem; /* 1920px - ultra-wide */
}
<!-- Cover the most common device ranges (2025/2026 data) -->
<!-- 375px-430px: ~50% of mobile devices (iPhone, modern Android) -->
<div class="px-4">Mobile base</div>
<!-- 768px+: Tablets and small laptops -->
<div class="px-4 md:px-6">Tablet enhancement</div>
<!-- 1024px+: Desktop experience -->
<div class="px-4 md:px-6 lg:px-8">Desktop enhancement</div>
<!-- 1440px+: Wide desktop experience -->
<div class="px-4 md:px-6 lg:px-8 xl:px-12">Wide desktop</div>
Fluid typography eliminates jarring size jumps between breakpoints:
@theme {
/* Fluid typography scale */
--text-fluid-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
--text-fluid-sm: clamp(0.875rem, 0.8rem + 0.375vw, 1rem);
--text-fluid-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
--text-fluid-lg: clamp(1.125rem, 1rem + 0.625vw, 1.25rem);
--text-fluid-xl: clamp(1.25rem, 1rem + 1.25vw, 1.5rem);
--text-fluid-2xl: clamp(1.5rem, 1.1rem + 2vw, 2rem);
--text-fluid-3xl: clamp(1.875rem, 1.2rem + 3.375vw, 2.5rem);
--text-fluid-4xl: clamp(2.25rem, 1rem + 6.25vw, 3.5rem);
--text-fluid-5xl: clamp(3rem, 1rem + 10vw, 5rem);
}
Important for Accessibility: Always combine vw with rem to respect user zoom preferences (WCAG compliance).
<!-- Fluid heading that scales smoothly -->
<h1 class="text-fluid-4xl font-bold leading-tight">
Responsive Heading
</h1>
<!-- Fluid body text -->
<p class="text-fluid-base leading-relaxed max-w-prose">
Body text that scales proportionally with the viewport
while respecting user's font size preferences.
</p>
<!-- Fluid with breakpoint overrides for fine control -->
<h2 class="text-fluid-2xl lg:text-fluid-3xl font-semibold">
Section Title
</h2>
@theme {
/* Fluid spacing scale */
--spacing-fluid-xs: clamp(0.25rem, 0.2rem + 0.25vw, 0.5rem);
--spacing-fluid-sm: clamp(0.5rem, 0.4rem + 0.5vw, 1rem);
--spacing-fluid-md: clamp(1rem, 0.75rem + 1.25vw, 2rem);
--spacing-fluid-lg: clamp(1.5rem, 1rem + 2.5vw, 3rem);
--spacing-fluid-xl: clamp(2rem, 1.25rem + 3.75vw, 4rem);
--spacing-fluid-2xl: clamp(3rem, 1.5rem + 7.5vw, 6rem);
--spacing-fluid-section: clamp(4rem, 2rem + 10vw, 8rem);
}
<!-- Fluid section padding -->
<section class="py-fluid-section px-fluid-md">
<div class="max-w-7xl mx-auto">
<h2 class="mb-fluid-lg">Section Title</h2>
<div class="grid gap-fluid-md grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
<!-- Cards -->
</div>
</div>
</section>
<!-- Hero section with fluid spacing -->
<header class="min-h-screen flex items-center py-fluid-2xl px-fluid-md">
<div class="max-w-4xl">
<h1 class="text-fluid-5xl mb-fluid-md">Hero Title</h1>
<p class="text-fluid-lg mb-fluid-lg">Hero description</p>
<button class="px-fluid-md py-fluid-sm">Get Started</button>
</div>
</header>
Minimum sizes (2025 standards):
<!-- WCAG 2.2 compliant touch targets -->
<!-- Minimum AA compliant (24px) -->
<button class="min-h-6 min-w-6 p-1">
<svg class="h-4 w-4">...</svg>
</button>
<!-- Recommended size (44px) -->
<button class="min-h-11 min-w-11 p-2.5">
<svg class="h-6 w-6">...</svg>
<span class="sr-only">Action</span>
</button>
<!-- Optimal for primary actions (48px) -->
<button class="min-h-12 min-w-12 px-6 py-3 text-base">
Primary Action
</button>
<!-- Extend touch target beyond visible element -->
<a href="/link" class="relative inline-block text-sm">
Small visible link
<!-- Invisible extended touch area -->
<span class="absolute -inset-3" aria-hidden="true"></span>
</a>
<!-- Icon button with extended target -->
<button class="relative p-2 -m-2 rounded-lg hover:bg-gray-100">
<svg class="h-5 w-5" aria-hidden="true">...</svg>
<span class="sr-only">Close</span>
</button>
<!-- Adequate spacing between touch targets (minimum 8px gap) -->
<nav class="flex gap-3">
<a href="#" class="min-h-11 px-4 py-2.5">Home</a>
<a href="#" class="min-h-11 px-4 py-2.5">About</a>
<a href="#" class="min-h-11 px-4 py-2.5">Contact</a>
</nav>
<!-- Stacked navigation with adequate spacing -->
<nav class="flex flex-col">
<a href="#" class="py-3 px-4 min-h-11 border-b border-gray-100">Link 1</a>
<a href="#" class="py-3 px-4 min-h-11 border-b border-gray-100">Link 2</a>
<a href="#" class="py-3 px-4 min-h-11">Link 3</a>
</nav>
<!-- Button group with safe spacing -->
<div class="flex flex-wrap gap-3">
<button class="min-h-11 px-4 py-2">Cancel</button>
<button class="min-h-11 px-4 py-2 bg-blue-600 text-white">Confirm</button>
</div>
Container queries enable component-level responsiveness, independent of viewport size.
@import "tailwindcss";
@plugin "@tailwindcss/container-queries";
| Class | Min-width |
|---|---|
@xs | 20rem (320px) |
@sm | 24rem (384px) |
@md | 28rem (448px) |
@lg | 32rem (512px) |
@xl | 36rem (576px) |
@2xl | 42rem (672px) |
@3xl | 48rem (768px) |
@4xl | 56rem (896px) |
@5xl | 64rem (1024px) |
<!-- Card that responds to its container, not viewport -->
<article class="@container">
<div class="
flex flex-col @sm:flex-row
gap-4 p-4
bg-white rounded-xl shadow-sm
">
<img
src="..."
class="
w-full @sm:w-32 @lg:w-48
aspect-video @sm:aspect-square
object-cover rounded-lg
"
/>
<div class="flex-1 min-w-0">
<h3 class="text-base @md:text-lg @lg:text-xl font-semibold truncate">
Card Title
</h3>
<p class="
text-sm @md:text-base
text-gray-600
line-clamp-2 @lg:line-clamp-3
mt-2
">
Description that adapts to available space...
</p>
<div class="mt-4 flex flex-wrap gap-2 @md:gap-3">
<span class="text-xs @md:text-sm px-2 py-1 bg-gray-100 rounded">Tag</span>
</div>
</div>
</div>
</article>
<div class="@container/sidebar w-64">
<nav class="
flex flex-col @lg/sidebar:flex-row
@lg/sidebar:items-center
gap-2 @lg/sidebar:gap-4
">
<a href="#">Link 1</a>
<a href="#">Link 2</a>
</nav>
</div>
<main class="@container/main flex-1">
<div class="
grid grid-cols-1
@md/main:grid-cols-2
@xl/main:grid-cols-3
gap-6
">
<!-- Grid items respond to main container -->
</div>
</main>
| Use Container Queries | Use Viewport Queries |
|---|---|
| Reusable components | Page-level layouts |
| Sidebar widgets | Navigation bars |
| Card grids | Hero sections |
| Embedded content | Full-width sections |
| CMS/dynamic content | Fixed app shells |
<!-- Auto-responsive grid with minimum card width -->
<div class="
grid gap-6
grid-cols-1
sm:grid-cols-2
lg:grid-cols-3
xl:grid-cols-4
">
<!-- Cards automatically flow -->
</div>
<!-- CSS Grid auto-fit (no breakpoints needed) -->
<div class="grid grid-cols-[repeat(auto-fit,minmax(280px,1fr))] gap-6">
<!-- Cards auto-fit with 280px minimum -->
</div>
<!-- CSS Grid auto-fill for fixed-size items -->
<div class="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-4">
<!-- Fixed minimum size, grows to fill -->
</div>
<!-- Stack to row -->
<div class="flex flex-col md:flex-row gap-4">
<div class="flex-1">Content 1</div>
<div class="flex-1">Content 2</div>
</div>
<!-- Wrap with centered items -->
<div class="flex flex-wrap justify-center gap-4">
<div class="w-full sm:w-auto">Item 1</div>
<div class="w-full sm:w-auto">Item 2</div>
<div class="w-full sm:w-auto">Item 3</div>
</div>
<!-- Mobile stack, desktop inline with push -->
<div class="flex flex-col sm:flex-row sm:items-center gap-4">
<div class="flex-1">
<h3>Title</h3>
<p class="text-sm text-gray-600">Description</p>
</div>
<button class="sm:ml-auto min-h-11 px-4 py-2">Action</button>
</div>
<!-- Collapsible sidebar -->
<div class="flex min-h-screen">
<!-- Sidebar: hidden on mobile, visible on desktop -->
<aside class="
hidden lg:flex lg:flex-col
w-64 border-r bg-gray-50
">
<nav class="flex-1 p-4">...</nav>
</aside>
<!-- Main content -->
<main class="flex-1 p-4 lg:p-8">
Content
</main>
</div>
<!-- Mobile drawer + desktop sidebar -->
<div class="relative flex min-h-screen">
<!-- Mobile drawer overlay -->
<div class="
fixed inset-0 z-40 lg:hidden
bg-black/50
data-[open=false]:hidden
" data-open="false">
<aside class="w-64 h-full bg-white">
Mobile navigation
</aside>
</div>
<!-- Desktop sidebar -->
<aside class="hidden lg:block w-64 border-r">
Desktop navigation
</aside>
<main class="flex-1">Content</main>
</div>
<div class="min-h-screen grid grid-rows-[auto_1fr_auto]">
<!-- Header -->
<header class="sticky top-0 z-50 h-16 bg-white border-b shadow-sm">
<nav class="h-full max-w-7xl mx-auto px-4 flex items-center justify-between">
<Logo />
<ul class="hidden md:flex gap-6">...</ul>
<button class="md:hidden min-h-11 min-w-11">Menu</button>
</nav>
</header>
<!-- Main content area with optional sidebars -->
<div class="
grid
grid-cols-1
md:grid-cols-[240px_1fr]
lg:grid-cols-[240px_1fr_280px]
gap-0
">
<nav class="hidden md:block border-r p-4">Left Nav</nav>
<main class="p-4 md:p-6 lg:p-8">Main Content</main>
<aside class="hidden lg:block border-l p-4">Right Sidebar</aside>
</div>
<!-- Footer -->
<footer class="bg-gray-900 text-white py-8 md:py-12">
Footer content
</footer>
</div>
<!-- Maintain aspect ratio across all sizes -->
<div class="aspect-video overflow-hidden rounded-lg">
<img src="..." class="w-full h-full object-cover" loading="lazy" />
</div>
<!-- Responsive aspect ratio -->
<div class="aspect-square sm:aspect-video lg:aspect-[4/3] overflow-hidden">
<img src="..." class="w-full h-full object-cover" />
</div>
<!-- Art direction with picture element -->
<picture>
<source media="(min-width: 1024px)" srcset="large.jpg" />
<source media="(min-width: 640px)" srcset="medium.jpg" />
<img
src="small.jpg"
alt="Description"
class="w-full h-auto rounded-lg"
loading="lazy"
/>
</picture>
<!-- Responsive srcset -->
<img
src="image-800.jpg"
srcset="
image-400.jpg 400w,
image-800.jpg 800w,
image-1200.jpg 1200w,
image-1600.jpg 1600w
"
sizes="
(min-width: 1280px) 1200px,
(min-width: 768px) 80vw,
100vw
"
alt="Responsive image"
class="w-full h-auto"
loading="lazy"
/>
<!-- Mobile-first heading scale -->
<h1 class="
text-2xl sm:text-3xl md:text-4xl lg:text-5xl xl:text-6xl
font-bold leading-tight tracking-tight
">
Main Heading
</h1>
<h2 class="
text-xl sm:text-2xl md:text-3xl lg:text-4xl
font-semibold leading-snug
">
Section Heading
</h2>
<h3 class="
text-lg sm:text-xl md:text-2xl
font-medium
">
Subsection Heading
</h3>
<!-- Optimal line length and spacing -->
<article class="max-w-prose mx-auto">
<p class="
text-base md:text-lg
leading-relaxed md:leading-loose
text-gray-700 dark:text-gray-300
">
Body text optimized for readability with 45-75 characters per line.
</p>
</article>
<!-- Text that balances across lines -->
<h2 class="text-balance text-2xl md:text-3xl font-bold max-w-2xl">
This heading will balance text across lines to avoid orphans
</h2>
<nav class="relative">
<!-- Desktop navigation -->
<ul class="hidden md:flex items-center gap-6">
<li><a href="#" class="py-2 hover:text-blue-600">Home</a></li>
<li><a href="#" class="py-2 hover:text-blue-600">Products</a></li>
<li><a href="#" class="py-2 hover:text-blue-600">About</a></li>
<li><a href="#" class="py-2 hover:text-blue-600">Contact</a></li>
</ul>
<!-- Mobile menu button -->
<button
class="md:hidden min-h-11 min-w-11 p-2"
aria-expanded="false"
aria-controls="mobile-menu"
aria-label="Toggle navigation menu"
>
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
</svg>
</button>
<!-- Mobile menu (controlled via JS) -->
<div
id="mobile-menu"
class="
md:hidden
absolute top-full left-0 right-0
bg-white shadow-lg border-t
hidden
"
>
<ul class="py-2">
<li><a href="#" class="block px-4 py-3 min-h-11 hover:bg-gray-50">Home</a></li>
<li><a href="#" class="block px-4 py-3 min-h-11 hover:bg-gray-50">Products</a></li>
<li><a href="#" class="block px-4 py-3 min-h-11 hover:bg-gray-50">About</a></li>
<li><a href="#" class="block px-4 py-3 min-h-11 hover:bg-gray-50">Contact</a></li>
</ul>
</div>
</nav>
<!-- Fixed bottom navigation for mobile -->
<nav class="
fixed bottom-0 inset-x-0 z-50
md:hidden
bg-white border-t shadow-lg
safe-area-pb
">
<ul class="flex justify-around">
<li>
<a href="#" class="
flex flex-col items-center
min-h-14 min-w-14 px-3 py-2
text-xs
text-gray-600 hover:text-blue-600
aria-current:text-blue-600
">
<svg class="h-6 w-6 mb-1">...</svg>
Home
</a>
</li>
<li>
<a href="#" class="flex flex-col items-center min-h-14 min-w-14 px-3 py-2 text-xs">
<svg class="h-6 w-6 mb-1">...</svg>
Search
</a>
</li>
<li>
<a href="#" class="flex flex-col items-center min-h-14 min-w-14 px-3 py-2 text-xs">
<svg class="h-6 w-6 mb-1">...</svg>
Profile
</a>
</li>
</ul>
</nav>
<!-- Add padding to main content to prevent overlap -->
<main class="pb-20 md:pb-0">
Content
</main>
@utility safe-area-pt {
padding-top: env(safe-area-inset-top);
}
@utility safe-area-pb {
padding-bottom: env(safe-area-inset-bottom);
}
@utility safe-area-pl {
padding-left: env(safe-area-inset-left);
}
@utility safe-area-pr {
padding-right: env(safe-area-inset-right);
}
@utility safe-area-p {
padding-top: env(safe-area-inset-top);
padding-right: env(safe-area-inset-right);
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
}
<!-- Header that respects notch -->
<header class="sticky top-0 safe-area-pt bg-white">
<div class="h-16 flex items-center px-4">
Navigation
</div>
</header>
<!-- Bottom navigation with safe area -->
<nav class="fixed bottom-0 inset-x-0 safe-area-pb bg-white border-t">
Bottom nav
</nav>
<!-- Native lazy loading for images -->
<img src="image.jpg" alt="..." loading="lazy" class="w-full h-auto" />
<!-- Lazy load below-fold content -->
<div class="contents" data-lazy-load>
Heavy component loaded on demand
</div>
@utility content-auto {
content-visibility: auto;
contain-intrinsic-size: auto 500px;
}
<!-- Skip rendering off-screen content -->
<section class="content-auto">
Large section that may be off-screen
</section>
<!-- Show simpler version on slow connections -->
<picture>
<source
srcset="video-poster.jpg"
media="(prefers-reduced-data: reduce)"
/>
<img src="animated.gif" alt="..." />
</picture>
| Practice | Implementation |
|---|---|
| Mobile-first utilities | Unprefixed first, then sm:, md:, lg: |
| Touch targets | min-h-11 min-w-11 (44px minimum) |
| Fluid typography | clamp(min, preferred, max) with rem + vw |
| Fluid spacing | clamp() for padding and margins |
| Container queries | @container for component responsiveness |
| Safe areas | env(safe-area-inset-*) for notched devices |
| Readable text | max-w-prose (65ch) and leading-relaxed |
| Lazy loading | loading="lazy" on below-fold images |
| Touch spacing | gap-3 (12px) minimum between targets |
| Viewport meta | width=device-width, initial-scale=1 |
Expert guidance for Next.js Cache Components and Partial Prerendering (PPR). **PROACTIVE ACTIVATION**: Use this skill automatically when working in Next.js projects that have `cacheComponents: true` in their next.config.ts/next.config.js. When this config is detected, proactively apply Cache Components patterns and best practices to all React Server Component implementations. **DETECTION**: At the start of a session in a Next.js project, check for `cacheComponents: true` in next.config. If enabled, this skill's patterns should guide all component authoring, data fetching, and caching decisions. **USE CASES**: Implementing 'use cache' directive, configuring cache lifetimes with cacheLife(), tagging cached data with cacheTag(), invalidating caches with updateTag()/revalidateTag(), optimizing static vs dynamic content boundaries, debugging cache issues, and reviewing Cache Component implementations.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.