When a page shifts sideways on a phone, the first move is usually to check document.documentElement.scrollWidth in the console. If it exceeds the viewport, something is overflowing. The trouble is that the number can look perfectly correct while the screen is visibly clipped.
The number is fine, the screen is not
If some ancestor in the middle carries overflow: hidden, anything overflowing inside it is cut off right there. As far as the document is concerned nothing overflows, so scrollWidth equals the viewport exactly. Meanwhile the right edge of a card is sliced off and half a button has disappeared.
The reverse happens too: a large scrollWidth while every visible element sits where it should, because a decorative element or a closed menu panel is parked off-screen. Either way, a single document-level number cannot tell you where the problem is.
Finding the culprit by rectangle
Measure every element and keep the ones crossing the viewport edge. This pastes straight into the console.
const w = document.documentElement.clientWidth; [...document.querySelectorAll('*')].filter(el => { const r = el.getBoundingClientRect(); return r.right > w + 1 || r.left < -1; }).slice(0, 20);
Expect several results — when a child is pushed out, its ancestors go with it. So read from the deepest element backwards; the real cause is usually there. To confirm, give that element a temporary outline: 2px solid and it becomes obvious on screen.
Five common causes
That first line deserves a note. 100vw includes the vertical scrollbar, so wherever a scrollbar takes up space it is always wider than the content area. Building a full-bleed background with something like margin-left: calc(50% - 50vw) adds a second failure: if the element is not horizontally centred, one side lands in the wrong place — which it will be next to any off-centre layout such as a table-of-contents rail. If you need a full-bleed band, make that section itself full width instead.
Do not paper over it with overflow-x: hidden
The quickest-looking fix is body { overflow-x: hidden; }. The symptom vanishes, the cause does not, and there are two costs. The overflowing element is still off-screen, so part of it stays clipped; and hiding one axis while leaving the other visible makes the visible axis compute to a scrolling value.
That second cost is the quiet one. Once you have created a scroll container, every position: sticky inside it stops working — no error, no warning, it simply does not stick. The final part of this series is about exactly that.
Turning front-end checks into a repeatable routine is covered in the development workflow archive, and the diagnostic plugin we publish is on the free tools page.
Next part
With the layout holding still, the next question is operating it. Next: tap target sizes, and where a cursor-designed interface fails under a fingertip.