Overflow & Layering
Control clipping and scrolling with overflow-*, and manage what sits on top with z-index, isolation and stacking contexts.
Two problems that show up together: what happens when content is too big for its box, and what happens when boxes overlap.
Overflow#
| Utility | Behaviour |
|---|---|
overflow-auto | Scrollbars only when needed |
overflow-scroll | Scrollbars always reserved |
overflow-hidden | Clip, no scrolling |
overflow-clip | Clip, and disable programmatic scrolling too |
overflow-visible | Default — content spills out |
overflow-x-auto / overflow-y-auto | Per-axis |
<div class="grid gap-4 p-4 sm:grid-cols-2">
<div>
<p class="mb-1 font-mono text-xs text-gray-500">overflow-auto</p>
<div class="h-24 overflow-auto rounded-lg border border-gray-200 p-3 text-sm text-gray-700">
<p class="mb-2">Scrollable region.</p>
<p class="mb-2">More content here.</p>
<p class="mb-2">And more again.</p>
<p>The end.</p>
</div>
</div>
<div>
<p class="mb-1 font-mono text-xs text-gray-500">overflow-hidden</p>
<div class="h-24 overflow-hidden rounded-lg border border-gray-200 p-3 text-sm text-gray-700">
<p class="mb-2">Clipped region.</p>
<p class="mb-2">More content here.</p>
<p class="mb-2">And more again.</p>
<p>You cannot reach this.</p>
</div>
</div>
</div>Overflow's side effects#
overflow does more than clip. Setting anything other than visible:
- Creates a block formatting context — floats are contained, margins stop collapsing.
- Makes the element a scroll container, which breaks
position: stickyon descendants. - Clips absolutely positioned children, even ones intended to escape.
Rounded corners need clipping#
<div class="overflow-hidden rounded-xl border border-gray-200">
<img class="w-full" src="…" alt="">
</div>Without overflow-hidden the image's square corners poke through the rounded border.
Scroll behaviour#
| Utility | Purpose |
|---|---|
scroll-smooth | Animate programmatic scrolling |
overscroll-contain | Stop scroll chaining to the page |
snap-x / snap-mandatory + snap-start | Scroll snapping |
scroll-mt-* | Offset for anchor targets under a sticky header |
<div class="flex snap-x snap-mandatory gap-4 overflow-x-auto pb-4">
<div class="w-64 shrink-0 snap-start rounded-lg bg-gray-100 p-4">Slide 1</div>
<div class="w-64 shrink-0 snap-start rounded-lg bg-gray-100 p-4">Slide 2</div>
</div>Scrollbar styling#
v4.3 First-party scrollbar utilities.
| Utility | CSS |
|---|---|
scrollbar-auto / scrollbar-thin / scrollbar-none | scrollbar-width |
scrollbar-thumb-* | Thumb colour |
scrollbar-track-* | Track colour |
scrollbar-gutter-auto / -stable / -both | scrollbar-gutter |
<div class="scrollbar-thin scrollbar-thumb-sky-700 scrollbar-track-sky-100 h-48 overflow-auto">
…
</div>scrollbar-gutter-stable reserves space for the scrollbar so content does not shift when it appears — worth knowing for dialogs and dynamic lists.
Layering#
z-index#
| Utility | Value |
|---|---|
z-0 z-10 z-20 z-30 z-40 z-50 | Positive steps |
z-auto | Default |
-z-10 | Negative |
z-[999] | Arbitrary |
z-index only applies to positioned elements (relative, absolute, fixed, sticky) and to flex/grid children.
Stacking contexts#
A stacking context is a self-contained layer. Children can never escape it, no matter how high their z-index.
A new stacking context is created by:
positionother thanstaticwith az-indexother thanautoopacityless than 1 — includingopacity-95transform,filter,backdrop-filter,perspectiveisolate(isolation: isolate)will-changeon any of the above propertiesmix-blend-modeother thannormal
<!-- The child's z-50 cannot beat the sibling's z-10,
because the parent's opacity-90 traps it. -->
<div class="relative opacity-90">
<div class="absolute z-50">Trapped</div>
</div>
<div class="relative z-10">Always on top</div>isolate#
isolate creates a stacking context deliberately, so a component's internal layering cannot leak out or be interfered with.
<article class="isolate relative rounded-xl">
<div class="absolute inset-0 -z-10 bg-gradient-to-br from-indigo-500 to-purple-600"></div>
<div class="p-6 text-white">Content sits above the gradient.</div>
</article>Without isolate, -z-10 would push the gradient behind the page background rather than behind the card's content.
A z-index scale worth adopting#
Fixed numbers scattered through a codebase drift. Name them:
@theme {
--z-dropdown: 10;
--z-sticky: 20;
--z-header: 30;
--z-overlay: 40;
--z-modal: 50;
--z-toast: 60;
}<div class="z-(--z-modal)">…</div>Common patterns#
Truncating text#
<!-- Single line -->
<p class="truncate">A very long single line…</p>
<!-- Fixed number of lines -->
<p class="line-clamp-3">Three lines, then an ellipsis…</p>Inside a flex item, both need min-w-0 on the item — see Flexbox.
Horizontally scrollable table#
<div class="overflow-x-auto rounded-lg border border-gray-200">
<table class="w-full min-w-3xl text-sm">…</table>
</div>min-w-* on the table forces the wrapper to scroll rather than squashing columns.
Locking body scroll behind a modal#
<body class="has-[dialog[open]]:overflow-hidden">Common mistakes#
z-indexon a static element. Addrelative.- Fighting stacking contexts with bigger numbers.
z-[9999]cannot escape a parent context. Find the ancestor instead. overflow-hiddenon a wrapper containing a dropdown. The menu gets clipped; useoverflow-visibleor portal the menu.overflow-autoon the sticky element's ancestor. Breaksstickysilently.overflow-hiddenwhereoverflow-clipis meant.overflow-clipclips without creating a scroll container, so it does not break sticky descendants — often the better choice for purely decorative clipping.
Related topics#
- Position — the positioning schemes underneath
- Flexbox —
min-w-0and truncation - Filters & Effects — the properties that create stacking contexts
- Modals & Toasts — layering in practice