Zero overflow in 10 minutes: responsive testing with Claude Code and Playwright

My markdown code viewer worked perfectly on desktop. On tablet, horizontal scroll. On mobile, buttons overflowing the viewport. The classic. Except this time, instead of spending 2 hours in DevTools manually resizing, I let Claude Code run the loop: screenshot → diagnose → fix → retest. 10 minutes, zero overflow across 10 resolutions. Here's the workflow.

The problem — the overflow you don't see

The scenario is always the same. You code on a 1920px screen. Everything lines up. The layout breathes. You ship. Then someone opens it on an iPad and a horizontal scrollbar appears. On an iPhone SE, a button bleeds past the right edge. The pre blocks expand beyond the viewport and drag the entire page with them.

The real issue isn't the CSS. It's the feedback loop. On desktop you never see the overflow — because there's nothing to overflow. Detecting it requires resizing, which requires switching tools, which requires discipline you don't have at 11 PM when you're shipping a feature.

There's a one-liner that exposes the problem programmatically:

document.querySelectorAll('*').filter(el =>
    el.scrollWidth > el.clientWidth
).map(el => el.tagName + ' ' + el.className)

Run this in the DevTools console at any viewport width and you get the exact list of offending elements. The problem: you have to remember to run it, at the right width, for every breakpoint. Nobody does that manually.

The tool — Playwright MCP in Claude Code

Playwright MCP exposes four tools that are all you need for this kind of work:

  • browser_navigate — load a URL in the headless browser
  • browser_resize — set the viewport to an exact width/height
  • browser_take_screenshot — capture the current state as an image
  • browser_evaluate — run arbitrary JavaScript in the page context

The key tool is browser_evaluate. It lets Claude Code run the overflow detection one-liner directly in the page, at any resolution, and get back the result as structured data. No screenshots needed to know if there's a problem — only to understand what it looks like.

The detection script:

Array.from(document.querySelectorAll('*'))
    .filter(el => el.scrollWidth > el.clientWidth)
    .map(el => ({
        tag: el.tagName,
        class: el.className,
        scrollWidth: el.scrollWidth,
        clientWidth: el.clientWidth,
        overflow: el.scrollWidth - el.clientWidth
    }))

Returns an empty array? No overflow. Returns elements? Claude has the tag, the class, and the exact number of pixels overflowing. Enough to go straight to the fix.

The loop workflow

The workflow runs against 8 target resolutions, chosen to cover the main device categories:

Resolution Target
375pxiPhone SE — narrowest mainstream screen
390pxiPhone 14/15 — current iOS baseline
412pxAndroid mid-range (Pixel, Samsung A-series)
480pxLarge phone / small phone landscape
768pxiPad portrait
900pxiPad landscape / small laptop
1280pxStandard laptop
1920pxDesktop — the baseline nobody tests against

For each resolution, the loop is:

  1. Resizebrowser_resize to the target width
  2. Navigatebrowser_navigate to reload the page
  3. Evaluate — run the overflow detection script
  4. Screenshot — only if overflow is detected (saves time)
  5. Fix — edit the CSS directly in the source file
  6. Reload and retest — confirm the fix eliminated the overflow

The loop doesn't move to the next resolution until the current one returns zero overflow. No approximation, no "probably fine on mobile".

The prompt that does everything

One prompt to Claude Code and it runs the entire loop autonomously:

I need you to do a full responsive overflow audit on my blog view page.

Target URL: http://localhost:8000/blog/claude-md/view.php?ctx=mobile-css-redesign

Test these resolutions in order: 375, 390, 412, 480, 768, 900, 1280, 1920px.

For each resolution:
1. browser_resize to the target width (height: 900)
2. browser_navigate to reload the page
3. browser_evaluate this script:
   Array.from(document.querySelectorAll('*'))
     .filter(el => el.scrollWidth > el.clientWidth)
     .map(el => ({ tag: el.tagName, class: el.className,
                   overflow: el.scrollWidth - el.clientWidth }))
4. If the result is non-empty: browser_take_screenshot, then identify the
   CSS rule causing the overflow and fix it in the source file directly.
5. After each fix: reload and re-evaluate to confirm zero overflow.
6. Move to next resolution only when current one is clean.

Report: for each resolution, list elements that overflowed and the fix applied.
Final summary: total fixes made, time per resolution.

That's the entire prompt. Claude Code reads it, runs Playwright MCP, edits the CSS files when needed, retests, and gives you a summary at the end. You watch it work.

Concrete results

On my view.php — the markdown code viewer for the CLAUDE.md catalogue — the audit found 6 overflow issues across 4 resolutions. Before: 2 hours of manual DevTools resizing. After the automated loop: 10 minutes, including Claude Code time to identify and fix each issue.

The issues found, in order of frequency:

  • Pre blocks expanding beyond viewport (375px, 390px, 412px) — pre elements with no white-space: pre-wrap or word-break: break-word. The code was wider than the screen.
  • Button row overflowing on mobile (375px, 390px) — a flex row of three buttons with no flex-wrap: wrap. At 375px, the third button disappeared off-screen.
  • Inline code spilling (480px) — code elements inside paragraphs with white-space: nowrap inherited from a parent rule. A single long identifier broke the layout.
  • Sidebar min-width too wide (768px) — a min-width: 320px on a panel that should collapse to width: 100% on tablet.

Each fix was applied directly to blog.css or view.php's inline styles, retested immediately, and confirmed clean before moving on.

The manual alternative: open DevTools, drag the viewport handle, squint at the scrollbar, right-click → Inspect, trace which rule is overriding what, fix, reload, repeat. For 8 resolutions and 6 issues, that's 2+ hours minimum.

CSS rules to remember

The fixes applied in this session reduce to five CSS patterns that cover 90% of real-world overflow cases:

1. overflow-x: hidden on html and body. The base safety net. Doesn't fix the root cause, but contains the damage. Warning: breaks position: sticky if applied to the wrong element.

html, body {
    overflow-x: hidden;
    max-width: 100%;
}

2. pre-wrap and word-break on code blocks. pre elements preserve whitespace, which means they expand horizontally forever if you let them. These two rules wrap them instead.

pre {
    white-space: pre-wrap;
    word-break: break-word;
    overflow-x: auto; /* fallback for very long unbreakable tokens */
}

3. flex-wrap: wrap on button rows. Any flex row that contains multiple buttons needs this on mobile. Fixed widths in a flex container are a recipe for overflow.

.button-row {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

4. max-width: 100% on images and media. An image wider than its container will push the layout. This should be in every project's reset.

img, video, svg, canvas {
    max-width: 100%;
    height: auto;
}

5. text-wrap: balance on headings. Not an overflow fix — a readability one. Prevents awkward line breaks on narrow viewports where a long heading wraps with one word on the last line.

h1, h2, h3 {
    text-wrap: balance;
}

For a complete mobile CSS reference — touch targets, iOS zoom prevention, safe-area-inset, form inputs — see the article on mobile CSS best practices.

Conclusion

Responsive isn't a CSS problem. It's a feedback loop problem. The CSS rules that fix overflow are not complex — five patterns cover most cases. The problem is that you don't see the overflow until you're at the right viewport, and getting to the right viewport, for every breakpoint, is friction enough that it doesn't happen consistently.

The Playwright MCP loop eliminates that friction. Claude Code runs the detection script at every resolution, identifies the element, reads the source, applies the fix, retests. The loop is mechanical — exactly the kind of work that should be automated.

The workflow works on any page served locally. The prompt is generic enough to reuse as-is on any project. The only thing that changes is the URL. For the full context on how to integrate this into a CLAUDE.md workflow, see the article on specialized CLAUDE.md for mobile redesign.

📄 Associated CLAUDE.md

Comments (0)