Responsive
Mobile-first layout patterns, breakpoints, and navigation strategies for MVP UI apps.
Breakpoints
MVP UI uses Tailwind v4 default breakpoints. All breakpoints are min-width — apply from the smallest applicable size upward.
sm:640pxLarge phones, small tabletsmd:768pxTablets — primary mobile/desktop splitlg:1024pxSmall desktops, landscape tabletsxl:1280pxStandard desktop2xl:1536pxWide desktop, data-dense layoutsThe md: breakpoint is the primary split between mobile and desktop layouts. Below md shows a top bar and drawer; at md and above shows the sidebar.
Mobile-first approach
Write the mobile layout first — no prefix. Add prefixed overrides for larger breakpoints. Never write desktop-first styles and try to undo them on mobile.
{/* WRONG — desktop-first, fighting mobile */}
<div className="flex-row md:flex-col">
{/* CORRECT — mobile-first, progressive enhancement */}
<div className="flex-col md:flex-row">Avoid hiding content with hidden on mobile unless the element has a full mobile substitute. Prefer layout reflow over toggling visibility where possible.
App layout pattern
Example app shells use h-screen overflow-hidden on the root to constrain the viewport and prevent page scroll. Internal panels own their own scroll context with overflow-y-auto.
<div className="flex h-screen overflow-hidden bg-bg">
{/* Sidebar — desktop only */}
<div className="hidden md:block shrink-0">
<AppSidebar />
</div>
{/* Main area */}
<div className="flex flex-1 min-w-0 flex-col overflow-hidden">
{/* Mobile top bar */}
<div className="flex shrink-0 items-center justify-between
border-b border-border-secondary bg-bg px-4 py-3 md:hidden">
{/* logo + hamburger */}
</div>
{/* Scrollable content */}
<main className="flex-1 overflow-y-auto">
{children}
</main>
</div>
</div>Key rules:
- Root uses
h-screen, notmin-h-screen. - Sidebar hidden via
hidden md:block— rendered in DOM, not conditional. - Top bar visible via
md:hidden— mirrors the sidebar toggle. - Content area uses
flex-1 min-w-0to prevent overflow bleed.
Mobile nav drawer
Replace the desktop sidebar with a Drawer on mobile. Open it from the hamburger button in the top bar. The drawer renders the same AppSidebar component — pass className="border-r-0 w-full" to strip the sidebar's own border when it sits inside a drawer.
import { Drawer } from "@mvp-ui/ui";
import { Menu } from "lucide-react";
// In the mobile top bar:
<button onClick={() => setNavOpen(true)} aria-label="Open navigation">
<Menu className="size-5" />
</button>
// Drawer placed adjacent to the layout root:
<Drawer
side="left"
size="sm"
isOpen={navOpen}
onOpenChange={setNavOpen}
aria-label="Navigation menu"
showCloseButton
>
<AppSidebar className="border-r-0 w-full" />
</Drawer>Use aria-label="Navigation menu" on the Drawer and aria-label="Open navigation" on the trigger button so screen readers announce both correctly.
Responsive grid
Prefer named grid patterns over arbitrary column counts. Common stacks:
grid-cols-1 md:grid-cols-2·Card pairs, form + previewgrid-cols-1 md:grid-cols-2 lg:grid-cols-3·Feature cards, stat blocksgrid-cols-1 sm:grid-cols-2 xl:grid-cols-4·Dashboard KPI tilesgrid-cols-1 lg:grid-cols-[260px_1fr]·Settings, detail pagesAlways pair gap-4 or gap-6 with a grid. Never use margin hacks to space grid children.
Responsive typography
Scale display and hero text across breakpoints. Body text stays fixed — scaling prose with the viewport introduces readability regressions.
{/* Hero heading — large on desktop, smaller on mobile */}
<h1 className="text-3xl md:text-4xl lg:text-5xl font-semibold text-fg">
Page heading
</h1>
{/* Section heading — stable */}
<h2 className="text-xl font-semibold text-fg">
Section heading
</h2>
{/* Body — never scale */}
<p className="text-md text-fg-secondary">
Description text
</p>Keep text size changes to one or two breakpoint steps per heading level. Skipping sizes (e.g. text-sm to text-4xl) creates jarring jumps.
Testing breakpoints
Test every meaningful page at the canonical widths. Use Chrome DevTools device toolbar or Playwright's page.setViewportSize.
320pxSmallest phone — overflow stress test375pxiPhone SE — common budget device768pxTablet / md breakpoint trigger1024pxSmall desktop / lg breakpoint trigger1440pxStandard desktop — primary design targetAt each width, verify:
- No horizontal overflow (
overflow-x: hiddenon body masks real bugs — check without it). - Sidebar ↔ top-bar toggle fires at
mdas expected. - Touch targets are at least 44×44 px on mobile widths.
- No text truncation in critical labels or headings.