A page scrolls sideways when one element is wider than the viewport. The usual causes: fixed pixel widths, 100vw inside padding, long unbroken strings, wide tables and code blocks, images and iframes with width attributes, flex or grid children that cannot shrink, and elements positioned or transformed past the right edge. Find the element first; overflow-x: hidden only hides it.

Key takeaways

  • Every case in the tests was one element wider than the viewport. There is no mystery source; the job is finding which element.
  • A short console snippet names the offender. It lists the elements whose right edge is past the viewport, widest first.
  • Each cause has a one-line fix, and every fix brought the page back to exactly the viewport width at 390px.
  • Past the right edge scrolls; past the left edge does not. The same decorative box at right: -100px made the page 474px wide, at left: -100px it stayed 390px.
  • width: 100vw is wider than the page on desktop too when the browser has a classic scrollbar: 1,280px against 1,265px.
  • overflow-x: hidden on html and body hides the problem and breaks sticky positioning. It also cuts off the content that was too wide.

Find the element first

Open DevTools, switch to a phone width (390px is a common one) and paste this into the console. It lists elements whose right edge is past the viewport, widest first:

const w = document.documentElement.clientWidth;
[...document.querySelectorAll('body *')]
  .filter(el => el.getBoundingClientRect().right > w + 0.5)
  .map(el => ({ el, right: Math.round(el.getBoundingClientRect().right) }))
  .sort((a, b) => b.right - a.right)
  .slice(0, 5)
  .forEach(x => console.log(x.right, x.el));

On a test page with a flex row holding a long URL, it printed one line: the .cell element, at 893px. Elements inside a deliberate scroller, such as a table in an overflow-x: auto wrapper, will also be listed even though they do not widen the page, so check the top result first.

The 8 causes, measured at 390px

Each row is a small test page with one offender, in a mobile viewport 390px wide. "Page width" is the document's scroll width: 390 means no sideways scroll.

Page width with the offender, and after the fix. Chromium 141, 390px viewport.
Cause Page width Fix After
1. Fixed pixel width (width: 500px)516max-width: 100%390
2. width: 100vw inside a padded container406width: 100%390
2b. width: 100vw on desktop with a classic scrollbar1,280 vs 1,265width: 100%1,265
3. Long unbroken string, such as a URL893overflow-wrap: anywhere390
4. Wide table (six no-wrap columns)959Table inside an overflow-x: auto wrapper390
5. A long line inside pre1,943pre { overflow-x: auto }390
6. Image with width="800"816img { max-width: 100%; height: auto }390
6b. iframe with width="560"580iframe { max-width: 100% }390
7. Flex child holding a long string893min-width: 0 plus overflow-wrap on the child390
7b. Grid with a 1fr column and a long string893grid-template-columns: minmax(0, 1fr)390
8. Absolutely positioned box at right: -100px474Keep it inside, or wrap it in overflow: clip390
8b. transform: translateX(100%)732Wrap it in overflow: clip390
Same box at left: -100px390No fix needed-

Three of these are worth a second look. Flex and grid items default to a minimum size based on their content, so one long word can stretch the whole layout; which is the reason to declare a grid column minmax(0, 1fr) rather than 1fr, as this site's article layout does. Images are the same problem with a different trigger: give them a max-width as above, and dimensions too, as the layout-shift guide explains. And 100vw includes the scrollbar, which is why it overflows on desktop even when nothing looks wrong.

Why not just hide it

overflow-x: hidden on html and body does make the scrollbar disappear: both that and clip brought a 500px box on a 390px page back to a 390px page width. It has two costs. The content past the edge is still cut off, so a table or a button can be unreachable. And on both elements together it breaks position: sticky for everything on the page, as the sticky guide measures.

If you must contain a decorative element, contain that element. Wrap it in a box with overflow: clip instead of setting overflow on the whole page. That is the pattern behind the last two rows of the table, and it leaves sticky alone.

Testing it properly

The check is one comparison: is the document's scroll width greater than its client width? Run it at 390px on every template, not just the home page. This site's audit does exactly that for all of its pages, and the same check is also on the pre-launch checklist as a "test on an actual phone" item.

const sideways = document.documentElement.scrollWidth
  > document.documentElement.clientWidth;
console.log(sideways ? 'scrolls sideways' : 'fits');

What this does not cover

Chromium only, synthetic pages. The widths come from Chromium 141 with mobile emulation, on small pages with a single offender each. Real pages combine several, so fix the top result of the snippet and run it again. Safari on iOS has its own quirks around the viewport that were not tested.
A quick reflow check. Reflow at 320 pixels is one of six checks in the 10-minute accessibility test.

Badri Dutta

Software engineer · 8 years technical SEO

Written from the same afternoon as the sticky guide. The sideways scroll on this site was real, the quick fix broke sticky positioning, and measuring each cause separately was the only way to find the right fix.

Full background →