Responsive Design Pass
- Explain Tailwind's default breakpoint scale (sm/md/lg/xl/2xl) and its mobile-first prefix behavior
- Identify where flex-wrap removes the need for a breakpoint entirely versus where a breakpoint is the right tool
- Read the dashboard header's real responsive classes and explain what each one does at a narrow viewport
- Recognize when a single-column list layout does not need additional grid breakpoints
- Write a Claude Code prompt that names a specific viewport and constraint instead of asking for generic responsiveness
- Verify a responsive change by inspecting the rendered layout at a target width, not just reading the diff
One Breakpoint, Used Deliberately
It is tempting to think a "responsive design pass" means adding sm:, md:, lg:, and xl: variants to everything in sight. The reputation dashboard takes the opposite approach, and it is worth understanding why before copying a heavier pattern into your own project. Tailwind's default breakpoint scale is sm (640px), md (768px), lg (1024px), xl (1280px), and 2xl (1536px) -- unprefixed utilities apply at every size, and a prefixed utility like sm:gap-4 only takes effect at that width and above, because Tailwind's responsive system is mobile-first. The dashboard's actual layout only needs one of those five breakpoints, because most of the responsive behavior comes from flexbox wrapping, not from swapping layouts at fixed widths.
Where the Real Breakpoint Is Used
app/dashboard/layout.tsx is the file with the dashboard's only responsive prefixes -- the header that wraps every page:
<div className="mx-auto flex max-w-5xl flex-wrap items-center justify-between gap-3 px-4 py-4">
<Link href="/dashboard" className="text-lg font-semibold text-slate-900">
Reputation Dashboard
</Link>
<div className="flex flex-wrap items-center gap-3 text-sm sm:gap-4">
<Link href="/dashboard/billing" className="text-slate-600 hover:text-slate-900">
Billing
</Link>
<span className="hidden text-slate-500 sm:inline">{user.email}</span>
<form action={logout}>
<button type="submit" className="rounded-md border border-slate-300 px-3 py-1.5 text-slate-700 hover:bg-slate-50">
Sign out
</button>
</form>
</div>
</div>
<main className="mx-auto max-w-5xl px-4 py-6 sm:py-8">{children}</main>
Three deliberate decisions are packed into a handful of classes. First, flex-wrap on both the outer header row and the inner nav group means the title, nav links, email, and sign-out button reflow onto a second line on a narrow screen instead of squeezing into one row or overflowing it -- no breakpoint required for that part at all. Second, hidden ... sm:inline on the user's email address removes it below 640px and brings it back above that width -- on a phone, the email address is the least useful piece of header text competing for a cramped first line, so it drops rather than truncating awkwardly. Third, gap-3 sm:gap-4 and py-6 sm:py-8 nudge spacing up slightly once there is more room, which is a refinement, not a structural change.
Where There Are Deliberately No Breakpoints
app/dashboard/page.tsx -- the review list itself -- has zero sm:/md:/lg:/xl: classes. The filter pills and review list items rely entirely on flex-wrap and relative units (max-w-5xl, percentage-based widths inside flex containers) to hold up across screen sizes. This is a real, intentional choice worth naming directly: a single-column list of cards does not need a different layout on desktop versus mobile -- it is already the right shape at every width from a phone to a laptop. Reaching for a grid grid-cols-1 md:grid-cols-2 pattern here would add a breakpoint to manage for no real gain, since multi-column review cards would not obviously read better at any width this app targets. The lesson here is not "always add more breakpoints" -- it is "add the breakpoint where the content genuinely needs to behave differently, and lean on flexbox wrapping everywhere else."
How Claude Code Helped Find the Real Problem Spots
The efficient way to do a responsive pass with Claude Code is not "make this responsive" as a blanket prompt against the whole app/dashboard/ tree -- that produces speculative breakpoints sprinkled everywhere, most of which fix nothing. A tighter prompt names the actual constraint:
Here is app/dashboard/layout.tsx. At a 375px viewport width, will the title, billing link, email, and sign-out button overflow or wrap awkwardly? Identify only the elements that would actually break at that width, and propose the smallest Tailwind class change to fix each one -- prefer flex-wrap and hidden/sm:inline over adding new grid breakpoints.
That framing -- name the specific viewport, ask what actually breaks, constrain the kind of fix -- is what produced the real hidden sm:inline and flex-wrap choices in this file, rather than a generic sweep that adds md: and lg: variants nothing in this layout needs.
Verifying It, Not Just Trusting the Diff
A responsive pass is one of the few kinds of change where reading the diff is not enough to confirm it actually works -- you have to look at the rendered page at the widths you claimed to fix. Resize a real browser window, or use your browser devtools' device toolbar set to a 375px-wide phone viewport, and confirm the header wraps and the email genuinely disappears below 640px rather than just trusting that the class names are correct. This is the same instinct as Lesson 17's testing philosophy applied visually: a class name being syntactically valid Tailwind is not the same claim as the layout looking right at the width it targets.
With loading states, error states, and a layout that holds up on a phone in place, the dashboard is visually done. The next two lessons turn to a different kind of confidence: knowing the app keeps working as it changes, starting with how to get Claude Code to write tests that actually catch real bugs instead of tests that just pad a coverage number.
- Tailwind breakpoints are mobile-first: an unprefixed class applies everywhere, sm: and up apply only at 640px and wider
- flex-wrap on a header row handles reflow without needing any breakpoint -- it is the first tool to reach for, not the last
- hidden sm:inline removes low-priority content (like a user email) below a breakpoint and restores it above -- useful when space, not layout shape, is the constraint
- app/dashboard/page.tsx intentionally has zero responsive prefixes because a single-column card list is already correct at every width this app targets
- A vague "make this responsive" prompt produces speculative breakpoints; naming the exact viewport and what actually breaks produces a minimal, correct fix
- Reading a CSS diff does not confirm a layout works -- check the rendered page at the target width before trusting the change