
Tailwind Responsive Ui
- 372 installs
- 191 repo stars
- Updated July 24, 2026
- pproenca/dot-skills
tailwind-responsive-ui: A skill for development.
About
tailwind-responsive-ui: A skill for development. This provides functionality for development workflows.
- tailwind-responsive-ui
Tailwind Responsive Ui by the numbers
- 372 all-time installs (skills.sh)
- +20 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #1,118 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/pproenca/dot-skills --skill tailwind-responsive-uiAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 372 |
|---|---|
| repo stars | ★ 191 |
| Last updated | July 24, 2026 |
| Repository | pproenca/dot-skills ↗ |
How do I use tailwind-responsive-ui for development tasks?
Use tailwind-responsive-ui for development tasks
Who is it for?
Best when you're working on backend & apis and need structured help with tailwind responsive ui.
Skip if: Teams with no backend & apis needs, or anyone wanting a generic chat assistant without this specific workflow.
When should I use this skill?
When you need to use tailwind-responsive-ui for development tasks, or when tailwind-responsive-ui: a skill for development.
What you get
Structured output aligned to tailwind-responsive-ui: tailwind-responsive-ui.
Files
Community Responsive UI Tailwind CSS Best Practices
Comprehensive responsive transformation guide for Tailwind CSS applications, based on Refactoring UI by Adam Wathan & Steve Schoger and modern responsive design patterns. Contains 49 rules across 8 categories, prioritized by impact to guide automated refactoring and code generation.
When to Apply
Reference these guidelines when:
- Making an existing UI responsive across screen sizes
- Building new responsive layouts with Tailwind CSS
- Refactoring desktop-only interfaces for mobile support
- Reviewing responsive code for breakpoint, spacing, and typography issues
- Adapting navigation, forms, and data tables for touch devices
Rule Categories by Priority
| Priority | Category | Impact | Prefix |
|---|---|---|---|
| 1 | Breakpoint Strategy | CRITICAL | bp- |
| 2 | Layout Transformation | CRITICAL | layout- |
| 3 | Responsive Spacing | HIGH | rspac- |
| 4 | Fluid Typography | HIGH | fluid- |
| 5 | Navigation Patterns | MEDIUM-HIGH | nav- |
| 6 | Touch & Interaction | MEDIUM | touch- |
| 7 | Responsive Media | MEDIUM | rmedia- |
| 8 | Data Adaptation | LOW-MEDIUM | data- |
Quick Reference
1. Breakpoint Strategy (CRITICAL)
- `bp-mobile-first-default` - Use Mobile-First Breakpoint Direction
- `bp-content-driven-breakpoints` - Set Breakpoints Where Content Breaks
- `bp-avoid-device-widths` - Avoid Device-Specific Breakpoint Values
- `bp-consolidate-breakpoints` - Consolidate Breakpoints to Three or Four
- `bp-min-width-over-max` - Use min-width Over max-width for Breakpoints
- `bp-debug-breakpoints` - Use Visual Breakpoint Indicators During Development
2. Layout Transformation (CRITICAL)
- `layout-stack-to-row` - Stack Elements on Mobile, Row on Desktop
- `layout-sidebar-collapse` - Collapse Sidebar to Top or Bottom on Mobile
- `layout-grid-column-reduction` - Reduce Grid Columns at Narrower Breakpoints
- `layout-holy-grail-responsive` - Use Responsive Holy Grail Layout with Grid
- `layout-sticky-to-static` - Convert Sticky Elements to Static on Mobile
- `layout-fixed-to-relative` - Replace Fixed Positioning with Relative on Mobile
- `layout-aspect-ratio-containers` - Use Aspect Ratio for Responsive Containers
3. Responsive Spacing (HIGH)
- `rspac-scale-padding-per-bp` - Scale Padding Independently Per Breakpoint
- `rspac-responsive-gap` - Use Responsive Gap for Grid and Flex Spacing
- `rspac-compact-mobile-generous-desktop` - Use Compact Spacing on Mobile, Generous on Desktop
- `rspac-section-spacing` - Scale Section Dividers with Viewport
- `rspac-inline-to-stack-spacing` - Convert Inline Spacing to Stack Spacing on Mobile
- `rspac-container-padding` - Use Responsive Container Padding
4. Fluid Typography (HIGH)
- `fluid-clamp-font-size` - Use clamp() for Fluid Font Sizing
- `fluid-responsive-line-height` - Tighten Line Height as Font Size Increases
- `fluid-responsive-measure` - Constrain Line Length to 45-75 Characters
- `fluid-scale-headings-independently` - Scale Heading Sizes Independently Across Breakpoints
- `fluid-responsive-letter-spacing` - Adjust Letter Spacing for Responsive Headlines
- `fluid-type-scale` - Use a Responsive Type Scale
5. Navigation Patterns (MEDIUM-HIGH)
- `nav-horizontal-to-hamburger` - Collapse Horizontal Nav to Hamburger on Mobile
- `nav-tab-bar-mobile` - Use Bottom Tab Bar for Primary Mobile Navigation
- `nav-breadcrumb-collapse` - Truncate Breadcrumbs on Mobile
- `nav-sidebar-drawer` - Convert Sidebar Nav to Off-Canvas Drawer on Mobile
- `nav-dropdown-to-fullscreen` - Expand Dropdown Menus to Full-Width on Mobile
- `nav-sticky-header-compact` - Compact the Header on Scroll for Mobile
6. Touch & Interaction (MEDIUM)
- `touch-min-touch-target` - Ensure Minimum 44px Touch Targets on Mobile
- `touch-hover-to-tap` - Replace Hover Interactions with Tap-Friendly Alternatives
- `touch-swipe-affordance` - Add Visual Swipe Affordances for Horizontal Scrolling
- `touch-scroll-snap-mobile` - Use Scroll Snap for Carousel-Like Mobile Interfaces
- `touch-input-sizing-mobile` - Size Form Inputs to Prevent iOS Zoom
- `touch-focus-visible-touch` - Use focus-visible for Touch-Friendly Focus Styles
7. Responsive Media (MEDIUM)
- `rmedia-responsive-images` - Use Responsive Image Sizing with Object-Fit
- `rmedia-video-aspect-ratio` - Maintain Video Aspect Ratio Across Breakpoints
- `rmedia-icon-size-scaling` - Scale Icons Per Breakpoint, Not with Font Size
- `rmedia-avatar-responsive` - Scale Avatar Sizes Per Context and Breakpoint
- `rmedia-background-image-bp` - Swap Background Images at Breakpoints
- `rmedia-embed-responsive` - Make Embedded Content Responsive with Container Constraints
8. Data Adaptation (LOW-MEDIUM)
- `data-table-to-cards` - Transform Tables to Cards on Mobile
- `data-horizontal-scroll-table` - Use Horizontal Scroll for Dense Data Tables
- `data-responsive-data-grid` - Adapt Data Grid Density for Screen Size
- `data-list-density-mobile` - Increase List Item Density on Mobile
- `data-truncate-overflow` - Truncate Overflowing Text on Mobile
- `data-responsive-form-layout` - Stack Form Fields on Mobile, Use Grid on Desktop
How to Use
Read individual reference files for detailed explanations and code examples:
- Section definitions - Category structure and impact levels
- Rule template - Template for adding new rules
Reference Files
| File | Description |
|---|---|
| references/_sections.md | Category definitions and ordering |
| assets/templates/_template.md | Template for new rules |
| metadata.json | Version and reference information |
Rule Title Here
{1-3 sentences explaining WHY this matters for responsive design. Focus on what breaks without this pattern.}
Incorrect ({what's wrong}):
{Bad code example - production-realistic Tailwind HTML}
{<!-- Comments explaining the problem -->}Correct ({what's right}):
{Good code example - minimal diff from incorrect}
{<!-- Comments explaining the benefit -->}{Optional: Alternative approach, when NOT to use, benefits list, reference links}
{
"version": "1.0.4",
"organization": "Community",
"technology": "Tailwind CSS",
"date": "February 2026",
"abstract": "Comprehensive responsive UI transformation guide for Tailwind CSS applications, designed for AI agents and LLMs. Contains 49 rules across 8 categories, prioritized by impact from critical (breakpoint strategy, layout transformation) to incremental (data adaptation). Each rule includes detailed explanations, real-world examples comparing incorrect vs. correct implementations with Tailwind utility classes, and specific impact metrics to guide automated refactoring and code generation. Based on Refactoring UI by Adam Wathan & Steve Schoger and modern responsive design patterns.",
"references": [
"https://tailwindcss.com/docs/responsive-design",
"https://tailwindcss.com/docs/container-queries",
"https://web.dev/responsive-web-design-basics/",
"https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html",
"https://every-layout.dev"
]
}
Sections
This file defines all sections, their ordering, impact levels, and descriptions. The section ID (in parentheses) is the filename prefix used to group rules.
---
1. Breakpoint Strategy (bp)
Impact: CRITICAL Description: Breakpoint decisions cascade through every responsive rule. Wrong strategy (device-based, too many, max-width) forces rewrites across the entire codebase. Content-driven, consolidated breakpoints reduce CSS output by 30-50%.
2. Layout Transformation (layout)
Impact: CRITICAL Description: Layout shifts are the most visible responsive change and the #1 cause of broken mobile UIs. Stack-to-row, sidebar collapse, and grid reduction patterns determine whether the interface works or breaks at each breakpoint.
3. Responsive Spacing (rspac)
Impact: HIGH Description: Spacing that scales proportionally looks wrong — mobile gets cramped, desktop gets sparse. Independent spacing per breakpoint maintains visual balance and content density across screen sizes.
4. Fluid Typography (fluid)
Impact: HIGH Description: Fixed font sizes create unreadable text on small screens and wasted space on large ones. Fluid type scaling with clamp(), responsive line-height, and adaptive measure keeps text readable at every width.
5. Navigation Patterns (nav)
Impact: MEDIUM-HIGH Description: Navigation is the most complex responsive component. A horizontal nav that doesn't collapse properly is the fastest way to break a mobile layout. Pattern selection (hamburger, tab bar, drawer) depends on item count and depth.
6. Touch & Interaction (touch)
Impact: MEDIUM Description: Desktop hover states don't exist on mobile. Touch targets below 44px cause mis-taps. Responsive interaction patterns ensure usability across input methods without duplicating code.
7. Responsive Media (rmedia)
Impact: MEDIUM Description: Unresponsive images and embeds overflow containers, waste bandwidth, and break layouts. Proper srcset, aspect ratios, and adaptive sizing keep media performant and contained.
8. Data Adaptation (data)
Impact: LOW-MEDIUM Description: Wide tables and dense data grids are unusable on mobile. Card transformations, horizontal scrolling, and density adjustments make data-heavy interfaces work on small screens.
Avoid Device-Specific Breakpoint Values
Device widths change yearly -- foldables, new tablet sizes, and manufacturer-specific resolutions make device-targeting a losing game. There are over 15,000 distinct screen sizes in active use. Designing for specific devices (iPhone 15, iPad Air, MacBook Pro) creates fragile layouts that silently break when the next hardware generation ships with a slightly different viewport.
Incorrect (device-specific breakpoints tied to hardware):
@theme {
--breakpoint-phone: 375px; /* iPhone 15 */
--breakpoint-phone-lg: 430px; /* iPhone 15 Pro Max */
--breakpoint-tablet: 768px; /* iPad Mini */
--breakpoint-tablet-lg: 1024px; /* iPad Pro 11" */
--breakpoint-laptop: 1440px; /* MacBook Pro 14" */
}<!-- Tied to specific Apple devices — breaks on Samsung, Pixel, Surface -->
<header class="phone:px-4 tablet:px-8 laptop:px-16 flex items-center justify-between">
<span class="phone:text-sm tablet:text-base laptop:text-lg font-semibold">Acme Inc</span>
<nav class="phone:hidden tablet:flex gap-6">
<a href="/products" class="text-gray-700">Products</a>
<a href="/pricing" class="text-gray-700">Pricing</a>
</nav>
</header>Correct (content-oriented breakpoint ranges):
<!-- Tailwind defaults (640, 768, 1024, 1280) are content-range oriented,
not device-specific — they work across all screen sizes -->
<header class="flex items-center justify-between px-4 sm:px-6 lg:px-16">
<span class="text-sm font-semibold sm:text-base lg:text-lg">Acme Inc</span>
<nav class="hidden sm:flex gap-6">
<a href="/products" class="text-gray-700">Products</a>
<a href="/pricing" class="text-gray-700">Pricing</a>
</nav>
</header>When custom breakpoints are needed, define them by content behavior, not device names:
@theme {
--breakpoint-content-narrow: 32rem; /* where single-column content fits */
--breakpoint-content-wide: 64rem; /* where multi-column layout works */
}Consolidate Breakpoints to Three or Four
Each breakpoint multiplies your responsive utility count. A component with 5 responsive properties across 7 breakpoints generates 35 class variants instead of 15. More than 4 breakpoints creates exponential CSS complexity, makes templates unreadable, and increases the chance of conflicting rules at adjacent breakpoints. Most layouts only need three distinct states: compact (mobile), medium (tablet), and expanded (desktop).
Incorrect (too many breakpoints with narrow ranges):
@theme {
--breakpoint-xs: 375px;
--breakpoint-sm: 480px;
--breakpoint-md: 640px;
--breakpoint-lg: 768px;
--breakpoint-xl: 1024px;
--breakpoint-2xl: 1280px;
--breakpoint-3xl: 1536px;
}<!-- 7 breakpoints — every element needs 7 responsive variants -->
<section class="px-2 xs:px-3 sm:px-4 md:px-6 lg:px-8 xl:px-12 2xl:px-16 3xl:px-20">
<h2 class="text-lg xs:text-xl sm:text-xl md:text-2xl lg:text-2xl xl:text-3xl 2xl:text-4xl 3xl:text-5xl font-bold">
Our Features
</h2>
<div class="grid grid-cols-1 xs:grid-cols-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3 2xl:grid-cols-4 3xl:grid-cols-4 gap-4 md:gap-6 xl:gap-8">
<!-- Every card, button, and text element repeats this pattern -->
</div>
</section>Correct (3 well-chosen breakpoints covering mobile/tablet/desktop):
<!-- Tailwind defaults: sm (640), md (768), lg (1024) cover 95% of layouts.
Add xl (1280) only for wide-screen content grids. -->
<section class="px-4 md:px-8 lg:px-16">
<h2 class="text-xl font-bold md:text-3xl lg:text-4xl">
Our Features
</h2>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 lg:gap-8">
<div class="rounded-xl border border-gray-200 p-5">
<h3 class="font-semibold">Fast Deploys</h3>
<p class="mt-2 text-sm text-gray-600">Ship to production in under 30 seconds.</p>
</div>
<!-- Fewer variants = easier to read, maintain, and debug -->
</div>
</section>Set Breakpoints Where Content Breaks
Don't choose breakpoints based on popular device widths. Instead, resize your content until it looks bad -- text overflows, cards stack awkwardly, or whitespace becomes excessive -- and set a breakpoint there. Device-specific breakpoints leave gaps where content breaks but no breakpoint catches it, causing layout failures on the thousands of screen sizes between standard device widths.
Incorrect (device-width breakpoints miss content overflow):
<!-- Breakpoints at 768px and 1024px, but the 3-column card grid
actually breaks at ~680px where cards get too narrow to read -->
<div class="grid grid-cols-3 gap-6 max-[768px]:grid-cols-1 px-8">
<div class="rounded-lg border border-gray-200 p-6">
<h3 class="text-lg font-semibold">Annual Plan</h3>
<!-- At 700px: 3 columns still active, cards are 190px wide,
text wraps badly, price overflows -->
<p class="text-3xl font-bold">$199/year</p>
<p class="text-gray-600">Best for teams that need unlimited access to all features</p>
</div>
<!-- ...two more cards -->
</div>Correct (breakpoint set where content actually breaks):
/* tailwind.config — or @theme in v4 */
@theme {
--breakpoint-cards: 42rem; /* ~672px — where 3-col cards get too narrow */
}<!-- Custom breakpoint matches where the content stops working -->
<div class="grid grid-cols-1 gap-6 px-4 cards:grid-cols-3 cards:gap-6 cards:px-8">
<div class="rounded-lg border border-gray-200 p-6">
<h3 class="text-lg font-semibold">Annual Plan</h3>
<p class="text-3xl font-bold">$199/year</p>
<p class="text-gray-600">Best for teams that need unlimited access to all features</p>
</div>
<!-- ...two more cards — columns kick in exactly when cards have room -->
</div>Use Visual Breakpoint Indicators During Development
When testing responsive layouts, manually resizing the browser and guessing which breakpoint is active leads to missed bugs -- you think you are testing md: but you are actually 2px into lg:. A small fixed indicator that shows the current active breakpoint removes guesswork and catches missing responsive styles before they reach production.
Incorrect (guessing which breakpoint is active while resizing):
<!-- No breakpoint indicator — developer resizes browser and guesses -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 p-4">
<div class="rounded-lg border p-4">Card 1</div>
<div class="rounded-lg border p-4">Card 2</div>
<div class="rounded-lg border p-4">Card 3</div>
<!-- Bug: missing md:grid-cols-2 override, but developer can't tell
when they've crossed from sm to md to lg while dragging -->
</div>Correct (dev-only breakpoint indicator in corner):
<!-- Drop this component in your layout during development, remove before production.
Only one label is visible at a time using Tailwind's hidden/block utilities. -->
<div class="fixed bottom-1 left-1 z-50 flex h-6 items-center rounded-full bg-gray-900 px-2.5 text-xs font-mono text-white opacity-80">
<span class="sm:hidden">xs</span>
<span class="hidden sm:block md:hidden">sm</span>
<span class="hidden md:block lg:hidden">md</span>
<span class="hidden lg:block xl:hidden">lg</span>
<span class="hidden xl:block 2xl:hidden">xl</span>
<span class="hidden 2xl:block">2xl</span>
</div>
<!-- Now the grid bug is immediately obvious:
the indicator shows "md" but grid is still 2-col from sm -->
<div class="grid grid-cols-1 gap-4 p-4 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3">
<div class="rounded-lg border p-4">Card 1</div>
<div class="rounded-lg border p-4">Card 2</div>
<div class="rounded-lg border p-4">Card 3</div>
</div>Tip: Wrap the indicator in a conditional so it is automatically excluded from production builds:
<!-- Vite example: only rendered in dev mode -->
<div v-if="import.meta.env.DEV" class="fixed bottom-1 left-1 z-50 ...">
<!-- breakpoint labels -->
</div>Use min-width Over max-width for Breakpoints
max-width breakpoints create a cascade hazard: when you add a new intermediate breakpoint, existing max-width rules may overlap and produce invisible bugs where desktop styles leak into tablet views. min-width builds upward — you only add properties as screen space grows, so new breakpoints never break existing ones. While bp-mobile-first-default covers the general direction, this rule focuses on the specific cascade and specificity problems max-width introduces.
Incorrect (max-width overriding desktop defaults downward):
<!-- Desktop-first: base = complex layout, then strip it away going down -->
<div class="flex gap-8 max-[1024px]:gap-6 max-[768px]:gap-4 max-[768px]:flex-col">
<aside class="w-72 shrink-0 max-[1024px]:w-60 max-[768px]:w-full">
<ul class="space-y-2">
<li><a href="#" class="block rounded-md bg-blue-50 px-4 py-2 text-blue-700 max-[768px]:bg-transparent max-[768px]:px-0 max-[768px]:py-1">Overview</a></li>
<li><a href="#" class="block rounded-md px-4 py-2 text-gray-600 max-[768px]:px-0 max-[768px]:py-1">Analytics</a></li>
<li><a href="#" class="block rounded-md px-4 py-2 text-gray-600 max-[768px]:px-0 max-[768px]:py-1">Settings</a></li>
</ul>
</aside>
<main class="flex-1 max-[768px]:border-t max-[768px]:border-l-0 border-l border-gray-200 max-[768px]:pl-0 max-[768px]:pt-4 pl-8">
<h1 class="text-2xl font-bold max-[768px]:text-lg">Dashboard</h1>
</main>
</div>Correct (min-width building upward from simple base):
<!-- Mobile-first: base = stacked layout, add complexity as space grows -->
<div class="flex flex-col gap-4 md:flex-row md:gap-8">
<aside class="w-full md:w-60 lg:w-72 md:shrink-0">
<ul class="space-y-2">
<li><a href="#" class="block py-1 text-blue-700 md:rounded-md md:bg-blue-50 md:px-4 md:py-2">Overview</a></li>
<li><a href="#" class="block py-1 text-gray-600 md:rounded-md md:px-4 md:py-2">Analytics</a></li>
<li><a href="#" class="block py-1 text-gray-600 md:rounded-md md:px-4 md:py-2">Settings</a></li>
</ul>
</aside>
<main class="flex-1 border-t border-gray-200 pt-4 md:border-l md:border-t-0 md:pl-8 md:pt-0">
<h1 class="text-lg font-bold md:text-2xl">Dashboard</h1>
</main>
</div>Use Mobile-First Breakpoint Direction
Mobile-first (min-width) means base styles cover the smallest screen, and you layer complexity upward. Tailwind's default breakpoints (sm:, md:, lg:) are already mobile-first, so your unsuffixed utilities become the mobile baseline. Fighting this direction with desktop-first overrides doubles your responsive declarations and introduces fragile max-* rules that break when new breakpoints are added.
Incorrect (desktop-first with max-width overrides):
<!-- Base styles assume desktop, then override downward — doubles the work -->
<div class="max-[1024px]:flex-col max-[1024px]:px-4 max-[768px]:px-2 flex-row px-12 gap-8 max-[768px]:gap-4">
<aside class="w-64 max-[1024px]:w-full max-[1024px]:border-b max-[1024px]:border-r-0 border-r border-gray-200">
<nav class="flex max-[1024px]:flex-row flex-col gap-2 max-[768px]:gap-1">
<a href="/dashboard" class="px-4 py-2 text-sm">Dashboard</a>
<a href="/settings" class="px-4 py-2 text-sm">Settings</a>
</nav>
</aside>
<main class="flex-1 max-[1024px]:pt-4">
<h1 class="text-3xl max-[768px]:text-xl">Welcome back</h1>
</main>
</div>Correct (mobile-first with progressive enhancement):
<!-- Base = mobile, layer complexity upward with sm:/md:/lg: -->
<div class="flex flex-col gap-4 px-2 md:flex-row md:gap-8 md:px-12">
<aside class="w-full border-b border-gray-200 md:w-64 md:border-b-0 md:border-r">
<nav class="flex flex-row gap-1 md:flex-col md:gap-2">
<a href="/dashboard" class="px-4 py-2 text-sm">Dashboard</a>
<a href="/settings" class="px-4 py-2 text-sm">Settings</a>
</nav>
</aside>
<main class="flex-1 md:pt-0">
<h1 class="text-xl md:text-3xl">Welcome back</h1>
</main>
</div>Note: This rule covers the full-page layout implications of mobile-first direction. See also tailwind/resp-mobile-first for the basic syntax pattern.
Use Horizontal Scroll for Dense Data Tables
When table data must stay in tabular format — financial data, comparison matrices, spreadsheet-like views — breaking the table into cards destroys the column alignment that gives the data meaning. Instead, wrap the table in a horizontally scrollable container so users can pan through columns while row headers stay contextually linked. This preserves data integrity without shrinking columns to unreadable widths.
Incorrect (table shrinks columns until text wraps and becomes unreadable):
<!-- Columns compress to fit 375px — text wraps, numbers misalign, layout breaks -->
<div class="px-4">
<table class="w-full border-collapse text-sm">
<thead>
<tr class="border-b bg-gray-50 text-left text-xs font-medium uppercase text-gray-500">
<th class="px-3 py-3">Product</th>
<th class="px-3 py-3">Q1 Revenue</th>
<th class="px-3 py-3">Q2 Revenue</th>
<th class="px-3 py-3">Q3 Revenue</th>
<th class="px-3 py-3">Q4 Revenue</th>
<th class="px-3 py-3">YoY Growth</th>
<th class="px-3 py-3">Margin</th>
</tr>
</thead>
<tbody>
<tr class="border-b">
<td class="px-3 py-3 font-medium text-gray-900">Enterprise Plan</td>
<td class="px-3 py-3 text-gray-600">$1,245,000</td>
<td class="px-3 py-3 text-gray-600">$1,389,200</td>
<td class="px-3 py-3 text-gray-600">$1,502,750</td>
<td class="px-3 py-3 text-gray-600">$1,678,400</td>
<td class="px-3 py-3 text-green-600">+34.8%</td>
<td class="px-3 py-3 text-gray-600">72.3%</td>
</tr>
<tr class="border-b">
<td class="px-3 py-3 font-medium text-gray-900">Starter Plan</td>
<td class="px-3 py-3 text-gray-600">$423,100</td>
<td class="px-3 py-3 text-gray-600">$456,800</td>
<td class="px-3 py-3 text-gray-600">$498,200</td>
<td class="px-3 py-3 text-gray-600">$512,600</td>
<td class="px-3 py-3 text-green-600">+21.2%</td>
<td class="px-3 py-3 text-gray-600">68.1%</td>
</tr>
</tbody>
</table>
</div>Correct (horizontal scroll container with edge-to-edge bleed on mobile):
<!-- Negative margins let the table bleed to screen edges on mobile for maximum width -->
<div class="overflow-x-auto -mx-4 px-4 md:mx-0 md:px-0">
<table class="min-w-[700px] w-full border-collapse text-sm">
<thead>
<tr class="border-b bg-gray-50 text-left text-xs font-medium uppercase text-gray-500">
<th class="whitespace-nowrap px-3 py-3">Product</th>
<th class="whitespace-nowrap px-3 py-3">Q1 Revenue</th>
<th class="whitespace-nowrap px-3 py-3">Q2 Revenue</th>
<th class="whitespace-nowrap px-3 py-3">Q3 Revenue</th>
<th class="whitespace-nowrap px-3 py-3">Q4 Revenue</th>
<th class="whitespace-nowrap px-3 py-3">YoY Growth</th>
<th class="whitespace-nowrap px-3 py-3">Margin</th>
</tr>
</thead>
<tbody>
<tr class="border-b">
<td class="whitespace-nowrap px-3 py-3 font-medium text-gray-900">Enterprise Plan</td>
<td class="whitespace-nowrap px-3 py-3 tabular-nums text-gray-600">$1,245,000</td>
<td class="whitespace-nowrap px-3 py-3 tabular-nums text-gray-600">$1,389,200</td>
<td class="whitespace-nowrap px-3 py-3 tabular-nums text-gray-600">$1,502,750</td>
<td class="whitespace-nowrap px-3 py-3 tabular-nums text-gray-600">$1,678,400</td>
<td class="whitespace-nowrap px-3 py-3 tabular-nums text-green-600">+34.8%</td>
<td class="whitespace-nowrap px-3 py-3 tabular-nums text-gray-600">72.3%</td>
</tr>
<tr class="border-b">
<td class="whitespace-nowrap px-3 py-3 font-medium text-gray-900">Starter Plan</td>
<td class="whitespace-nowrap px-3 py-3 tabular-nums text-gray-600">$423,100</td>
<td class="whitespace-nowrap px-3 py-3 tabular-nums text-gray-600">$456,800</td>
<td class="whitespace-nowrap px-3 py-3 tabular-nums text-gray-600">$498,200</td>
<td class="whitespace-nowrap px-3 py-3 tabular-nums text-gray-600">$512,600</td>
<td class="whitespace-nowrap px-3 py-3 tabular-nums text-green-600">+21.2%</td>
<td class="whitespace-nowrap px-3 py-3 tabular-nums text-gray-600">68.1%</td>
</tr>
</tbody>
</table>
</div>Key principle: The pattern is overflow-x-auto on the wrapper plus min-w-[...] on the table to set a minimum readable width. The -mx-4 px-4 md:mx-0 md:px-0 trick pulls the scrollable area to the screen edges on mobile, giving the table the full viewport width. Use whitespace-nowrap on cells to prevent awkward line breaks within data, and tabular-nums for proper number alignment.
Increase List Item Density on Mobile
Desktop list items can afford generous padding and full secondary details because users see 15-20 items at once in a tall viewport. On mobile, the same generous spacing means users see only 3-4 items per screen, forcing excessive scrolling through a list of 50+ results. Tightening padding and hiding secondary details on mobile doubles the visible item count, making lists scannable and reducing scroll fatigue.
Incorrect (desktop-sized list items show only 3-4 per mobile screen):
<ul class="divide-y divide-gray-200">
<li class="flex items-center gap-4 p-6">
<img src="/avatars/sarah.jpg" alt="" class="h-12 w-12 rounded-full" />
<div class="flex-1">
<h3 class="text-base font-semibold text-gray-900">Sarah Chen</h3>
<p class="text-sm text-gray-500">Engineering Lead</p>
<p class="mt-1 text-sm text-gray-400">Last active 2 hours ago</p>
</div>
<div class="text-right">
<p class="text-sm font-medium text-gray-900">12 projects</p>
<p class="text-sm text-gray-500">San Francisco, CA</p>
</div>
</li>
<li class="flex items-center gap-4 p-6">
<img src="/avatars/marcus.jpg" alt="" class="h-12 w-12 rounded-full" />
<div class="flex-1">
<h3 class="text-base font-semibold text-gray-900">Marcus Johnson</h3>
<p class="text-sm text-gray-500">Product Manager</p>
<p class="mt-1 text-sm text-gray-400">Last active 5 minutes ago</p>
</div>
<div class="text-right">
<p class="text-sm font-medium text-gray-900">8 projects</p>
<p class="text-sm text-gray-500">New York, NY</p>
</div>
</li>
<li class="flex items-center gap-4 p-6">
<img src="/avatars/priya.jpg" alt="" class="h-12 w-12 rounded-full" />
<div class="flex-1">
<h3 class="text-base font-semibold text-gray-900">Priya Patel</h3>
<p class="text-sm text-gray-500">Design Director</p>
<p class="mt-1 text-sm text-gray-400">Last active yesterday</p>
</div>
<div class="text-right">
<p class="text-sm font-medium text-gray-900">15 projects</p>
<p class="text-sm text-gray-500">London, UK</p>
</div>
</li>
</ul>Correct (compact on mobile, generous on desktop):
<ul class="divide-y divide-gray-200">
<li class="flex items-center gap-3 p-3 md:gap-4 md:p-6">
<img src="/avatars/sarah.jpg" alt="" class="h-9 w-9 rounded-full md:h-12 md:w-12" />
<div class="min-w-0 flex-1">
<h3 class="truncate text-sm font-semibold text-gray-900 md:text-base">Sarah Chen</h3>
<p class="truncate text-xs text-gray-500 md:text-sm">Engineering Lead</p>
<!-- Secondary info hidden on mobile -->
<p class="mt-1 hidden text-sm text-gray-400 md:block">Last active 2 hours ago</p>
</div>
<div class="hidden text-right md:block">
<p class="text-sm font-medium text-gray-900">12 projects</p>
<p class="text-sm text-gray-500">San Francisco, CA</p>
</div>
<!-- Compact badge visible on mobile only as a summary -->
<span class="text-xs text-gray-400 md:hidden">12</span>
</li>
<li class="flex items-center gap-3 p-3 md:gap-4 md:p-6">
<img src="/avatars/marcus.jpg" alt="" class="h-9 w-9 rounded-full md:h-12 md:w-12" />
<div class="min-w-0 flex-1">
<h3 class="truncate text-sm font-semibold text-gray-900 md:text-base">Marcus Johnson</h3>
<p class="truncate text-xs text-gray-500 md:text-sm">Product Manager</p>
<p class="mt-1 hidden text-sm text-gray-400 md:block">Last active 5 minutes ago</p>
</div>
<div class="hidden text-right md:block">
<p class="text-sm font-medium text-gray-900">8 projects</p>
<p class="text-sm text-gray-500">New York, NY</p>
</div>
<span class="text-xs text-gray-400 md:hidden">8</span>
</li>
<li class="flex items-center gap-3 p-3 md:gap-4 md:p-6">
<img src="/avatars/priya.jpg" alt="" class="h-9 w-9 rounded-full md:h-12 md:w-12" />
<div class="min-w-0 flex-1">
<h3 class="truncate text-sm font-semibold text-gray-900 md:text-base">Priya Patel</h3>
<p class="truncate text-xs text-gray-500 md:text-sm">Design Director</p>
<p class="mt-1 hidden text-sm text-gray-400 md:block">Last active yesterday</p>
</div>
<div class="hidden text-right md:block">
<p class="text-sm font-medium text-gray-900">15 projects</p>
<p class="text-sm text-gray-500">London, UK</p>
</div>
<span class="text-xs text-gray-400 md:hidden">15</span>
</li>
</ul>Key principle: Use p-3 md:p-6 and gap-3 md:gap-4 to tighten spacing on mobile. Hide secondary details with hidden md:block and reduce avatar sizes with h-9 w-9 md:h-12 md:w-12. Add min-w-0 on flex children to enable truncate to work properly, preventing text from blowing out the layout.
Adapt Data Grid Density for Screen Size
Data grids in dashboards and admin panels need different information density at different screen sizes. Showing all 8 columns on a 375px screen forces text into 40px-wide cells — unreadable and useless. Instead, show only the 3-4 most essential columns on mobile with a "view details" affordance, then progressively reveal more columns as the viewport widens. This keeps mobile scannable while preserving the full data experience on desktop.
Incorrect (all columns shown regardless of screen size):
<!-- 8 columns at ~80px each need 640px+ — tiny unreadable text on mobile -->
<table class="w-full border-collapse text-sm">
<thead>
<tr class="border-b bg-gray-50 text-left text-xs font-medium text-gray-500">
<th class="px-3 py-3">Order ID</th>
<th class="px-3 py-3">Customer</th>
<th class="px-3 py-3">Email</th>
<th class="px-3 py-3">Product</th>
<th class="px-3 py-3">Quantity</th>
<th class="px-3 py-3">Total</th>
<th class="px-3 py-3">Date</th>
<th class="px-3 py-3">Status</th>
</tr>
</thead>
<tbody>
<tr class="border-b">
<td class="px-3 py-3 text-gray-900">#4821</td>
<td class="px-3 py-3 text-gray-900">Sarah Chen</td>
<td class="px-3 py-3 text-gray-600">sarah.chen@example.com</td>
<td class="px-3 py-3 text-gray-600">Pro Annual Plan</td>
<td class="px-3 py-3 text-gray-600">1</td>
<td class="px-3 py-3 text-gray-900">$299.00</td>
<td class="px-3 py-3 text-gray-600">2025-01-15</td>
<td class="px-3 py-3"><span class="rounded-full bg-green-100 px-2 py-0.5 text-xs text-green-700">Paid</span></td>
</tr>
<tr class="border-b">
<td class="px-3 py-3 text-gray-900">#4820</td>
<td class="px-3 py-3 text-gray-900">Marcus Johnson</td>
<td class="px-3 py-3 text-gray-600">m.johnson@example.com</td>
<td class="px-3 py-3 text-gray-600">Starter Monthly</td>
<td class="px-3 py-3 text-gray-600">3</td>
<td class="px-3 py-3 text-gray-900">$87.00</td>
<td class="px-3 py-3 text-gray-600">2025-01-14</td>
<td class="px-3 py-3"><span class="rounded-full bg-yellow-100 px-2 py-0.5 text-xs text-yellow-700">Pending</span></td>
</tr>
</tbody>
</table>Correct (progressive column reveal with mobile "view details" action):
<div class="overflow-x-auto">
<table class="w-full border-collapse text-sm">
<thead>
<tr class="border-b bg-gray-50 text-left text-xs font-medium text-gray-500">
<th class="px-3 py-3">Order</th>
<th class="px-3 py-3">Customer</th>
<!-- Hidden on mobile, shown from md up -->
<th class="hidden px-3 py-3 md:table-cell">Email</th>
<th class="hidden px-3 py-3 lg:table-cell">Product</th>
<th class="hidden px-3 py-3 lg:table-cell">Qty</th>
<th class="px-3 py-3">Total</th>
<th class="hidden px-3 py-3 md:table-cell">Date</th>
<th class="px-3 py-3">Status</th>
<!-- Mobile-only details column -->
<th class="px-3 py-3 md:hidden"><span class="sr-only">Details</span></th>
</tr>
</thead>
<tbody>
<tr class="border-b">
<td class="px-3 py-3 font-medium text-gray-900">#4821</td>
<td class="px-3 py-3 text-gray-900">Sarah Chen</td>
<td class="hidden px-3 py-3 text-gray-600 md:table-cell">sarah.chen@example.com</td>
<td class="hidden px-3 py-3 text-gray-600 lg:table-cell">Pro Annual Plan</td>
<td class="hidden px-3 py-3 text-gray-600 lg:table-cell">1</td>
<td class="px-3 py-3 font-medium text-gray-900">$299.00</td>
<td class="hidden px-3 py-3 text-gray-600 md:table-cell">Jan 15</td>
<td class="px-3 py-3"><span class="rounded-full bg-green-100 px-2 py-0.5 text-xs text-green-700">Paid</span></td>
<td class="px-3 py-3 md:hidden">
<button class="text-blue-600 hover:text-blue-800" aria-label="View order #4821 details">
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</button>
</td>
</tr>
<tr class="border-b">
<td class="px-3 py-3 font-medium text-gray-900">#4820</td>
<td class="px-3 py-3 text-gray-900">Marcus Johnson</td>
<td class="hidden px-3 py-3 text-gray-600 md:table-cell">m.johnson@example.com</td>
<td class="hidden px-3 py-3 text-gray-600 lg:table-cell">Starter Monthly</td>
<td class="hidden px-3 py-3 text-gray-600 lg:table-cell">3</td>
<td class="px-3 py-3 font-medium text-gray-900">$87.00</td>
<td class="hidden px-3 py-3 text-gray-600 md:table-cell">Jan 14</td>
<td class="px-3 py-3"><span class="rounded-full bg-yellow-100 px-2 py-0.5 text-xs text-yellow-700">Pending</span></td>
<td class="px-3 py-3 md:hidden">
<button class="text-blue-600 hover:text-blue-800" aria-label="View order #4820 details">
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</button>
</td>
</tr>
</tbody>
</table>
</div>Key principle: Use hidden md:table-cell and hidden lg:table-cell to progressively reveal columns at wider breakpoints. Keep 3-4 essential columns always visible (identifier, name, key metric, status) and add a mobile-only chevron button (md:hidden) that links to a detail view. This keeps the mobile grid scannable without losing access to any data.
Stack Form Fields on Mobile, Use Grid on Desktop
Side-by-side form fields like first name and last name produce inputs that are only 150px wide on a 375px mobile screen — too narrow for comfortable typing and barely enough to show the placeholder text. On iOS, the auto-zoom on input focus (triggered by text smaller than 16px) compounds the problem. Stack form fields vertically on mobile so each input gets the full viewport width, and arrange them in a grid on desktop where there is room for side-by-side layout.
Incorrect (grid-cols-2 produces cramped inputs on mobile):
<form class="space-y-4 p-4">
<!-- Two 150px-wide inputs on a 375px screen — users can barely see what they type -->
<div class="grid grid-cols-2 gap-4">
<div>
<label for="first-name" class="block text-sm font-medium text-gray-700">First name</label>
<input
type="text"
id="first-name"
name="first_name"
placeholder="Jane"
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-blue-500 focus:ring-blue-500"
/>
</div>
<div>
<label for="last-name" class="block text-sm font-medium text-gray-700">Last name</label>
<input
type="text"
id="last-name"
name="last_name"
placeholder="Smith"
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-blue-500 focus:ring-blue-500"
/>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label for="email" class="block text-sm font-medium text-gray-700">Email</label>
<input
type="email"
id="email"
name="email"
placeholder="jane@example.com"
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-blue-500 focus:ring-blue-500"
/>
</div>
<div>
<label for="phone" class="block text-sm font-medium text-gray-700">Phone</label>
<input
type="tel"
id="phone"
name="phone"
placeholder="+1 (555) 000-0000"
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-blue-500 focus:ring-blue-500"
/>
</div>
</div>
<div>
<label for="message" class="block text-sm font-medium text-gray-700">Message</label>
<textarea
id="message"
name="message"
rows="4"
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-blue-500 focus:ring-blue-500"
></textarea>
</div>
<button type="submit" class="rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700">
Submit
</button>
</form>Correct (stacked on mobile, grid on desktop with proper input sizing):
<form class="space-y-4 p-4 md:space-y-6">
<!-- Full-width stacked inputs on mobile, side-by-side from md up -->
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div>
<label for="first-name" class="block text-sm font-medium text-gray-700">First name</label>
<!-- h-12 + text-base on mobile prevents iOS auto-zoom; h-10 + text-sm on desktop -->
<input
type="text"
id="first-name"
name="first_name"
placeholder="Jane"
class="mt-1 block h-12 w-full rounded-md border border-gray-300 px-3 py-2 text-base shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 md:h-10 md:text-sm"
/>
</div>
<div>
<label for="last-name" class="block text-sm font-medium text-gray-700">Last name</label>
<input
type="text"
id="last-name"
name="last_name"
placeholder="Smith"
class="mt-1 block h-12 w-full rounded-md border border-gray-300 px-3 py-2 text-base shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 md:h-10 md:text-sm"
/>
</div>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div>
<label for="email" class="block text-sm font-medium text-gray-700">Email</label>
<input
type="email"
id="email"
name="email"
placeholder="jane@example.com"
class="mt-1 block h-12 w-full rounded-md border border-gray-300 px-3 py-2 text-base shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 md:h-10 md:text-sm"
/>
</div>
<div>
<label for="phone" class="block text-sm font-medium text-gray-700">Phone</label>
<input
type="tel"
id="phone"
name="phone"
placeholder="+1 (555) 000-0000"
class="mt-1 block h-12 w-full rounded-md border border-gray-300 px-3 py-2 text-base shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 md:h-10 md:text-sm"
/>
</div>
</div>
<div>
<label for="message" class="block text-sm font-medium text-gray-700">Message</label>
<textarea
id="message"
name="message"
rows="4"
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-base shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 md:text-sm"
></textarea>
</div>
<!-- Full-width button on mobile, auto-width on desktop -->
<button
type="submit"
class="h-12 w-full rounded-md bg-blue-600 px-6 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 md:h-10 md:w-auto md:text-sm"
>
Submit
</button>
</form>Key principle: Use grid grid-cols-1 md:grid-cols-2 gap-4 to stack fields on mobile and pair them on desktop. Set h-12 text-base on mobile inputs to create comfortable 48px touch targets and prevent the iOS auto-zoom that triggers on inputs smaller than 16px. Scale down to md:h-10 md:text-sm on desktop for a more compact form. Make the submit button w-full md:w-auto so it is an easy full-width tap target on mobile.
Transform Tables to Cards on Mobile
Wide tables with 5+ columns are unusable on mobile — users see only 2 columns and must scroll horizontally to access the rest, losing context between the row identifier and the data they need. Converting each table row into a self-contained card on mobile lets users scan vertically through complete records instead of scrolling in two dimensions.
Incorrect (standard table forces horizontal scrolling on mobile):
<!-- 5 columns at ~100-150px each need ~600px minimum — overflows a 375px screen -->
<table class="w-full border-collapse">
<thead>
<tr class="border-b bg-gray-50 text-left text-sm font-medium text-gray-500">
<th class="px-4 py-3">Name</th>
<th class="px-4 py-3">Email</th>
<th class="px-4 py-3">Role</th>
<th class="px-4 py-3">Status</th>
<th class="px-4 py-3">Actions</th>
</tr>
</thead>
<tbody>
<tr class="border-b text-sm">
<td class="px-4 py-3 font-medium text-gray-900">Sarah Chen</td>
<td class="px-4 py-3 text-gray-600">sarah.chen@company.com</td>
<td class="px-4 py-3 text-gray-600">Engineering Lead</td>
<td class="px-4 py-3"><span class="rounded-full bg-green-100 px-2 py-1 text-xs text-green-700">Active</span></td>
<td class="px-4 py-3"><button class="text-blue-600 hover:text-blue-800">Edit</button></td>
</tr>
<tr class="border-b text-sm">
<td class="px-4 py-3 font-medium text-gray-900">Marcus Johnson</td>
<td class="px-4 py-3 text-gray-600">m.johnson@company.com</td>
<td class="px-4 py-3 text-gray-600">Product Manager</td>
<td class="px-4 py-3"><span class="rounded-full bg-yellow-100 px-2 py-1 text-xs text-yellow-700">Pending</span></td>
<td class="px-4 py-3"><button class="text-blue-600 hover:text-blue-800">Edit</button></td>
</tr>
</tbody>
</table>Correct (cards on mobile, table on desktop using dual-view approach):
<!-- Desktop table — hidden on mobile, shown from md up -->
<table class="hidden w-full border-collapse md:table">
<thead>
<tr class="border-b bg-gray-50 text-left text-sm font-medium text-gray-500">
<th class="px-4 py-3">Name</th>
<th class="px-4 py-3">Email</th>
<th class="px-4 py-3">Role</th>
<th class="px-4 py-3">Status</th>
<th class="px-4 py-3">Actions</th>
</tr>
</thead>
<tbody>
<tr class="border-b text-sm">
<td class="px-4 py-3 font-medium text-gray-900">Sarah Chen</td>
<td class="px-4 py-3 text-gray-600">sarah.chen@company.com</td>
<td class="px-4 py-3 text-gray-600">Engineering Lead</td>
<td class="px-4 py-3"><span class="rounded-full bg-green-100 px-2 py-1 text-xs text-green-700">Active</span></td>
<td class="px-4 py-3"><button class="text-blue-600 hover:text-blue-800">Edit</button></td>
</tr>
<tr class="border-b text-sm">
<td class="px-4 py-3 font-medium text-gray-900">Marcus Johnson</td>
<td class="px-4 py-3 text-gray-600">m.johnson@company.com</td>
<td class="px-4 py-3 text-gray-600">Product Manager</td>
<td class="px-4 py-3"><span class="rounded-full bg-yellow-100 px-2 py-1 text-xs text-yellow-700">Pending</span></td>
<td class="px-4 py-3"><button class="text-blue-600 hover:text-blue-800">Edit</button></td>
</tr>
</tbody>
</table>
<!-- Mobile cards — shown on mobile, hidden from md up -->
<div class="flex flex-col gap-3 md:hidden">
<div class="rounded-lg border bg-white p-4 shadow-sm">
<div class="flex items-center justify-between">
<h3 class="font-medium text-gray-900">Sarah Chen</h3>
<span class="rounded-full bg-green-100 px-2 py-1 text-xs text-green-700">Active</span>
</div>
<p class="mt-1 text-sm text-gray-600">sarah.chen@company.com</p>
<p class="mt-1 text-sm text-gray-500">Engineering Lead</p>
<div class="mt-3 border-t pt-3">
<button class="text-sm font-medium text-blue-600 hover:text-blue-800">Edit</button>
</div>
</div>
<div class="rounded-lg border bg-white p-4 shadow-sm">
<div class="flex items-center justify-between">
<h3 class="font-medium text-gray-900">Marcus Johnson</h3>
<span class="rounded-full bg-yellow-100 px-2 py-1 text-xs text-yellow-700">Pending</span>
</div>
<p class="mt-1 text-sm text-gray-600">m.johnson@company.com</p>
<p class="mt-1 text-sm text-gray-500">Product Manager</p>
<div class="mt-3 border-t pt-3">
<button class="text-sm font-medium text-blue-600 hover:text-blue-800">Edit</button>
</div>
</div>
</div>Key principle: Use hidden md:table on the desktop table and md:hidden on the mobile card container. This dual-view approach avoids CSS hacks with display: block on table elements and gives you full control over the card layout. For server-rendered pages, consider generating both views from the same data source to avoid duplication.
Truncate Overflowing Text on Mobile
Long strings like email addresses, URLs, file paths, and product names break mobile layouts by forcing containers wider than the viewport or wrapping into multiple lines that destroy the visual rhythm. A 40-character email in a 160px-wide column either overflows or stacks into 3 lines. Truncating with ellipsis on mobile preserves the layout while still showing enough of the content for identification, with full text available on hover or on wider screens.
Incorrect (full-length text wraps and breaks card layout on mobile):
<ul class="divide-y divide-gray-200">
<li class="flex items-center justify-between gap-4 px-4 py-3">
<div>
<p class="text-sm font-medium text-gray-900">Sarah Chen</p>
<!-- 30+ character email wraps to 2 lines on mobile, misaligning the row -->
<p class="text-sm text-gray-500">sarah.chen.engineering@longcompanyname.com</p>
</div>
<span class="rounded-full bg-green-100 px-2 py-1 text-xs text-green-700">Active</span>
</li>
<li class="flex items-center justify-between gap-4 px-4 py-3">
<div>
<p class="text-sm font-medium text-gray-900">Alexander Konstantinidis</p>
<!-- Long name + long email = 4 lines of text on mobile for a single list item -->
<p class="text-sm text-gray-500">alexander.konstantinidis@international-corp.co.uk</p>
</div>
<span class="rounded-full bg-yellow-100 px-2 py-1 text-xs text-yellow-700">Pending</span>
</li>
<li class="flex items-center justify-between gap-4 px-4 py-3">
<div>
<p class="text-sm font-medium text-gray-900">Documentation Update</p>
<!-- URLs are the worst offenders — no natural break points -->
<p class="text-sm text-gray-500">https://github.com/organization/repository-name/pull/1234/files#diff-abc123</p>
</div>
<span class="rounded-full bg-blue-100 px-2 py-1 text-xs text-blue-700">Review</span>
</li>
</ul>Correct (truncated on mobile, full text on desktop):
<ul class="divide-y divide-gray-200">
<li class="flex items-center justify-between gap-4 px-4 py-3">
<!-- min-w-0 is critical — allows flex child to shrink below its content size -->
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-medium text-gray-900">Sarah Chen</p>
<!-- truncate clips at container edge, line-clamp-1 md:line-clamp-none for multi-line control -->
<p class="truncate text-sm text-gray-500 md:overflow-visible md:text-clip md:whitespace-normal">
sarah.chen.engineering@longcompanyname.com
</p>
</div>
<span class="shrink-0 rounded-full bg-green-100 px-2 py-1 text-xs text-green-700">Active</span>
</li>
<li class="flex items-center justify-between gap-4 px-4 py-3">
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-medium text-gray-900">Alexander Konstantinidis</p>
<p class="truncate text-sm text-gray-500 md:overflow-visible md:text-clip md:whitespace-normal">
alexander.konstantinidis@international-corp.co.uk
</p>
</div>
<span class="shrink-0 rounded-full bg-yellow-100 px-2 py-1 text-xs text-yellow-700">Pending</span>
</li>
<li class="flex items-center justify-between gap-4 px-4 py-3">
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-medium text-gray-900">Documentation Update</p>
<!-- line-clamp-1 on mobile, full text on desktop -->
<p class="line-clamp-1 text-sm text-gray-500 md:line-clamp-none">
https://github.com/organization/repository-name/pull/1234/files#diff-abc123
</p>
</div>
<span class="shrink-0 rounded-full bg-blue-100 px-2 py-1 text-xs text-blue-700">Review</span>
</li>
</ul>Key principle: Three utilities work together: min-w-0 on the flex child (allows it to shrink below content width), truncate on the text element (sets overflow-hidden text-ellipsis whitespace-nowrap), and shrink-0 on badges or actions so they never collapse. Use line-clamp-1 md:line-clamp-none when you want to allow wrapping on desktop but restrict to one line on mobile. Always add md:overflow-visible md:text-clip md:whitespace-normal to undo truncation at wider breakpoints when full text should be visible.
Use clamp() for Fluid Font Sizing
Fixed font sizes with breakpoint overrides create jarring text jumps — at exactly 768px a heading snaps from one size to another in a single frame. clamp() produces smooth, continuous scaling between a minimum and maximum size based on the viewport width. In Tailwind v4, use arbitrary values with clamp() to eliminate every discrete size jump while still capping the range for readability on extremes.
Incorrect (discrete size jumps at breakpoints):
<!-- Heading snaps from 1.5rem to 2.25rem at 768px and to 3.75rem at 1024px -->
<section class="px-4 py-12 md:px-8 lg:px-16">
<h1 class="text-2xl font-bold md:text-4xl lg:text-6xl">
Ship faster with fluid design
</h1>
<p class="mt-4 text-base text-gray-600 md:text-lg lg:text-xl">
Our platform helps teams deliver responsive interfaces that look polished
on every screen size — from phones to ultrawide monitors.
</p>
</section>Correct (smooth scaling via clamp()):
<!-- Font scales continuously from 1.5rem at 320px to 3.75rem at 1280px — no jumps -->
<section class="px-4 py-12 md:px-8 lg:px-16">
<h1 class="text-[clamp(1.5rem,1rem+2.5vw,3.75rem)] font-bold">
Ship faster with fluid design
</h1>
<p class="mt-4 text-[clamp(1rem,0.875rem+0.625vw,1.25rem)] text-gray-600">
Our platform helps teams deliver responsive interfaces that look polished
on every screen size — from phones to ultrawide monitors.
</p>
</section>Key principle: The clamp(min, preferred, max) formula uses a viewport-relative middle value (e.g., 1rem + 2.5vw) to create a linear scaling curve. The min and max cap the range so text never becomes too small on phones or absurdly large on ultrawides. Calculate the preferred value based on the viewport range where you want the transition to happen.
Adjust Letter Spacing for Responsive Headlines
Large headings on desktop benefit from tighter letter-spacing (negative tracking) because at large sizes the default inter-character gaps appear proportionally wider, making words look loose and disconnected. On mobile, where those same headings render at smaller sizes, tight tracking reduces readability by cramming characters together. Match tracking to the rendered size — default or normal at small sizes, progressively tighter as the heading scales up at wider breakpoints.
Incorrect (same tight tracking at all sizes):
<!-- tracking-tight on a text-xl mobile heading cramps the characters -->
<header class="bg-gray-950 px-4 py-16 text-white md:px-8 lg:py-24">
<h1 class="text-xl font-bold tracking-tight md:text-4xl lg:text-6xl">
The Future of Design Systems
</h1>
<p class="mt-4 text-sm tracking-tight text-gray-300 md:text-base lg:text-lg">
A comprehensive guide to building scalable, maintainable UI architectures
that work across teams and products.
</p>
</header>Correct (tracking tightens as heading size increases):
<!-- Normal tracking on mobile, tighter as the heading grows at wider breakpoints -->
<header class="bg-gray-950 px-4 py-16 text-white md:px-8 lg:py-24">
<h1 class="text-xl font-bold tracking-normal md:text-4xl md:tracking-tight lg:text-6xl lg:tracking-tighter">
The Future of Design Systems
</h1>
<p class="mt-4 text-sm text-gray-300 md:text-base lg:text-lg">
A comprehensive guide to building scalable, maintainable UI architectures
that work across teams and products.
</p>
</header>Key principle: Only apply tracking-tight or tracking-tighter at breakpoints where the heading is large enough to benefit from it (typically text-3xl and above). Body text and small UI labels should almost always use default tracking. If you are using clamp() for fluid font sizing, apply the tighter tracking at the breakpoint where the font size crosses into the "large heading" range.
See also: tailwind-ui-refactor/type-letter-spacing for the baseline principle; this rule adds responsive breakpoint-conditional application.
Tighten Line Height as Font Size Increases
Line height and font size are inversely proportional — large headings need tighter line-height (1.1-1.2) while body text needs looser spacing (1.5-1.75) for comfortable reading. Applying a single generous line-height to both creates enormous vertical gaps between heading lines that break visual cohesion and waste above-the-fold space, especially on mobile where a two-line text-5xl heading with leading-relaxed can consume over half the viewport.
Incorrect (same relaxed line-height on headings and body):
<!-- leading-relaxed on a 3rem heading creates ~30px gaps between lines -->
<article class="mx-auto max-w-3xl px-4 py-8">
<h1 class="text-3xl font-bold leading-relaxed text-gray-900 md:text-5xl md:leading-relaxed">
Building Responsive Interfaces That Scale Across Every Device
</h1>
<p class="mt-6 leading-relaxed text-gray-600">
When designing for the web, typography choices have an outsized impact
on how professional and readable your interface feels. Getting line-height
right is one of the highest-leverage typographic decisions you can make.
</p>
</article>Correct (tight leading on headings, relaxed on body):
<!-- Heading uses leading-tight (1.25), body uses leading-relaxed (1.625) -->
<article class="mx-auto max-w-3xl px-4 py-8">
<h1 class="text-3xl font-bold leading-tight text-gray-900 md:text-5xl md:leading-[1.15]">
Building Responsive Interfaces That Scale Across Every Device
</h1>
<p class="mt-6 text-base leading-relaxed text-gray-600">
When designing for the web, typography choices have an outsized impact
on how professional and readable your interface feels. Getting line-height
right is one of the highest-leverage typographic decisions you can make.
</p>
</article>Key principle: As headings scale up at wider breakpoints, tighten the line-height further — leading-tight at mobile sizes, then md:leading-[1.15] or lg:leading-none at desktop where the heading font size is largest. Body text should stay at leading-relaxed or leading-7 regardless of breakpoint.
See also: tailwind-ui-refactor/type-line-height-inverse for the baseline principle; this rule adds the responsive breakpoint transformations.
Constrain Line Length to 45-75 Characters
Optimal reading measure is 45-75 characters per line (roughly 20-35em). On wide screens, unconstrained paragraph text stretches to 120+ characters per line, forcing the reader's eye to travel too far to find the next line and drastically increasing reading fatigue. On mobile, the viewport naturally constrains line length, but on anything wider than a tablet you must explicitly cap it. Tailwind's max-w-prose (65ch) is purpose-built for this.
Incorrect (full-width paragraph stretching across the viewport):
<!-- At 1440px wide, this paragraph runs ~130 characters per line -->
<div class="px-8 py-12">
<h2 class="text-2xl font-bold text-gray-900">Why Responsive Design Matters</h2>
<p class="mt-4 text-lg text-gray-600">
Responsive design is not just about making things fit on smaller screens. It is about
creating an experience that feels intentional at every width. When text stretches across
the full width of a wide monitor, readers lose their place between lines and comprehension
drops significantly. Studies show that reading speed and retention both decrease when line
length exceeds 75 characters, making measure one of the most impactful typographic choices.
</p>
</div>Correct (line length capped with max-w-prose):
<!-- max-w-prose caps width at 65ch — comfortable reading on any screen -->
<div class="px-8 py-12">
<div class="mx-auto max-w-prose">
<h2 class="text-2xl font-bold text-gray-900">Why Responsive Design Matters</h2>
<p class="mt-4 text-lg text-gray-600">
Responsive design is not just about making things fit on smaller screens. It is about
creating an experience that feels intentional at every width. When text stretches across
the full width of a wide monitor, readers lose their place between lines and comprehension
drops significantly. Studies show that reading speed and retention both decrease when line
length exceeds 75 characters, making measure one of the most impactful typographic choices.
</p>
</div>
</div>Key principle: Wrap text content in max-w-prose (65ch), max-w-2xl (~42rem), or max-w-3xl (~48rem) and center it with mx-auto. This only constrains the text container — your outer layout can still span the full viewport for backgrounds and decorative elements. On mobile, the screen width is already within comfortable range, so the constraint only activates on wider viewports.
See also: tailwind-ui-refactor/type-line-length for the baseline 45-75 character principle; this rule adds the responsive implementation with Tailwind utilities.
Scale Heading Sizes Independently Across Breakpoints
Don't scale all headings by the same ratio across breakpoints. An h1 that is 3x body size on desktop would dominate a small mobile screen, while an h3 that is 1.2x body size on desktop might look nearly identical to body text if reduced further on mobile. Each heading level needs its own scaling curve — the hierarchy should compress on mobile (smaller ratio between levels) and expand on desktop (larger ratio) so every level remains visually distinct without overwhelming the viewport.
Incorrect (uniform scaling ratio across all heading levels):
<!-- Every heading doubles at md: — h1 dominates mobile, h3 barely differs from body on desktop -->
<div class="mx-auto max-w-4xl px-4 py-8">
<h1 class="text-3xl font-bold text-gray-900 md:text-6xl">Product Features</h1>
<h2 class="mt-8 text-2xl font-semibold text-gray-800 md:text-4xl">Performance</h2>
<p class="mt-2 text-base text-gray-600 md:text-lg">
Optimized for speed with sub-100ms response times.
</p>
<h3 class="mt-6 text-xl font-medium text-gray-700 md:text-2xl">Caching Layer</h3>
<p class="mt-2 text-base text-gray-600 md:text-lg">
Intelligent edge caching reduces server load by 80%.
</p>
</div>Correct (independent scaling — tighter ratios on mobile, wider on desktop):
<!-- h1: 1.67× jump, h2: 1.33× jump, h3: 1.14× jump — hierarchy preserved at both sizes -->
<div class="mx-auto max-w-4xl px-4 py-8">
<h1 class="text-3xl font-bold text-gray-900 md:text-5xl">Product Features</h1>
<h2 class="mt-8 text-2xl font-semibold text-gray-800 md:text-3xl">Performance</h2>
<p class="mt-2 text-base text-gray-600 md:text-lg">
Optimized for speed with sub-100ms response times.
</p>
<h3 class="mt-6 text-xl font-medium text-gray-700 md:text-[1.375rem]">Caching Layer</h3>
<p class="mt-2 text-base text-gray-600 md:text-lg">
Intelligent edge caching reduces server load by 80%.
</p>
</div>Key principle: On mobile, the heading scale is compressed (h1=1.875rem, h2=1.5rem, h3=1.25rem — tight steps). On desktop, h1 jumps to 3rem while h2 only reaches 1.875rem and h3 stays at 1.375rem. This gives h1 the dramatic presence it needs on large screens without overwhelming mobile, while h2 and h3 grow just enough to remain distinct.
Use a Responsive Type Scale
A single modular scale (e.g., 1.25 ratio) applied uniformly produces sizes that are either too similar on mobile — where 1rem, 1.25rem, and 1.563rem are nearly indistinguishable — or excessively large on desktop. Hand-crafted responsive type scales solve this by defining deliberately chosen sizes that compress on small screens and expand on large ones. In Tailwind v4, use the @theme directive with CSS custom properties that change at breakpoints to create a type scale your entire design system can reference.
Incorrect (single mathematical scale across all viewports):
<!-- 1.25 modular scale: sizes are too close on mobile, h1 too large on desktop -->
<style>
:root {
--text-sm: 0.8rem; /* 12.8px */
--text-base: 1rem; /* 16px */
--text-lg: 1.25rem; /* 20px — barely different from base */
--text-xl: 1.563rem; /* 25px */
--text-2xl: 1.953rem; /* 31.25px */
--text-hero: 3.052rem; /* 48.8px — too large on mobile */
}
</style>
<div class="px-4 py-12">
<h1 class="font-bold text-[length:var(--text-hero)]">Launch Day</h1>
<h2 class="mt-4 font-semibold text-[length:var(--text-2xl)]">What's New</h2>
<p class="mt-2 text-[length:var(--text-base)] text-gray-600">
We've rebuilt the entire platform from the ground up.
</p>
</div>Correct (responsive type scale with hand-picked sizes per breakpoint):
<!-- Type scale compresses on mobile, expands on desktop — each level stays distinct -->
<style>
:root {
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
--text-hero: 2.25rem;
}
@media (min-width: 768px) {
:root {
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.25rem;
--text-xl: 1.5rem;
--text-2xl: 2rem;
--text-hero: 3.5rem;
}
}
@media (min-width: 1280px) {
:root {
--text-sm: 0.875rem;
--text-base: 1.0625rem;
--text-lg: 1.25rem;
--text-xl: 1.75rem;
--text-2xl: 2.5rem;
--text-hero: 4.5rem;
}
}
</style>
<div class="px-4 py-12 md:px-8 lg:px-16">
<h1 class="font-bold text-[length:var(--text-hero)]">Launch Day</h1>
<h2 class="mt-4 font-semibold text-[length:var(--text-2xl)]">What's New</h2>
<p class="mt-2 text-[length:var(--text-base)] text-gray-600">
We've rebuilt the entire platform from the ground up.
</p>
</div>Key principle: Hand-craft your scale by choosing sizes where each level is visually distinct at every breakpoint. The hero jumps from 2.25rem to 4.5rem (2x), but base text barely changes (1rem to 1.0625rem). In Tailwind v4, define these custom properties inside @theme so they're available as first-class utilities like text-hero. This gives you a single source of truth that adapts across viewports without per-element breakpoint classes.
Use Aspect Ratio for Responsive Containers
Containers that rely on their content to determine height cause Cumulative Layout Shift (CLS) as images, videos, or embeds load. The page reflows and pushes content down, degrading both user experience and Core Web Vitals scores. Using Tailwind's aspect-* utilities reserves the correct space before content loads, producing zero layout shift regardless of viewport width.
Incorrect (no height constraint, causes layout shift as content loads):
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<!-- Container has no defined height — collapses to 0px until the image loads -->
<div class="overflow-hidden rounded-lg">
<img src="/hero-banner.jpg" alt="Summer collection" class="w-full" />
</div>
<!-- Embed has no reserved space — page jumps when iframe renders -->
<div class="overflow-hidden rounded-lg">
<iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ" class="w-full" title="Product demo"></iframe>
</div>
</div>
<!-- Card grid where image heights are inconsistent across cards -->
<div class="mt-6 grid grid-cols-1 gap-4 sm:grid-cols-3">
<div class="rounded-lg border">
<img src="/product-a.jpg" alt="Product A" class="w-full" />
<div class="p-4">
<h3 class="font-medium">Product A</h3>
</div>
</div>
<div class="rounded-lg border">
<img src="/product-b.jpg" alt="Product B" class="w-full" />
<div class="p-4">
<h3 class="font-medium">Product B</h3>
</div>
</div>
</div>Correct (aspect ratio reserves space, zero layout shift):
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<!-- 16:9 space reserved immediately — no reflow when image loads -->
<div class="aspect-video overflow-hidden rounded-lg">
<img src="/hero-banner.jpg" alt="Summer collection" class="h-full w-full object-cover" />
</div>
<!-- iframe fills the reserved 16:9 container -->
<div class="aspect-video overflow-hidden rounded-lg">
<iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ" class="h-full w-full" title="Product demo" allowfullscreen></iframe>
</div>
</div>
<!-- Consistent card image heights using aspect-square -->
<div class="mt-6 grid grid-cols-1 gap-4 sm:grid-cols-3">
<div class="rounded-lg border">
<div class="aspect-square overflow-hidden rounded-t-lg">
<img src="/product-a.jpg" alt="Product A" class="h-full w-full object-cover" />
</div>
<div class="p-4">
<h3 class="font-medium">Product A</h3>
</div>
</div>
<div class="rounded-lg border">
<div class="aspect-square overflow-hidden rounded-t-lg">
<img src="/product-b.jpg" alt="Product B" class="h-full w-full object-cover" />
</div>
<div class="p-4">
<h3 class="font-medium">Product B</h3>
</div>
</div>
</div>Common aspect ratio utilities:
aspect-video-- 16:9, ideal for video embeds and hero bannersaspect-square-- 1:1, ideal for product images and avatarsaspect-[4/3]-- 4:3, good for photos and thumbnailsaspect-[3/2]-- 3:2, classic photography ratio
Always pair aspect ratio containers with object-cover (or object-contain) on the inner media element to prevent stretching or letterboxing.
Replace Fixed Positioning with Relative on Mobile
Fixed-position elements (floating action buttons, chat widgets, cookie banners) are placed relative to the viewport and float above page content. On mobile, these elements can obscure text, overlap form inputs, or block tap targets -- especially near the bottom of the screen where thumbs naturally rest. Converting them to relative or integrating them into the document flow on mobile eliminates overlap and simplifies z-index management.
Incorrect (fixed FAB that covers content on mobile):
<div class="relative min-h-screen">
<main class="p-4">
<h1 class="text-xl font-bold">Help Center</h1>
<div class="mt-4 space-y-4">
<article class="rounded-lg border p-4">
<h2 class="font-medium">Getting Started Guide</h2>
<p class="mt-1 text-sm text-gray-600">Learn the basics of setting up your account.</p>
<a href="#" class="mt-2 inline-block text-sm text-blue-600">Read more</a>
</article>
<article class="rounded-lg border p-4">
<h2 class="font-medium">Billing FAQ</h2>
<p class="mt-1 text-sm text-gray-600">Common questions about plans and payments.</p>
<a href="#" class="mt-2 inline-block text-sm text-blue-600">Read more</a>
</article>
</div>
</main>
<!-- Covers the "Read more" links and bottom content on mobile -->
<button class="fixed bottom-4 right-4 z-50 rounded-full bg-blue-600 p-4 text-white shadow-lg">
<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="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" />
</svg>
</button>
</div>Correct (inline on mobile, fixed on desktop):
<div class="relative min-h-screen">
<main class="p-4">
<h1 class="text-xl font-bold">Help Center</h1>
<div class="mt-4 space-y-4">
<article class="rounded-lg border p-4">
<h2 class="font-medium">Getting Started Guide</h2>
<p class="mt-1 text-sm text-gray-600">Learn the basics of setting up your account.</p>
<a href="#" class="mt-2 inline-block text-sm text-blue-600">Read more</a>
</article>
<article class="rounded-lg border p-4">
<h2 class="font-medium">Billing FAQ</h2>
<p class="mt-1 text-sm text-gray-600">Common questions about plans and payments.</p>
<a href="#" class="mt-2 inline-block text-sm text-blue-600">Read more</a>
</article>
</div>
<!-- On mobile: full-width button at the bottom of content flow -->
<!-- On md+: fixed floating button in the corner -->
<div class="mt-6 md:mt-0">
<button class="w-full rounded-lg bg-blue-600 px-4 py-3 text-center text-sm font-medium text-white shadow md:fixed md:bottom-4 md:right-4 md:z-50 md:w-auto md:rounded-full md:p-4">
<span class="md:hidden">Chat with Support</span>
<svg class="hidden h-6 w-6 md:block" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" />
</svg>
</button>
</div>
</main>
</div>Key principle: Use md:fixed md:bottom-4 md:right-4 instead of unconditional fixed. On mobile, render the element inline as a full-width button or bar at a logical place in the content flow. This avoids overlap, eliminates z-index issues, and often provides a better mobile UX since the action is visible in context rather than floating on top.
Reduce Grid Columns at Narrower Breakpoints
A 4-column grid on a 375px mobile screen means each item gets roughly 80px of width -- far too narrow for any meaningful content like card text, images, or form fields. Progressively reduce columns as the viewport shrinks: 4 columns on desktop, 2 on tablet, 1 on mobile. This keeps each item at a readable, tappable size.
Incorrect (fixed 4-column grid at all screen sizes):
<!-- Each card gets ~80px on mobile — text wraps every word, images are thumbnails -->
<div class="grid grid-cols-4 gap-4 p-4">
<div class="rounded-lg border bg-white p-4 shadow-sm">
<img src="/product-1.jpg" alt="Wireless Headphones" class="h-24 w-full object-cover" />
<h3 class="mt-2 font-medium">Wireless Headphones</h3>
<p class="text-sm text-gray-500">$79.99</p>
</div>
<div class="rounded-lg border bg-white p-4 shadow-sm">
<img src="/product-2.jpg" alt="USB-C Hub" class="h-24 w-full object-cover" />
<h3 class="mt-2 font-medium">USB-C Hub</h3>
<p class="text-sm text-gray-500">$49.99</p>
</div>
<div class="rounded-lg border bg-white p-4 shadow-sm">
<img src="/product-3.jpg" alt="Mechanical Keyboard" class="h-24 w-full object-cover" />
<h3 class="mt-2 font-medium">Mechanical Keyboard</h3>
<p class="text-sm text-gray-500">$129.99</p>
</div>
<div class="rounded-lg border bg-white p-4 shadow-sm">
<img src="/product-4.jpg" alt="Monitor Stand" class="h-24 w-full object-cover" />
<h3 class="mt-2 font-medium">Monitor Stand</h3>
<p class="text-sm text-gray-500">$34.99</p>
</div>
</div>Correct (progressive column reduction: 1 → 2 → 4):
<!-- 1 column on mobile, 2 on sm/md, 4 on lg+ — each card stays readable -->
<div class="grid grid-cols-1 gap-4 p-4 sm:grid-cols-2 lg:grid-cols-4">
<div class="rounded-lg border bg-white p-4 shadow-sm">
<img src="/product-1.jpg" alt="Wireless Headphones" class="h-40 w-full rounded object-cover" />
<h3 class="mt-2 font-medium">Wireless Headphones</h3>
<p class="text-sm text-gray-500">$79.99</p>
</div>
<div class="rounded-lg border bg-white p-4 shadow-sm">
<img src="/product-2.jpg" alt="USB-C Hub" class="h-40 w-full rounded object-cover" />
<h3 class="mt-2 font-medium">USB-C Hub</h3>
<p class="text-sm text-gray-500">$49.99</p>
</div>
<div class="rounded-lg border bg-white p-4 shadow-sm">
<img src="/product-3.jpg" alt="Mechanical Keyboard" class="h-40 w-full rounded object-cover" />
<h3 class="mt-2 font-medium">Mechanical Keyboard</h3>
<p class="text-sm text-gray-500">$129.99</p>
</div>
<div class="rounded-lg border bg-white p-4 shadow-sm">
<img src="/product-4.jpg" alt="Monitor Stand" class="h-40 w-full rounded object-cover" />
<h3 class="mt-2 font-medium">Monitor Stand</h3>
<p class="text-sm text-gray-500">$34.99</p>
</div>
</div>Key principle: Start with grid-cols-1 as the mobile base, then increase with sm:grid-cols-2 and lg:grid-cols-4. Each card maintains a minimum usable width at every breakpoint. The gap can also be adjusted per breakpoint if needed (gap-3 md:gap-4 lg:gap-6).
Use Responsive Holy Grail Layout with Grid
The holy grail layout (header, left sidebar, main content, right sidebar, footer) is one of the most common page structures, but it requires careful responsive treatment. On mobile, all five regions must collapse into a single column. CSS Grid with named template areas makes this manageable, but in Tailwind the pragmatic approach is to combine grid column definitions with responsive ordering.
Incorrect (complex grid that breaks on tablet and mobile):
<!-- Fixed 3-column layout — sidebars squeeze content on tablet, overlap on mobile -->
<div class="grid grid-cols-[250px_1fr_200px] grid-rows-[auto_1fr_auto] min-h-screen">
<header class="col-span-3 border-b bg-white px-6 py-4">
<h1 class="text-xl font-bold">My App</h1>
</header>
<aside class="border-r bg-gray-50 p-4">
<nav class="space-y-2">
<a href="#" class="block rounded px-3 py-2 text-sm">Dashboard</a>
<a href="#" class="block rounded px-3 py-2 text-sm">Projects</a>
</nav>
</aside>
<main class="overflow-y-auto p-6">
<h2 class="text-lg font-semibold">Welcome back</h2>
<p class="mt-2 text-gray-600">Here is your activity summary.</p>
</main>
<aside class="border-l bg-gray-50 p-4">
<h3 class="text-sm font-semibold">Recent Activity</h3>
<ul class="mt-2 space-y-1 text-sm text-gray-600">
<li>Deployed v2.4.1</li>
<li>Merged PR #482</li>
</ul>
</aside>
<footer class="col-span-3 border-t bg-white px-6 py-3 text-sm text-gray-500">
© 2025 My App
</footer>
</div>Correct (single column on mobile, full holy grail on lg+):
<!-- Stacks to single column on mobile, 3-column holy grail on lg -->
<div class="grid min-h-screen grid-cols-1 grid-rows-[auto_1fr_auto] lg:grid-cols-[250px_1fr_200px]">
<header class="col-span-1 border-b bg-white px-4 py-4 lg:col-span-3 lg:px-6">
<h1 class="text-xl font-bold">My App</h1>
</header>
<!-- Left sidebar: appears first in source for accessibility, reordered by grid on lg -->
<aside class="border-b bg-gray-50 p-4 lg:row-start-2 lg:border-b-0 lg:border-r">
<nav class="flex gap-2 overflow-x-auto lg:flex-col lg:gap-0 lg:space-y-2">
<a href="#" class="shrink-0 rounded px-3 py-2 text-sm hover:bg-gray-200">Dashboard</a>
<a href="#" class="shrink-0 rounded px-3 py-2 text-sm hover:bg-gray-200">Projects</a>
</nav>
</aside>
<main class="overflow-y-auto p-4 lg:row-start-2 lg:p-6">
<h2 class="text-lg font-semibold">Welcome back</h2>
<p class="mt-2 text-gray-600">Here is your activity summary.</p>
</main>
<!-- Right sidebar: stacks below content on mobile -->
<aside class="border-t bg-gray-50 p-4 lg:row-start-2 lg:border-l lg:border-t-0">
<h3 class="text-sm font-semibold">Recent Activity</h3>
<ul class="mt-2 space-y-1 text-sm text-gray-600">
<li>Deployed v2.4.1</li>
<li>Merged PR #482</li>
</ul>
</aside>
<footer class="col-span-1 border-t bg-white px-4 py-3 text-sm text-gray-500 lg:col-span-3 lg:px-6">
© 2025 My App
</footer>
</div>Key principle: Define grid-cols-1 as the mobile base and lg:grid-cols-[250px_1fr_200px] for desktop. Use lg:col-span-3 on header and footer to span all columns only when they exist. Sidebars flow naturally in the single-column stack on mobile and slot into their grid positions on desktop via lg:row-start-2.
Collapse Sidebar to Top or Bottom on Mobile
Sidebars consume 200-300px of horizontal space that simply does not exist on a 375px mobile screen. Keeping a sidebar visible on mobile squeezes main content into an unusable 75-175px column. Collapse the sidebar into the normal document flow on mobile so it sits above or below the main content as a full-width section.
Incorrect (sidebar persists on mobile, squeezing main content):
<!-- On a 375px screen the sidebar takes 250px, leaving only 125px for content -->
<div class="flex flex-row">
<aside class="w-[250px] shrink-0 border-r bg-gray-50 p-4">
<nav>
<h2 class="mb-4 font-semibold">Dashboard</h2>
<a href="/overview" class="block rounded px-3 py-2 text-sm hover:bg-gray-200">Overview</a>
<a href="/analytics" class="block rounded px-3 py-2 text-sm hover:bg-gray-200">Analytics</a>
<a href="/reports" class="block rounded px-3 py-2 text-sm hover:bg-gray-200">Reports</a>
<a href="/settings" class="block rounded px-3 py-2 text-sm hover:bg-gray-200">Settings</a>
</nav>
</aside>
<main class="flex-1 p-6">
<h1 class="text-2xl font-bold">Analytics Dashboard</h1>
<p class="mt-2 text-gray-600">Your performance metrics at a glance.</p>
</main>
</div>Correct (sidebar collapses to full-width section above content on mobile):
<!-- Sidebar stacks above content on mobile, sits beside it on lg screens -->
<div class="flex flex-col lg:flex-row">
<aside class="w-full border-b bg-gray-50 p-4 lg:w-[250px] lg:shrink-0 lg:border-b-0 lg:border-r">
<nav>
<h2 class="mb-3 font-semibold lg:mb-4">Dashboard</h2>
<!-- Horizontal scroll on mobile, vertical list on desktop -->
<div class="flex gap-2 overflow-x-auto lg:flex-col lg:gap-0">
<a href="/overview" class="shrink-0 rounded px-3 py-2 text-sm hover:bg-gray-200">Overview</a>
<a href="/analytics" class="shrink-0 rounded px-3 py-2 text-sm hover:bg-gray-200">Analytics</a>
<a href="/reports" class="shrink-0 rounded px-3 py-2 text-sm hover:bg-gray-200">Reports</a>
<a href="/settings" class="shrink-0 rounded px-3 py-2 text-sm hover:bg-gray-200">Settings</a>
</div>
</nav>
</aside>
<main class="flex-1 p-4 lg:p-6">
<h1 class="text-2xl font-bold">Analytics Dashboard</h1>
<p class="mt-2 text-gray-600">Your performance metrics at a glance.</p>
</main>
</div>Key principle: Use flex-col lg:flex-row on the container and w-full lg:w-[250px] on the sidebar. The sidebar becomes a horizontal nav strip or stacked section on mobile, only becoming a fixed-width column when the viewport is wide enough.
Stack Elements on Mobile, Row on Desktop
The most common responsive pattern is elements sitting side-by-side on desktop that must stack vertically on mobile. Starting with flex-row as the default causes horizontal overflow on narrow screens because flex items get squeezed into unusable widths. Always start with flex-col (mobile-first) and switch to flex-row at wider breakpoints.
Incorrect (flex-row default that overflows on mobile):
<!-- Items squeeze to ~100px each on a 375px screen, text truncates and images distort -->
<div class="flex flex-row gap-6">
<div class="w-1/3 rounded-lg border p-4">
<img src="/feature-1.jpg" alt="Feature 1" class="h-32 w-full object-cover" />
<h3 class="text-lg font-semibold">Real-time Sync</h3>
<p class="text-sm text-gray-600">Collaborate with your team in real time across all devices.</p>
</div>
<div class="w-1/3 rounded-lg border p-4">
<img src="/feature-2.jpg" alt="Feature 2" class="h-32 w-full object-cover" />
<h3 class="text-lg font-semibold">Smart Search</h3>
<p class="text-sm text-gray-600">Find anything instantly with AI-powered search.</p>
</div>
<div class="w-1/3 rounded-lg border p-4">
<img src="/feature-3.jpg" alt="Feature 3" class="h-32 w-full object-cover" />
<h3 class="text-lg font-semibold">Analytics</h3>
<p class="text-sm text-gray-600">Track performance with detailed dashboards.</p>
</div>
</div>Correct (stack on mobile, row on desktop with mobile-first approach):
<!-- Stacks vertically on mobile, switches to row at md breakpoint -->
<div class="flex flex-col gap-4 md:flex-row md:gap-6">
<div class="rounded-lg border p-4 md:w-1/3">
<img src="/feature-1.jpg" alt="Feature 1" class="h-32 w-full rounded object-cover" />
<h3 class="mt-3 text-lg font-semibold">Real-time Sync</h3>
<p class="mt-1 text-sm text-gray-600">Collaborate with your team in real time across all devices.</p>
</div>
<div class="rounded-lg border p-4 md:w-1/3">
<img src="/feature-2.jpg" alt="Feature 2" class="h-32 w-full rounded object-cover" />
<h3 class="mt-3 text-lg font-semibold">Smart Search</h3>
<p class="mt-1 text-sm text-gray-600">Find anything instantly with AI-powered search.</p>
</div>
<div class="rounded-lg border p-4 md:w-1/3">
<img src="/feature-3.jpg" alt="Feature 3" class="h-32 w-full rounded object-cover" />
<h3 class="mt-3 text-lg font-semibold">Analytics</h3>
<p class="mt-1 text-sm text-gray-600">Track performance with detailed dashboards.</p>
</div>
</div>Key principle: flex-col is the mobile default; md:flex-row opts into horizontal layout only when there is enough room. Width constraints like md:w-1/3 should also be breakpoint-prefixed so items remain full-width on mobile.
Convert Sticky Elements to Static on Mobile
Sticky headers, toolbars, and sidebars consume fixed vertical space that is scarce on mobile screens. A 64px sticky header plus a 48px sticky toolbar already takes 112px from a 667px iPhone viewport -- that is 17% of the screen permanently unavailable for content. On desktop these elements improve navigation, but on mobile they should scroll with the page to maximize readable area.
Incorrect (sticky elements that consume 30% of mobile viewport):
<div class="min-h-screen">
<!-- 64px sticky header -->
<header class="sticky top-0 z-30 border-b bg-white px-4 py-4">
<div class="flex items-center justify-between">
<h1 class="text-lg font-bold">Project Board</h1>
<button class="rounded bg-blue-600 px-3 py-1.5 text-sm text-white">New Task</button>
</div>
</header>
<!-- 48px sticky toolbar — stacks below the header -->
<div class="sticky top-[64px] z-20 border-b bg-gray-50 px-4 py-3">
<div class="flex gap-2">
<button class="rounded border px-3 py-1 text-sm">All</button>
<button class="rounded border px-3 py-1 text-sm">In Progress</button>
<button class="rounded border px-3 py-1 text-sm">Done</button>
</div>
</div>
<!-- Content area now starts 112px down and loses that space while scrolling -->
<main class="p-4">
<p class="text-gray-600">Your tasks will appear here.</p>
</main>
</div>Correct (static on mobile, sticky on desktop):
<div class="min-h-screen">
<!-- Scrolls naturally on mobile, sticks on lg screens -->
<header class="static border-b bg-white px-4 py-4 lg:sticky lg:top-0 lg:z-30">
<div class="flex items-center justify-between">
<h1 class="text-lg font-bold">Project Board</h1>
<button class="rounded bg-blue-600 px-3 py-1.5 text-sm text-white">New Task</button>
</div>
</header>
<!-- Also static on mobile, sticky only on lg -->
<div class="static border-b bg-gray-50 px-4 py-3 lg:sticky lg:top-[64px] lg:z-20">
<div class="flex gap-2">
<button class="rounded border px-3 py-1 text-sm">All</button>
<button class="rounded border px-3 py-1 text-sm">In Progress</button>
<button class="rounded border px-3 py-1 text-sm">Done</button>
</div>
</div>
<!-- Full viewport available for content on mobile -->
<main class="p-4">
<p class="text-gray-600">Your tasks will appear here.</p>
</main>
</div>Key principle: Replace sticky top-0 with static lg:sticky lg:top-0. The static keyword is the CSS default, so you can also simply omit positioning classes at the base level and only add lg:sticky lg:top-0. This returns the full viewport to content on mobile while keeping the navigation convenience on desktop.
Truncate Breadcrumbs on Mobile
Long breadcrumb trails (Home > Category > Subcategory > Product > Details) overflow on mobile, wrapping to 2-3 lines and consuming precious vertical space above the actual content. Users rarely need the full trail on mobile — showing the first crumb, an ellipsis for collapsed middle items, and the last 1-2 crumbs provides enough context to navigate back while keeping the breadcrumb to a single line.
Incorrect (full breadcrumb trail wraps to multiple lines on mobile):
<!-- Five breadcrumb items at ~60-100px each overflow a 375px screen, wrapping to 2-3 lines -->
<nav aria-label="Breadcrumb" class="px-4 py-3">
<ol class="flex flex-wrap items-center gap-2 text-sm text-gray-600">
<li><a href="/" class="hover:text-gray-900">Home</a></li>
<li class="before:mr-2 before:content-['/']">
<a href="/electronics" class="hover:text-gray-900">Electronics</a>
</li>
<li class="before:mr-2 before:content-['/']">
<a href="/electronics/computers" class="hover:text-gray-900">Computers & Tablets</a>
</li>
<li class="before:mr-2 before:content-['/']">
<a href="/electronics/computers/laptops" class="hover:text-gray-900">Laptops</a>
</li>
<li class="before:mr-2 before:content-['/'] font-medium text-gray-900" aria-current="page">
MacBook Pro 16-inch
</li>
</ol>
</nav>Correct (collapsed middle items on mobile, full trail on desktop):
<!-- Middle breadcrumb items hidden on mobile, replaced by an ellipsis indicator -->
<nav aria-label="Breadcrumb" class="px-4 py-3">
<ol class="flex items-center gap-2 text-sm text-gray-600">
<!-- First item — always visible -->
<li>
<a href="/" class="hover:text-gray-900">Home</a>
</li>
<!-- Middle items — hidden on mobile, shown from md up -->
<li class="hidden before:mr-2 before:content-['/'] md:list-item">
<a href="/electronics" class="hover:text-gray-900">Electronics</a>
</li>
<li class="hidden before:mr-2 before:content-['/'] md:list-item">
<a href="/electronics/computers" class="hover:text-gray-900">Computers & Tablets</a>
</li>
<!-- Ellipsis — shown only on mobile to indicate collapsed items -->
<li class="before:mr-2 before:content-['/'] md:hidden">
<span class="text-gray-400" aria-label="collapsed breadcrumb items">...</span>
</li>
<!-- Last parent — always visible for one-tap back navigation -->
<li class="before:mr-2 before:content-['/']">
<a href="/electronics/computers/laptops" class="hover:text-gray-900">Laptops</a>
</li>
<!-- Current page — always visible -->
<li class="before:mr-2 before:content-['/'] font-medium text-gray-900" aria-current="page">
MacBook Pro 16-inch
</li>
</ol>
</nav>Key principle: Use hidden md:list-item on middle breadcrumb items to hide them on mobile and restore them on desktop. Add a md:hidden ellipsis <li> between the first crumb and the last parent. Always keep the first item (root) and last 1-2 items visible so the user can orient and navigate back.
Expand Dropdown Menus to Full-Width on Mobile
Narrow dropdown menus designed for mouse hover are difficult to use on touch devices. A w-48 (192px) dropdown with py-1 padding produces touch targets that are too small and too close together, leading to frequent mis-taps. On mobile, expand dropdowns to full viewport width with generous padding to create finger-friendly tap targets that meet the 44px minimum recommended by WCAG and Apple HIG.
Incorrect (narrow dropdown with small touch targets on mobile):
<!-- 192px-wide dropdown with cramped items — nearly impossible to tap accurately on a phone -->
<div class="relative">
<button
type="button"
class="flex items-center gap-1 text-sm font-medium text-gray-700 hover:text-gray-900"
>
Categories
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<div class="absolute left-0 top-full z-20 mt-1 w-48 rounded-md border bg-white py-1 shadow-lg">
<a href="/category/electronics" class="block px-4 py-1.5 text-sm text-gray-700 hover:bg-gray-100">Electronics</a>
<a href="/category/clothing" class="block px-4 py-1.5 text-sm text-gray-700 hover:bg-gray-100">Clothing</a>
<a href="/category/home-garden" class="block px-4 py-1.5 text-sm text-gray-700 hover:bg-gray-100">Home & Garden</a>
<a href="/category/sports" class="block px-4 py-1.5 text-sm text-gray-700 hover:bg-gray-100">Sports & Outdoors</a>
<a href="/category/books" class="block px-4 py-1.5 text-sm text-gray-700 hover:bg-gray-100">Books</a>
<a href="/category/toys" class="block px-4 py-1.5 text-sm text-gray-700 hover:bg-gray-100">Toys & Games</a>
</div>
</div>Correct (full-width dropdown on mobile, positioned dropdown on desktop):
<!-- Full-width on mobile with generous touch targets, narrow positioned dropdown on desktop -->
<div class="relative md:static">
<button
type="button"
aria-expanded="false"
class="flex items-center gap-1 text-sm font-medium text-gray-700 hover:text-gray-900"
>
Categories
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<div class="absolute inset-x-0 top-full z-20 mt-1 rounded-md border bg-white shadow-lg md:inset-x-auto md:left-0 md:w-48">
<div class="py-2 md:py-1">
<a href="/category/electronics" class="block px-4 py-3 text-base text-gray-700 active:bg-gray-100 md:py-2 md:text-sm md:hover:bg-gray-100">Electronics</a>
<a href="/category/clothing" class="block px-4 py-3 text-base text-gray-700 active:bg-gray-100 md:py-2 md:text-sm md:hover:bg-gray-100">Clothing</a>
<a href="/category/home-garden" class="block px-4 py-3 text-base text-gray-700 active:bg-gray-100 md:py-2 md:text-sm md:hover:bg-gray-100">Home & Garden</a>
<a href="/category/sports" class="block px-4 py-3 text-base text-gray-700 active:bg-gray-100 md:py-2 md:text-sm md:hover:bg-gray-100">Sports & Outdoors</a>
<a href="/category/books" class="block px-4 py-3 text-base text-gray-700 active:bg-gray-100 md:py-2 md:text-sm md:hover:bg-gray-100">Books</a>
<a href="/category/toys" class="block px-4 py-3 text-base text-gray-700 active:bg-gray-100 md:py-2 md:text-sm md:hover:bg-gray-100">Toys & Games</a>
</div>
</div>
</div>Key principle: Use absolute inset-x-0 md:inset-x-auto md:left-0 md:w-48 so the dropdown spans full width on mobile but snaps to a fixed-width positioned menu on desktop. Increase padding with py-3 md:py-2 and font size with text-base md:text-sm on mobile to ensure each touch target exceeds the 44px minimum. Use active:bg-gray-100 instead of hover:bg-gray-100 on mobile for immediate tap feedback.
Collapse Horizontal Nav to Hamburger on Mobile
Horizontal navigation with 5+ links overflows or wraps awkwardly on mobile screens. On a 375px viewport, six nav links with spacing simply cannot fit in a single row — they either overflow off-screen or wrap into multiple lines, both of which break the visual hierarchy and confuse users. Replace the horizontal link list with a hamburger toggle that reveals a vertical menu on mobile, keeping the full horizontal nav on wider screens.
Incorrect (horizontal nav links overflow or wrap on mobile):
<!-- Six links at ~80px each plus gap-6 spacing need ~560px — overflows a 375px screen -->
<header class="border-b bg-white px-4 py-3">
<nav class="flex items-center justify-between">
<a href="/" class="text-xl font-bold">Acme</a>
<div class="flex gap-6">
<a href="/products" class="text-sm font-medium text-gray-700 hover:text-gray-900">Products</a>
<a href="/solutions" class="text-sm font-medium text-gray-700 hover:text-gray-900">Solutions</a>
<a href="/pricing" class="text-sm font-medium text-gray-700 hover:text-gray-900">Pricing</a>
<a href="/docs" class="text-sm font-medium text-gray-700 hover:text-gray-900">Documentation</a>
<a href="/blog" class="text-sm font-medium text-gray-700 hover:text-gray-900">Blog</a>
<a href="/contact" class="text-sm font-medium text-gray-700 hover:text-gray-900">Contact</a>
</div>
</nav>
</header>Correct (hamburger on mobile, full horizontal nav on desktop):
<!-- Links hidden on mobile behind a hamburger toggle, visible as a row from md up -->
<header class="border-b bg-white px-4 py-3">
<nav class="flex items-center justify-between">
<a href="/" class="text-xl font-bold">Acme</a>
<!-- Hamburger button — visible only on mobile -->
<button
type="button"
aria-label="Toggle menu"
aria-expanded="false"
data-toggle="mobile-menu"
class="inline-flex items-center justify-center rounded-md p-2 text-gray-700 hover:bg-gray-100 md:hidden"
>
<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>
<!-- Desktop nav links — hidden on mobile, flex row from md up -->
<div class="hidden md:flex md:gap-6">
<a href="/products" class="text-sm font-medium text-gray-700 hover:text-gray-900">Products</a>
<a href="/solutions" class="text-sm font-medium text-gray-700 hover:text-gray-900">Solutions</a>
<a href="/pricing" class="text-sm font-medium text-gray-700 hover:text-gray-900">Pricing</a>
<a href="/docs" class="text-sm font-medium text-gray-700 hover:text-gray-900">Documentation</a>
<a href="/blog" class="text-sm font-medium text-gray-700 hover:text-gray-900">Blog</a>
<a href="/contact" class="text-sm font-medium text-gray-700 hover:text-gray-900">Contact</a>
</div>
</nav>
<!-- Mobile menu panel — toggled via JS, hidden by default, never shown on md+ -->
<div id="mobile-menu" class="hidden flex-col gap-1 pb-3 pt-2 md:hidden">
<a href="/products" class="rounded-md px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100">Products</a>
<a href="/solutions" class="rounded-md px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100">Solutions</a>
<a href="/pricing" class="rounded-md px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100">Pricing</a>
<a href="/docs" class="rounded-md px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100">Documentation</a>
<a href="/blog" class="rounded-md px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100">Blog</a>
<a href="/contact" class="rounded-md px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100">Contact</a>
</div>
</header>Key principle: Use hidden md:flex md:gap-6 on the desktop link container and md:hidden on the hamburger button. The mobile menu panel uses md:hidden so it can never appear on desktop, and JS toggles the hidden class on that panel. Always include aria-label and aria-expanded on the hamburger button for accessibility.
Convert Sidebar Nav to Off-Canvas Drawer on Mobile
Persistent sidebar navigation consumes 250px+ on mobile, leaving only ~125px for content on a 375px screen — making text unreadable and interactions impossible. An off-canvas drawer slides in from the left edge when triggered and sits completely out of view otherwise, giving the main content 100% of the viewport width. On desktop, the sidebar remains always visible as a static element in the normal flow.
Incorrect (persistent sidebar squeezes content on mobile):
<!-- Sidebar always visible — on a 375px screen, main content gets only 111px -->
<div class="flex min-h-screen">
<aside class="w-64 shrink-0 border-r bg-slate-900 p-4 text-white">
<div class="mb-8 text-lg font-bold">WorkspaceApp</div>
<nav class="flex flex-col gap-1">
<a href="/dashboard" class="rounded-md bg-slate-800 px-3 py-2 text-sm font-medium">Dashboard</a>
<a href="/projects" class="rounded-md px-3 py-2 text-sm font-medium text-slate-300 hover:bg-slate-800">Projects</a>
<a href="/team" class="rounded-md px-3 py-2 text-sm font-medium text-slate-300 hover:bg-slate-800">Team</a>
<a href="/calendar" class="rounded-md px-3 py-2 text-sm font-medium text-slate-300 hover:bg-slate-800">Calendar</a>
<a href="/documents" class="rounded-md px-3 py-2 text-sm font-medium text-slate-300 hover:bg-slate-800">Documents</a>
<a href="/settings" class="rounded-md px-3 py-2 text-sm font-medium text-slate-300 hover:bg-slate-800">Settings</a>
</nav>
</aside>
<main class="flex-1 p-6">
<h1 class="text-2xl font-bold">Dashboard</h1>
<p class="mt-2 text-gray-600">Welcome back. Here is your project overview.</p>
</main>
</div>Correct (off-canvas drawer on mobile, static sidebar on desktop):
<div class="relative min-h-screen lg:flex">
<!-- Backdrop overlay — shown when drawer is open on mobile -->
<div
id="sidebar-backdrop"
class="fixed inset-0 z-30 hidden bg-black/50 lg:hidden"
data-close="sidebar"
></div>
<!-- Sidebar / Drawer -->
<aside
id="sidebar"
class="fixed inset-y-0 left-0 z-40 w-64 -translate-x-full border-r bg-slate-900 p-4 text-white transition-transform duration-300 ease-in-out lg:static lg:z-auto lg:translate-x-0 lg:transition-none"
>
<div class="flex items-center justify-between">
<span class="text-lg font-bold">WorkspaceApp</span>
<!-- Close button — only on mobile -->
<button
type="button"
aria-label="Close sidebar"
data-close="sidebar"
class="rounded-md p-1 text-slate-400 hover:text-white lg:hidden"
>
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<nav class="mt-8 flex flex-col gap-1">
<a href="/dashboard" class="rounded-md bg-slate-800 px-3 py-2 text-sm font-medium">Dashboard</a>
<a href="/projects" class="rounded-md px-3 py-2 text-sm font-medium text-slate-300 hover:bg-slate-800">Projects</a>
<a href="/team" class="rounded-md px-3 py-2 text-sm font-medium text-slate-300 hover:bg-slate-800">Team</a>
<a href="/calendar" class="rounded-md px-3 py-2 text-sm font-medium text-slate-300 hover:bg-slate-800">Calendar</a>
<a href="/documents" class="rounded-md px-3 py-2 text-sm font-medium text-slate-300 hover:bg-slate-800">Documents</a>
<a href="/settings" class="rounded-md px-3 py-2 text-sm font-medium text-slate-300 hover:bg-slate-800">Settings</a>
</nav>
</aside>
<!-- Main content -->
<main class="flex-1 p-4 lg:p-6">
<!-- Toggle button — only on mobile -->
<button
type="button"
aria-label="Open sidebar"
data-open="sidebar"
class="mb-4 rounded-md border p-2 text-gray-700 hover:bg-gray-100 lg:hidden"
>
<svg class="h-5 w-5" 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>
<h1 class="text-2xl font-bold">Dashboard</h1>
<p class="mt-2 text-gray-600">Welcome back. Here is your project overview.</p>
</main>
</div>Key principle: Use fixed inset-y-0 left-0 w-64 -translate-x-full lg:static lg:translate-x-0 on the sidebar. On mobile it is fixed and translated off-screen; JS toggles translate-x-0 / -translate-x-full when the user taps the hamburger. On lg: it becomes static with translate-x-0, sitting in normal flow. Always include a backdrop overlay and a close button for the mobile drawer state.
Compact the Header on Scroll for Mobile
Mobile headers with logos, search fields, and navigation actions can consume 80-120px of vertical space. On a 667px viewport, that is 12-18% of the screen permanently occupied by chrome instead of content. After the user has scrolled past the initial view, they have committed to the content — compacting the header to its essential elements (logo + primary actions) recovers 30-40px of viewport without losing navigability.
Incorrect (tall header stays at full height permanently on mobile):
<!-- 80px header permanently consumes 12% of a 667px mobile viewport -->
<header class="sticky top-0 z-50 h-20 border-b bg-white shadow-sm">
<div class="flex h-full items-center justify-between px-4">
<a href="/" class="flex items-center gap-2">
<img src="/logo.svg" alt="SiteLogoLarge" class="h-10 w-10" />
<span class="text-lg font-bold">BrandName</span>
</a>
<!-- Search field always visible at full size -->
<div class="mx-4 flex-1">
<div class="relative">
<input
type="search"
placeholder="Search products, articles..."
class="w-full rounded-full border bg-gray-50 py-2.5 pl-10 pr-4 text-sm"
/>
<svg class="absolute left-3 top-1/2 h-5 w-5 -translate-y-1/2 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
</div>
<div class="flex items-center gap-3">
<button type="button" aria-label="Notifications" class="rounded-full p-2 text-gray-600 hover:bg-gray-100">
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" /></svg>
</button>
<button type="button" aria-label="Account" class="rounded-full p-2 text-gray-600 hover:bg-gray-100">
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /></svg>
</button>
</div>
</div>
</header>Correct (header compacts on scroll, hiding search and shrinking logo on mobile):
<!-- Header transitions from h-20 to h-14 on scroll via JS-toggled class -->
<header
id="site-header"
class="sticky top-0 z-50 border-b bg-white shadow-sm transition-all duration-300 ease-in-out"
data-compact="false"
>
<!-- Full header state: h-20 with search visible -->
<div class="flex h-20 items-center justify-between px-4 transition-all duration-300 [.header-compact_&]:h-14">
<a href="/" class="flex items-center gap-2">
<img
src="/logo.svg"
alt="SiteLogo"
class="h-10 w-10 transition-all duration-300 [.header-compact_&]:h-7 [.header-compact_&]:w-7"
/>
<span class="text-lg font-bold transition-all duration-300 [.header-compact_&]:text-base">BrandName</span>
</a>
<!-- Search field — hidden when compacted on mobile, always visible on desktop -->
<div class="mx-4 hidden flex-1 md:block [.header-compact_&]:hidden [.header-compact_&]:md:block">
<div class="relative">
<input
type="search"
placeholder="Search products, articles..."
class="w-full rounded-full border bg-gray-50 py-2 pl-10 pr-4 text-sm"
/>
<svg class="absolute left-3 top-1/2 h-5 w-5 -translate-y-1/2 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
</div>
<div class="flex items-center gap-2">
<!-- Search icon — shown only in compact mobile state as a replacement -->
<button
type="button"
aria-label="Search"
class="hidden rounded-full p-2 text-gray-600 hover:bg-gray-100 [.header-compact_&]:inline-flex [.header-compact_&]:md:hidden"
>
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</button>
<button type="button" aria-label="Notifications" class="rounded-full p-2 text-gray-600 hover:bg-gray-100">
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" /></svg>
</button>
<button type="button" aria-label="Account" class="rounded-full p-2 text-gray-600 hover:bg-gray-100">
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /></svg>
</button>
</div>
</div>
</header>
<!-- Scroll listener toggles the .header-compact class on #site-header -->
<script>
const header = document.getElementById('site-header');
let lastScroll = 0;
window.addEventListener('scroll', () => {
const scrollY = window.scrollY;
if (scrollY > 60) {
header.classList.add('header-compact');
} else {
header.classList.remove('header-compact');
}
lastScroll = scrollY;
}, { passive: true });
</script>Key principle: Use a JS scroll listener to toggle a parent class (e.g., .header-compact) after a scroll threshold (60px). Tailwind's arbitrary variant [.header-compact_&] lets child elements react to the parent state — shrinking the logo, hiding the search field on mobile, and reducing the header height. Keep transition-all duration-300 on affected elements for smooth visual transitions. On desktop (md:), keep the search field visible even in the compact state.
Use Bottom Tab Bar for Primary Mobile Navigation
For apps with 3-5 primary sections, a bottom tab bar is significantly more thumb-friendly than a top hamburger menu. On modern phones with 6"+ screens, the top of the display sits in the hardest-to-reach zone. A bottom tab bar keeps primary navigation within the natural thumb arc, reducing interaction cost and increasing feature discoverability since all sections are visible at once.
Incorrect (hamburger menu at top forces long thumb reach on mobile):
<!-- User must stretch to top-left corner on a 6.7" phone to access primary navigation -->
<header class="sticky top-0 z-50 border-b bg-white px-4 py-3">
<div class="flex items-center justify-between">
<button type="button" aria-label="Open menu" class="rounded-md p-2 text-gray-700 hover:bg-gray-100">
<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>
<span class="text-lg font-bold">AppName</span>
<div class="w-10"></div>
</div>
</header>
<!-- Hidden until hamburger toggled — all sections buried behind one tap -->
<nav id="mobile-nav" class="hidden fixed inset-0 z-40 bg-white p-6">
<a href="/home" class="block py-3 text-lg font-medium text-gray-900">Home</a>
<a href="/search" class="block py-3 text-lg font-medium text-gray-900">Search</a>
<a href="/orders" class="block py-3 text-lg font-medium text-gray-900">Orders</a>
<a href="/favorites" class="block py-3 text-lg font-medium text-gray-900">Favorites</a>
<a href="/account" class="block py-3 text-lg font-medium text-gray-900">Account</a>
</nav>Correct (bottom tab bar on mobile, sidebar or top nav on desktop):
<!-- Bottom tabs for mobile, traditional sidebar for desktop -->
<div class="flex min-h-screen flex-col md:flex-row">
<!-- Desktop sidebar — hidden on mobile -->
<aside class="hidden md:flex md:w-56 md:shrink-0 md:flex-col md:border-r md:bg-gray-50 md:p-4">
<span class="mb-6 text-lg font-bold">AppName</span>
<nav class="flex flex-col gap-1">
<a href="/home" class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-gray-900 bg-gray-100">
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-4 0h4" /></svg>
Home
</a>
<a href="/search" class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100">
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>
Search
</a>
<a href="/orders" class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100">
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 012-2h2a2 2 0 012 2M9 5h6" /></svg>
Orders
</a>
<a href="/favorites" class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100">
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" /></svg>
Favorites
</a>
<a href="/account" class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100">
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /></svg>
Account
</a>
</nav>
</aside>
<!-- Main content — add bottom padding on mobile to clear the fixed tabs -->
<main class="flex-1 pb-20 md:pb-0">
<!-- Page content here -->
</main>
<!-- Mobile bottom tab strip — fixed at bottom, hidden on md+ -->
<nav class="fixed bottom-0 inset-x-0 z-50 flex justify-around border-t bg-white pb-[env(safe-area-inset-bottom)] md:hidden">
<a href="/home" class="flex flex-col items-center gap-1 px-3 py-2 text-xs font-medium text-blue-600">
<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="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-4 0h4" /></svg>
Home
</a>
<a href="/search" class="flex flex-col items-center gap-1 px-3 py-2 text-xs font-medium text-gray-500">
<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="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>
Search
</a>
<a href="/orders" class="flex flex-col items-center gap-1 px-3 py-2 text-xs font-medium text-gray-500">
<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="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 012-2h2a2 2 0 012 2M9 5h6" /></svg>
Orders
</a>
<a href="/favorites" class="flex flex-col items-center gap-1 px-3 py-2 text-xs font-medium text-gray-500">
<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.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" /></svg>
Favorites
</a>
<a href="/account" class="flex flex-col items-center gap-1 px-3 py-2 text-xs font-medium text-gray-500">
<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="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /></svg>
Account
</a>
</nav>
</div>Key principle: Use fixed bottom-0 inset-x-0 md:hidden for the mobile tab strip and hidden md:flex for the desktop sidebar. Add pb-20 md:pb-0 to the main content area so it does not get obscured by the fixed tab strip. Include pb-safe on the tab strip to account for the home indicator on notched devices.
Scale Avatar Sizes Per Context and Breakpoint
Avatars at a single fixed size create visual imbalance — a w-12 h-12 (48px) avatar dominates a compact mobile list item but feels tiny in a desktop profile header. Each context (list item, card, profile hero) needs its own base size, and each of those should scale across breakpoints. A list avatar might be 32px on mobile and 40px on desktop, while a profile avatar goes from 64px on mobile to 96px on desktop.
Incorrect (one avatar size used in all contexts):
<!-- Same w-12 h-12 avatar everywhere — too large in the list, too small in the profile header -->
<div class="mx-auto max-w-2xl space-y-8 px-4 py-6">
<!-- Profile header — avatar feels undersized next to the large heading -->
<div class="flex items-center gap-4 border-b border-gray-200 pb-6">
<img src="/avatars/sarah.jpg" alt="Sarah Chen" class="w-12 h-12 rounded-full" />
<div>
<h1 class="text-2xl font-bold text-gray-900">Sarah Chen</h1>
<p class="text-gray-500">Senior Product Designer</p>
</div>
</div>
<!-- Team member list — avatars crowd the compact rows -->
<ul class="divide-y divide-gray-100">
<li class="flex items-center gap-3 py-3">
<img src="/avatars/james.jpg" alt="James Park" class="w-12 h-12 rounded-full" />
<div>
<p class="text-sm font-medium text-gray-900">James Park</p>
<p class="text-xs text-gray-500">Engineering</p>
</div>
</li>
<li class="flex items-center gap-3 py-3">
<img src="/avatars/maria.jpg" alt="Maria Lopez" class="w-12 h-12 rounded-full" />
<div>
<p class="text-sm font-medium text-gray-900">Maria Lopez</p>
<p class="text-xs text-gray-500">Marketing</p>
</div>
</li>
</ul>
</div>Correct (avatar size varies by context and breakpoint):
<!-- Avatars sized per context: large in profile header, compact in list items, both scale per breakpoint -->
<div class="mx-auto max-w-2xl space-y-8 px-4 py-6">
<!-- Profile header — w-16 on mobile, w-24 on desktop to match heading prominence -->
<div class="flex items-center gap-4 border-b border-gray-200 pb-6 md:gap-6">
<img src="/avatars/sarah.jpg" alt="Sarah Chen" class="w-16 h-16 md:w-24 md:h-24 rounded-full object-cover ring-2 ring-gray-100" />
<div>
<h1 class="text-2xl font-bold text-gray-900">Sarah Chen</h1>
<p class="text-gray-500">Senior Product Designer</p>
</div>
</div>
<!-- Team member list — w-8 on mobile, w-10 on desktop for compact rows -->
<ul class="divide-y divide-gray-100">
<li class="flex items-center gap-3 py-3">
<img src="/avatars/james.jpg" alt="James Park" class="w-8 h-8 md:w-10 md:h-10 rounded-full object-cover" />
<div>
<p class="text-sm font-medium text-gray-900">James Park</p>
<p class="text-xs text-gray-500">Engineering</p>
</div>
</li>
<li class="flex items-center gap-3 py-3">
<img src="/avatars/maria.jpg" alt="Maria Lopez" class="w-8 h-8 md:w-10 md:h-10 rounded-full object-cover" />
<div>
<p class="text-sm font-medium text-gray-900">Maria Lopez</p>
<p class="text-xs text-gray-500">Marketing</p>
</div>
</li>
</ul>
</div>Key principle: Treat avatar sizing as context-dependent, not global. Profile headers need large avatars (w-16 md:w-24), list items need compact ones (w-8 md:w-10), and cards fall somewhere in between. Always add object-cover so non-square source images don't distort inside the circular crop.
Related skills
FAQ
What does tailwind-responsive-ui do?
tailwind-responsive-ui: A skill for development.
When should I use tailwind-responsive-ui?
When you need to use tailwind-responsive-ui for development tasks, or when tailwind-responsive-ui: a skill for development.
What are the main capabilities?
tailwind-responsive-ui.