One-screen mobile layout: no page scroll, proven by a Playwright walk

Fit every screen of a flow into one viewport (top bar, scaling middle, bottom action bar), then walk it at several sizes and get a table of anything that scrolls, overflows or gets clipped.

Claude Code intermediate Tested Opus 5.5 · Nov 2026May be dated

The prompt

Run it in a Claude Code session on the repository. Install Playwright if needed (npm i -D playwright && npx playwright install chromium). In app, say where the code lives, how to run it (dev server, preview URL or a local file) and what is wrong today. If unsure of viewports, use 375x548, 390x664, 412x780, 1280x800.

Fill in the blanks3 placeholders · empty ones stay as {{…}}

Example

Input:

  • app: the plant-care onboarding quiz in quiz/: one index.html that swaps five screens, plain CSS and JS, no framework. Today each screen is min-height: 100vh with a fixed button bar over the content, and every screen scrolls on a small phone.
  • screens:
    1. welcome: illustration, headline, two-sentence intro, Start. Loads at /.
    2. room: "Where does your plant live?", six picture tiles in a grid, Back and Next (enabled once a tile is picked).
    3. light: "How much light does it get?", four text options with a one-line description each, Back and "See my plan".
    4. saving: spinner and "Building your care plan…"; moves on by itself after 3 seconds.
    5. result: care profile as an SVG bar chart with four labelled bars (Light, Water, Humidity, Temperature), two tips, "Start over" and "Get my care plan".
  • viewports: 390x664, 375x548, 412x780, 1280x800

Output (trimmed to the walk's first and last tables, a few of its problem lines, and part of the closing report):

| screen  | 390x664             | 375x548             | 412x780             | 1280x800            |
|---------|---------------------|---------------------|---------------------|---------------------|
| welcome | page scrolls +73px  | page scrolls +178px | page scrolls +57px  | page scrolls +57px  |
| room    | page scrolls +288px | page scrolls +414px | page scrolls +205px | page scrolls +527px |
| light   | page scrolls +133px | page scrolls +281px | page scrolls +57px  | page scrolls +57px  |
| saving  | page scrolls +57px  | page scrolls +57px  | page scrolls +57px  | page scrolls +57px  |
| result  | page scrolls +57px  | page scrolls +57px  | page scrolls +57px  | page scrolls +57px  |

Problems (130):
…
  375x548   room (tile picked)                 spill top +84px header.site-header
…
  375x548   result (on arrival)                label "Light" 10.3px < 11px in svg.chart
…
  375x548   result (on arrival)                text 13.6px < 14px p.step "Step 3 of 3"
…
| screen  | 390x664 | 375x548 | 412x780 | 1280x800 |
|---------|---------|---------|---------|----------|
| welcome | ok      | ok      | ok      | ok       |
| room    | ok      | ok      | ok      | ok       |
| light   | ok      | ok      | ok      | ok       |
| saving  | ok      | ok      | ok      | ok       |
| result  | ok      | ok      | ok      | ok       |

Problems: none

Every screen now fits on one screen at all four sizes, and the walk shows it: before the change all 20 cells failed, after it all 20 are ok. Nothing had to be cut. The smallest phone size (375x548) still has 15 to 54px to spare on every screen.

…

  • result: the chart now fills its box (at most 480x240), and app.js redraws it whenever it is shown or resized:
    • label size is the larger of 12 drawing units and 11 divided by the chart's scale (getScreenCTM().a), so labels never go below 11px on screen;
    • the bars get shorter to make room for bigger labels, rather than the labels shrinking.

What the short-screen query (max-height: 640px) hides: only the light screen's instruction, "Think of the brightest hour of a normal day." It would fit at 375x548 with 20px to spare. Hiding it leaves 54px, so a description that wraps to an extra line on a wider phone font still fits. …

  • Below the tested sizes: at 320x480 the light screen overflows by 23px and the middle scrolls. That size isn't on the list; say if it should be.

(trimmed)

The original layout failed all 20 cells, desktop included, and behind the scroll the walk also caught 13.6px step labels, chart labels under 11px at the two smallest sizes and a header pushed off the top after a tile was picked; after the changes every cell is ok. The report names the one line the short-screen query hides and what that bought, and flags a size below the tested list where the light screen still overflows.

Why it works

  • min-height: 0 and a size container do the fitting. A flex item defaults to min-height: auto (a grid row to minmax(auto, 1fr)), so tall content pushes the page down instead of shrinking. With min-height: 0 the middle gets exactly what the two bars leave, and media capped at 100cqh scales into it. A size container ignores its content, so unless it is a flex item with a floor it collapses to 0px, which check h catches. A bottom bar inside the column can't cover the last option the way a fixed bar does, and lines with reserved space can't push it when a state changes.
  • svh, and viewports measured inside the browser. svh is the height with the toolbars showing and doesn't change while the address bar moves, so the layout fits the worst case and never resizes mid-gesture. Testing 375x548, a small phone in a browser, rather than the device's 375x667 is what catches the real failures.
  • A walk, not a screenshot. With the clock paused, timers fire the same way on every run, and finishing running animations means the walk measures the final frame, not a transition halfway through. Clicking through by role, in every state, covers result screens, error lines and sheets that a page-by-page check never loads. Numeric checks (scroll sizes, bounds, elementFromPoint, font sizes, label boxes) turn "looks fine on my phone" into a table you can re-run after every change.
  • No escape hatches. Banning overflow: hidden, dropped actions and weakened checks stops the agent passing the walk by hiding the problem, and checks g and h stop it shrinking the type or collapsing the media instead. Chart labels grow in drawing units as the chart shrinks (11 / scale), so the plot gives up room, not the type; inputs stay at 16px because iOS Safari zooms into smaller ones. The short-height query cuts secondary copy on purpose, and the report says what went, so you can veto it.

Variations

  • Long, flowing pages. For a deck or article, let a section grow past one screen on phones (min-height: 100svh, scroll-snap-type: y proximity), give each figure a minimum size at which its labels fit, and run the label check at many sizes, landscape phones included.
  • Run it in CI. Point the walk at a preview URL through BASE and fail the pull request on any non-ok cell; attach the screenshots as an artifact.
  • Generalised from a quiz app and a slide deck.