MVP UI

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 tablets
md:768pxTablets — primary mobile/desktop split
lg:1024pxSmall desktops, landscape tablets
xl:1280pxStandard desktop
2xl:1536pxWide desktop, data-dense layouts

The 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, not min-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-0 to prevent overflow bleed.

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:

Single → two-column
grid-cols-1 md:grid-cols-2·Card pairs, form + preview
Single → three-column
grid-cols-1 md:grid-cols-2 lg:grid-cols-3·Feature cards, stat blocks
Single → four-column
grid-cols-1 sm:grid-cols-2 xl:grid-cols-4·Dashboard KPI tiles
Sidebar + content
grid-cols-1 lg:grid-cols-[260px_1fr]·Settings, detail pages

Always 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 test
375pxiPhone SE — common budget device
768pxTablet / md breakpoint trigger
1024pxSmall desktop / lg breakpoint trigger
1440pxStandard desktop — primary design target

At each width, verify:

  • No horizontal overflow (overflow-x: hidden on body masks real bugs — check without it).
  • Sidebar ↔ top-bar toggle fires at md as expected.
  • Touch targets are at least 44×44 px on mobile widths.
  • No text truncation in critical labels or headings.