Overflow and scrolling

Control what happens to content that does not fit, and stop a page scrolling sideways.

13of 26
≈ 9Min read
8Parts

The values

per axis
overflow: auto;              /* both */
overflow: hidden auto;       /* x then y */
overflow-x: auto;
overflow-y: hidden;
ValueWhat happens
visibleContent spills out and is still painted. The default.
hiddenClipped. Still scrollable by script and by focus, which is sometimes a surprise.
clipClipped, with no scrolling at all — not even programmatic.
scrollA scrollbar always, whether it is needed or not.
autoA scrollbar only when it is needed. The one you usually want.
You cannot mix visible with a clipping value

overflow-x: visible; overflow-y: auto is not possible: setting one axis to a clipping value forces the other to auto. If a dropdown is being cut off, the scroll container is the reason.

A scroll container changes several things

Giving an element an overflow value other than visible makes it a scroll container, and that has consequences beyond the scrollbar:

  • It becomes a block formatting context — floats are contained and margins stop collapsing through it.
  • It becomes the containing block for position: sticky inside it. A sticky header inside an overflow: hidden wrapper sticks to that wrapper, not the page.
  • Anything absolutely positioned inside is clipped by it.
  • It gets its own scroll position, which the browser may restore on navigation.

The horizontal scrollbar hunt

One element slightly wider than the viewport makes the whole page scroll sideways. The usual causes:

  • width: 100vw on a page that scrolls vertically — 100vw includes the scrollbar. Use 100%.
  • A negative margin or a translate pushing something past the right edge.
  • A long unbroken string — a URL, a hash, a code sample.
  • A flex or grid item without min-width: 0, refusing to shrink below its content.
  • A fixed-width element wider than a small screen.
find it in one line
[...document.querySelectorAll("*")]
  .filter(el => el.getBoundingClientRect().right > document.documentElement.clientWidth + 1)
and the blunt fix that also tells you where it is
* { outline: 1px solid rgb(255 0 0 / .3); }

Long words and URLs

overflow-wrap: break-word is the safe default for body text. word-break: break-all breaks mid-word even when it did not have to, which reads badly in prose.

let text break instead of overflowing
.prose {
  overflow-wrap: break-word;   /* break a word only if it cannot fit at all */
  hyphens: auto;               /* needs the right lang on the element */
}

.code {
  word-break: break-all;       /* break anywhere — for code and hashes only */
}

Scrolling a table

tabindex="0" is not decoration: a scroll container that a keyboard cannot reach is content a keyboard user cannot read. The role and label make it announce what it is.

the container does the scrolling
<div class="tablewrap" tabindex="0" role="region" aria-label="Lead times">
  <table>…</table>
</div>
style.css
.tablewrap {
  overflow-x: auto;
  overscroll-behavior-x: contain;   /* do not chain to the page */
}

Scroll snap

mandatory always lands on a snap point; proximity only snaps when you are close. Prefer proximity for long lists, where mandatory can make it impossible to stop in between.

a carousel with no JavaScript
.rail {
  display: flex;
  gap: 1rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: 1rem;
}

.rail > * {
  flex: none;
  width: min(80%, 24rem);
  scroll-snap-align: start;
}

Overscroll and smooth scrolling

two small quality details
.drawer {
  overflow-y: auto;
  overscroll-behavior: contain;   /* scrolling past the end does not scroll the page */
}

html {
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

overscroll-behavior: contain is what stops the page underneath a modal from scrolling when the modal reaches its end — the problem people used to solve by locking the body.

Styling scrollbars

scrollbar-gutter: stable on html is worth adding to most sites: without it, a page that gains a scrollbar shifts its whole layout sideways by 15 pixels.

the standard properties
.panel {
  scrollbar-width: thin;
  scrollbar-color: rgb(255 255 255 / .22) transparent;
  scrollbar-gutter: stable;   /* reserve the space so content does not shift */
}

Practice