overflow: hidden vs scroll vs auto.
Clip, scroll, or both.
Three values, three behaviors. hidden clips content — no scrollbar. scroll always shows scrollbars, even when content fits. auto shows them only when needed. Each has a specific use, and each has side effects that surprise developers. This page covers the difference, the scrollbar layout shift, the two-axis gotcha, and why overflow: hidden breaks sticky.
Short answer: Use overflow: auto for scrollable containers — the least surprising behavior. Use overflow: hidden when you deliberately want to clip content (image crops, rounded corners on images). Use overflow: scroll only when you want to reserve scrollbar space consistently. Add scrollbar-gutter: stable on html to prevent layout shift. Know that overflow: hidden breaks position: sticky on descendants and clips shadows.
✅ The four values
Each one clips or scrolls differently.
overflow: visible
Default. Content overflows the container and is visible outside it. No clipping, no scrollbar.
overflow: hidden
Content is clipped — no scrollbar, no way to see the overflow. Content is invisible outside the box.
overflow: scroll
Scrollbars are always shown, even if content fits. Layout space reserved. Consistent but often ugly.
overflow: auto
Scrollbars appear only when content overflows. Layout space may shift when they appear. Usually the right choice.
✅ Before and after
The same container, three overflow values.
/* overflow: hidden — clip */
.container-hidden {
width: 300px;
height: 200px;
overflow: hidden;
border: 1px solid #2965f1;
}
/* overflow: scroll — always show scrollbar */
.container-scroll {
width: 300px;
height: 200px;
overflow: scroll;
border: 1px solid #2965f1;
}
/* overflow: auto — scrollbar when needed */
.container-auto {
width: 300px;
height: 200px;
overflow: auto;
border: 1px solid #2965f1;
}
Content: 400px tall, inside a 200px container. overflow: hidden ┌─────────────────┐ │ Content visible │ │ Content visible │ │ Content visible │ │ ███████████████ │ ← clipped at 200px └─────────────────┘ No scrollbar. Rest of content is invisible. overflow: scroll ┌─────────────┬───┐ │ Content │ ▲ │ │ visible │ █ │ ← scrollbar always shown │ │ █ │ └─────────────┴───┘ Scrollbar always present, even if content fits. overflow: auto ┌─────────────┬───┐ │ Content │ ▲ │ │ visible │ █ │ ← scrollbar when needed │ │ █ │ └─────────────┴───┘ Scrollbar shown only when content overflows. ✅ auto is the least surprising. ✅ hidden for deliberate clipping. ✅ scroll for reserved-space patterns (rare).
✅ The two-axis gotcha
Mixing hidden with visible doesn't work.
/* ❌ Doesn't work as expected */
.container-bad {
overflow-x: hidden;
overflow-y: visible;
/* The browser treats this as auto/auto —
both axes become auto. Not what you wanted. */
}
/* ✅ Explicit — one hidden, one auto */
.container-good {
overflow-x: hidden;
overflow-y: auto;
}
/* ✅ overflow: clip — newer, no scroll container */
.container-clip {
overflow: clip;
/* Clips without creating a scroll container.
Sticky still works on descendants. */
}
/* ✅ For horizontal-scroll carousels */
.carousel {
overflow-x: auto;
overflow-y: hidden;
white-space: nowrap;
}
The spec rule: If one axis is visible and the other is anything else (hidden, scroll, auto), the browser treats BOTH as auto. ❌ overflow-x: hidden; overflow-y: visible → becomes auto / auto. Both axes scroll. ✅ overflow-x: hidden; overflow-y: auto → hidden on x, auto on y. As intended. ✅ overflow: clip → clips both axes, no scroll container. Sticky still works. Shadows still clip. ⚠️ overflow: clip is newer (2022+). For older browsers, use overflow: hidden on the specific axis and accept the side effects. Common patterns: Horizontal carousel: overflow-x: auto, overflow-y: hidden. Modal that clips: overflow: hidden on the container. Sticky header with clip: overflow: clip on the wrapper.
✅ The scrollbar layout shift
Reserve the scrollbar gutter, once.
/* ✅ The modern fix — one line on html */
html {
scrollbar-gutter: stable;
}
/* ✅ Fallback for older browsers — reserve space */
html {
padding-right: calc(100vw - 100%);
}
/* ✅ Modern scrollbar styling (WebKit) */
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background: #f1f5f9;
}
::-webkit-scrollbar-thumb {
background: #94a3b8;
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background: #64748b;
}
/* ✅ Modern scrollbar styling (Firefox) */
html {
scrollbar-width: thin;
scrollbar-color: #94a3b8 #f1f5f9;
}
The problem: Page A: short content → no scrollbar. Page B: long content → scrollbar, content shifts left by ~15px. User sees the whole page jump when navigating between pages. Misclicks, disorientation. The fix: scrollbar-gutter: stable on html → reserves scrollbar space always. → content position never shifts. → one line, works everywhere since 2024. Older fallback: padding-right: calc(100vw - 100%) → on html or the body wrapper. → works in all browsers. → slight layout differences on mobile, where scrollbars are overlay. Scrollbar styling: WebKit: ::-webkit-scrollbar (all Chrome/Safari/Edge) Firefox: scrollbar-width, scrollbar-color No single standard — use both. ⚠️ Don't hide scrollbars entirely (scrollbar-width: none) unless you provide an alternative. Users who rely on scrollbar position lose functionality. scrollbar-width: thin is a better compromise.
Don't add overflow: hidden to hide horizontal scroll. It works — the horizontal scrollbar disappears — but it also breaks position: sticky on any descendant, clips box-shadows and dropdowns, and hides absolutely positioned children that extend past the edges. The problem is usually an element that's accidentally too wide — find it and fix it. If you must hide horizontal scroll, use overflow-x: clip (newer) which doesn't create a scroll container, or apply overflow at the narrowest possible container — not on body or the layout wrapper. Every "why doesn't sticky work?" bug starts with a stray overflow: hidden.
⚠️ Common mistakes
The overflow mistakes that cost real time.
overflow: hidden to hide horizontal scrollBreaks sticky, clips shadows. Fix: find the wide element, or use overflow-x: clip.
hidden with visibleBrowsers treat both as auto. Fix: explicit overflow-x: hidden; overflow-y: auto.
Layout shifts between pages. Fix: scrollbar-gutter: stable on html.
overflow: scroll "to be safe"Always shows scrollbars — ugly on non-scrolling content. Fix: auto.
scrollbar-width: none removes accessibility. Fix: thin or style them.
Applied to body, breaks sticky everywhere. Fix: apply to the specific container.
It does — but flow-root is cleaner. Fix: display: flow-root on the parent.
<video>, <iframe> — use object-fit instead. Fix: object-fit: cover.
❓ Frequently asked
What is the difference between overflow: hidden, scroll, and auto?
overflow: hidden clips content that overflows — no scrollbar, content is invisible. overflow: scroll always shows scrollbars, even when content fits. overflow: auto shows scrollbars only when content overflows. Use auto for most scrolling containers. Use hidden when you deliberately want to clip content. Use scroll only when you want to reserve space for the scrollbar consistently.
Why does overflow: hidden break position: sticky?
overflow: hidden creates a new scrolling container. Sticky positions relative to the nearest scrolling ancestor, which is now that container — not the viewport. If the container doesn't scroll vertically, sticky has nowhere to stick. Fix: remove overflow from ancestors of sticky elements, or use display: flow-root instead, or move the sticky element outside the overflow container in the DOM.
What is the scrollbar layout shift?
When a page goes from no scrollbar to showing one, the content shifts horizontally by the width of the scrollbar. It happens when opening a modal that locks scroll, or navigating between pages where one is shorter. Fix: reserve space for the scrollbar always, using scrollbar-gutter: stable on the html element.
Can I style the scrollbar?
Partially. WebKit/Blink browsers support ::-webkit-scrollbar and related pseudo-elements. Firefox supports scrollbar-width and scrollbar-color. The modern approach: use both — scrollbar-width and scrollbar-color for Firefox, ::-webkit-scrollbar for Chrome/Safari. Don't hide scrollbars entirely unless you provide an alternative.
Why does overflow: hidden still show a scrollbar?
Two causes. (1) overflow: hidden only applies to the element's own overflow — if the element is itself taller than the viewport, the page scrolls. (2) overflow-x: hidden combined with overflow-y: visible — browsers treat the pair as auto/auto when one axis is hidden. Fix: use overflow-x: hidden; overflow-y: auto for horizontal clip + vertical scroll, or use overflow: clip.
Does overflow: hidden improve performance?
Slightly, sometimes. overflow: hidden creates a containing block and a new block formatting context — the browser can optimize rendering for it. This is a minor effect, not a reason to add it. The main reasons to use overflow: hidden are visual (clipping content) and layout (preventing float collapse, hiding horizontal scroll).