Nothing on This Page Is Too Wide
A page can slide sideways under your thumb while every single element on it fits inside the screen. The usual way to find the culprit is to look for the element wider than the viewport. Here are two detectors, running live in your browser on the same specimens, and a count of what each one found across all 1,032 pages of this site.
Ask how to find what is making a page scroll sideways and you will be told, more or less everywhere, to look for the element that is wider than the viewport. Walk the DOM, take each element's bounding rectangle, and flag anything whose right edge lands past the screen. It is good advice. It works most of the time.
It also returns nothing at all on a page that is visibly, measurably scrolling. Not "nothing useful": nothing. Every element fits, the widest right edge is exactly the width of the screen, and the document is still 232 pixels wider than the window. When that happens the recipe does not fail loudly. It reports a clean sweep, and you go and look somewhere else.
This is not a hypothetical. Below are three specimens. The first is the ordinary case and the recipe finds it. The second is the one this page is about. Run both detectors on each and watch them disagree.
Two detectors, one specimen at a time
Each stage below is a real 390-pixel-wide column with real content inside it, in your browser, laid out by your browser's own engine. Nothing here is a picture of a result. The recipe walks every element and reports the widest right edge, skipping anything an ancestor already scrolls or clips. Ablation hides one thing at a time and re-measures the whole column, keeping only what actually made it narrower.
Some ordinary prose above it.
And some below.
Ready.
Provenance line, of the kind this site prints under a figure:
source SHA-256 45ea884f4b47ea8f96e9a5a32c764a65b35525277aab40272b1bbff4a4a75af5
Nothing else unusual.
Ready.
45ea884f4b47ea8f96e9a5a32c764a65b35525277aab40272b1bbff4a4a75af5
1f8a660589ad181575b622e671cdc8bc6cdcefed617c52314ba982080d71c1de
Ready.
Why the recipe cannot see specimen 2
Because a bounding rectangle describes a box, and CSS lets ink land outside the box.
The paragraph holding that hash is exactly as wide as its column. Its rectangle
fits. What does not fit is its content: scrollWidth is far larger than
clientWidth, overflow-x is visible, so the
characters are painted straight out past the edge of the element that contains them.
Ask every element how wide it is and every element answers honestly, and the answer is
not the question you needed.
There are two more cases a rectangle sweep cannot reach. A bare text node has no
element and therefore no rectangle at all; you have to put a Range around
it to measure it. And a pseudo-element is not in the DOM you are walking.
Ablation does not care which of these it is, and that is the whole of its advantage. It never asks how wide anything is. It removes something, measures the document, and puts it back. If the document got narrower, that thing is part of the reason it was wide. That is not a heuristic about widths; it is the definition of the thing being looked for.
One pass is not an account
Remove-one-at-a-time has a failure of its own, which specimen 3 is built to show. Those two hashes are the same length, so in a monospace font they are exactly the same width. Remove either one and the box does not get one pixel narrower, because the other is still holding it open. A single remove-one pass therefore concludes that neither is to blame, which is the correct answer to the question it asked and the wrong answer to the question you have. Ablation gets past it by hiding every candidate and then revealing them one at a time against that empty background, which tests each one on its own rather than in the presence of its rivals. Watch the readout say so.
The same problem shows up along the other axis, in sequence rather than in parallel: a small cause standing behind a large one is invisible until the large one is gone. So the search runs in rounds, keeps what it has found hidden, and looks again at what is left. Then it hides everything it found at once and reports what is still unaccounted for, so that a partial answer is never mistaken for a complete one. On the real page described below, the hash accounted for 98 of 111 pixels and the remaining 13 were simply standing in its shadow.
What happened when both were run over this whole site
On 31 August 2026 a real browser was driven at a 390-pixel viewport over every built page of this site: 1,032 pages, three OG card templates excluded because they exist to be photographed at 1200 pixels. Nothing in the build chain had ever looked.
38 pages were wider than the phone they were being read on. They split into two defects that a width measurement alone cannot tell apart:
| What the reader gets | Pages |
|---|---|
| the document scrolls sideways, usually to blank space | 29 |
| content is clipped off instead: the page looks fine and part of it is unreachable | 9 |
The second group sets overflow-x: hidden on the body, which propagates to
the viewport. Nothing scrolls and something is simply gone. A check that measures
scrollWidth and stops has been counting both of those as one defect.
How often the recipe would have come up empty
| Of the 38 broken pages | Count |
|---|---|
| the widest unclipped element fits inside the viewport: the recipe finds nothing at all | 11 |
| the widest rectangle under-reads the document's real width | 14 |
| fully attributed by ablation, residual zero | 37 |
The page that prompted all this was already known to be broken. Someone had measured it
at 505 pixels wide in a 390 window, hunted for the cause, and stopped honestly at
"the widest element outside any scroller measures only 392px",
with 115 pixels unaccounted for. Ablation named it on the first run: a paragraph of
provenance carrying a 64-character SHA-256 with overflow-wrap: normal, its
content 483 pixels wide inside a 353-pixel box. The document goes 501, then 403 once
that paragraph is hidden, then 390 once the three figures behind it are. Residual zero.
That is the same specimen you ran above.
The defect turned out to be structural, not careless
36 of the 38 were standalone pages. The two that were not were both in the
clipped group, held by a rule they inherit and the others do not. This site's
Astro-rendered pages load a stylesheet that gives them overflow-x: hidden
on the body and a horizontal scroller for wide tables. A standalone page in
public/ never loads it. The same stylesheet also ships
white-space: nowrap on the first cell of a table, next to the rule that
rescues it, and the hazard travels by copy-paste into pages that have no
.prose to rescue them. The defect tracked the stylesheet, not the author.
The fix, and what measuring it changed
The obvious guard is a blanket rule making every table on a narrow screen scroll inside its own box. Written, applied to the whole corpus and then measured, it fixed 25 pages and restyled 52 that had no problem, one of them by 29 per cent of its height, purely because a rule aimed at a defect it did not have reached it anyway. So the guard that shipped measures first: if the document already fits, it touches nothing.
Two more corrections came out of measuring rather than reasoning. Detection has to
compare a table to its parent, not to the viewport, because a 382-pixel table
in a 350-pixel column on a 390-pixel screen is real horizontal scroll and is not wider
than the screen. And max-width: 100% is not always tighter than what the
author already wrote: on one page it won the cascade against a deliberate 240-pixel cap,
relaxed it, and pushed a page that had fit 74 pixels over. The rule now carries
zero specificity, so anything the page itself says beats it.
| Measured the same way, before and after | Before | After |
|---|---|---|
| pages wider than a 390px viewport | 38 | 15 |
| …the reader can scroll | 29 | 13 |
| …content clipped off | 9 | 2 |
| total overflow across the corpus | 3,730px | 1,449px |
| pages newly broken by the guard | n/a | 0 |
23 pages fixed, none broken. Fifteen remain, each with a proven cause recorded, and shrinking that list is somebody's next evening.
The control that stopped this page making a false claim
Every page's height was recorded before and after, to catch a guard quietly rearranging pages it was not aimed at. 36 pages changed height. Then the same code was run twice and the two runs compared: 10 of those 36 do not survive it. The two largest apparent regressions, one of them 717 pixels, resolve to exactly zero. They are instrument pages whose height depends on what they happen to draw. Without that control this page would be reporting two regressions that do not exist. The 26 real changes top out at 150 pixels, 2.50 per cent of a page: a long word now breaks onto a second line, on lines that were already sticking out of their column.
The page that scrolls to nothing
One of the 38 is not accounted for, and it is the most interesting of them. The document
scrolls 225 pixels, confirmed two independent ways. The strip it scrolls to is
empty: ask the browser what is at that coordinate after scrolling and it names
the root element, which is to say nothing. The body's own scrollWidth is
390 throughout, so nothing inside the body overflows. The page's own table scroller is
working exactly as its author built it. Hiding the table it holds takes the document
from 615 back to 390 anyway.
A real defect a reader can feel, on a page with no element too wide, no failing scroller, and no mechanism this run could name. It is recorded as an open question rather than closed with a plausible story. It is also the strongest case for ablation there is, since the recipe that looks for a wide element has, here, nothing whatever to look at.
The check
Everything above is reproducible offline from a fresh checkout. The verifier makes 60 assertions in three kinds, and the third kind is the one that matters: the central claim is re-derived in a browser on specimens built for it, with no reference to this site's corpus at all. It runs both detectors on a page carrying a long hash and requires that the recipe reports a widest right edge no larger than the viewport while the document is measurably wider, and that ablation then finds the cause and accounts for all of it. If every number on this page were wrong, that check would still stand.
node research/nothing-on-this-page-is-too-wide/verify.mjs
The gate that keeps it true is scripts/check-mobile-overflow.mjs, now in
the build chain. Its --selftest proves it can go red, on the three causes
the census actually found, while staying green on wide content correctly held inside a
scroller. The full lab record, including three ways the instrument was wrong before it
was right, is in
research/nothing-on-this-page-is-too-wide/.
What this does not show
- One browser, one width. Every number here is Chromium at 390 CSS pixels. Nothing has looked at 320 pixels, where more pages will fail, and nothing has looked at any other engine. Layout is not identical across engines and these counts should not be assumed to transfer.
- "The recipe" is our writing of it. The sweep run here takes each element's
bounding rectangle and skips anything an ancestor scrolls or clips. That is the
common form and it is the form that fails as described. A sweep that also ranged over
text nodes, or compared
scrollWidthtoclientWidth, would do better; the point is that the advice as usually given does not include those, and the count of 11 is against the version as usually given. - The census is of one site. 3.7 per cent of pages broken is this corpus, whose pages are unusually script-heavy and unusually full of hashes and long decimals. It is not an estimate of the web.
- Ablation is a causal test, not an explanation. It tells you truthfully that removing something narrows the page. It does not tell you why that thing is wide, and on the page described above it does not produce a mechanism at all.
- The remaining 15 are not fixed, and two of them are outside the gate's scope because it serves the unbuilt source directory rather than the built site.