zudo-css-wisdom
GitHub repository

Type to search...

to open search from anywhere

Overflow Clip and Hidden

Per-axis overflow semantics — pure paint clipping with clip vs scroll containers with hidden, the forcing rule, and the grid paint-through bug class

The Problem

AI agents treat overflow: hidden as the universal fix for unwanted overflow. It is not — hidden does more than clip paint, and the extra behavior produces a recognizable class of bugs. Two real review findings turned entirely on overflow semantics:

  • In an app-shell grid (grid-template-rows: auto 1fr auto), oversized content in the body row painted through the sibling footer row. min-height: 0 kept the track geometry stable, but the default overflow: visible let the content overlay the next row.

  • The naive fix — overflow: hidden on the body — stopped the paint-through but sheared the inline-axis box-shadows of full-width children flat at the padding edge.

Both failures come from two under-known rules. First, hidden creates a scroll container, with scroll side effects you did not ask for. Second, the two overflow axes are not independent: visible cannot coexist with hidden, scroll, or auto on the other axis — it is silently forced to auto. The correct fix for the panel was neither hidden nor a wrapper element: overflow-x: visible; overflow-y: clip.

The Solution

Choose the overflow value by what you actually need:

  • A real scroll area (user scrolling, scrollTo, the single-line ellipsis recipe) — use auto, scroll, or hidden.

  • Paint clipping only — use clip. It creates no scroll container, does not force a new formatting context, lets the browser skip scroll machinery entirely, and accepts overflow-clip-margin.

  • Clipping on one axis while decorations (shadows, outlines) breathe on the other — use overflow-x: visible; overflow-y: clip (or the transposed pair). visible + clip is the only mixed pair the forcing rule leaves untouched.

clip vs hidden

Both values clip paint at the box edge. Everything else differs:

Behavioroverflow: hiddenoverflow: clip
Clips paintYes, at the padding boxYes, at the overflow clip edge (adjustable via overflow-clip-margin)
Creates a scroll containerYes — programmatically scrollable (scrollTop, scrollIntoView)No — the box cannot scroll at all
Scrolls on focus / fragment navigationYes, content shifts silently with no scrollbar to recover itNo
Participates in scroll anchoringYesNo
Establishes a new formatting contextYesNo
Pairs with visible on the other axisNo — forces it to autoYes
/* A scroll container you cannot see or operate */
.card {
  overflow: hidden;
}

/* Pure paint clipping */
.card {
  overflow: clip;
}

The scroll-container difference is not theoretical. A hidden box scrolls whenever the browser decides something inside must be revealed: keyboard focus moving to an off-screen descendant, location.hash navigation, scrollIntoView from a library. The content shifts, there is no scrollbar to shift it back, and the layout looks corrupted. A clip box cannot be scrolled by anything.

overflow: hidden Is a Scroll Container

Note

Focusable content hidden behind any clipping is an accessibility smell — keyboard users land on an element they cannot see. The demo only proves the scrollability difference; the real fix for interactive content is to not clip it.

The Per-Axis Forcing Rule

overflow is a shorthand for overflow-x and overflow-y, and the two axes are not independent. When one axis is hidden, scroll, or auto, a visible on the other axis computes to auto, and a clip computes to hidden. You silently get a scroll container on the axis you wanted left alone.

DeclaredComputedEffect
overflow-y: hidden (x left as visible)overflow-x: auto; overflow-y: hiddenSilent scroll container on the x axis
overflow-x: hidden (y left as visible)overflow-x: hidden; overflow-y: autoSilent scroll container on the y axis
overflow-x: clip; overflow-y: autooverflow-x: hidden; overflow-y: autoThe clip is upgraded to a scroll-container value
overflow-x: visible; overflow-y: clipUnchangedThe one legal mixed pair

visible + clip is the only combination that survives as written — and therefore the only way to say "clip block-axis overflow, let inline-axis decorations breathe."

/* What you wrote */
.track {
  overflow-y: hidden;
}

/* What you got */
.track {
  overflow-x: auto; /* forced — a scroll container you never declared */
  overflow-y: hidden;
}

/* The only way to clip one axis and keep the other truly visible */
.track {
  overflow-x: visible;
  overflow-y: clip;
}
visible Next to hidden Is Forced to auto

The Grid Paint-Through Bug

Grid and flex tracks have two separate failure layers when content is oversized: geometry and paint. min-height: 0 (or min-width: 0) fixes geometry — the track stops growing to fit its content. It does nothing about paint: with the default overflow: visible, the item's content keeps painting past the track edge, overlaying the footer row below and escaping the panel entirely. Grid items paint atomically in document order, so an opaque background on the footer masks its own strip — but that only hides the overlap; the overflow still runs past the panel border below it. Nothing contains the content until the track's item clips it.

.app-panel {
  display: grid;
  grid-template-rows: auto 1fr auto;
  height: 100dvh;
}

.app-panel__body {
  /* Step 1: geometry — let the track shrink below its content */
  min-height: 0;
  /* Step 2: paint — stop content from overlaying the footer */
  overflow-y: clip;
}

Both steps are required. min-height: 0 alone leaves the paint-through; overflow-y: clip alone leaves the track oversized. And the clip must be the per-axis form: the shorthand overflow: clip (or overflow: hidden) also clips the inline axis, which shears shadows and outlines — the next section. The min-width: 0 / min-height: 0 reset itself is covered in Flexbox Patterns.

Grid Row Paint-Through and the clip Fix

Shadow Shearing and the Per-Axis Fix

Clipping both axes cuts more than layout overflow — it cuts decorations. Full-width children inside an overflow: hidden container have their inline-axis box-shadows sheared flat at the container's padding edge, because the shadow paints outside the child's border box and the container clips everything beyond its own. The per-axis pair keeps the block-axis clip and releases the inline axis, so shadows and outlines render complete. Shadow construction itself is covered in Smooth Shadow Transitions.

/* NG: shears the shadows flat at the inline edges */
.card-stack {
  overflow: hidden;
}

/* OK: clips the block axis, lets inline-axis shadows breathe */
.card-stack {
  overflow-x: visible;
  overflow-y: clip;
}
overflow: hidden Shears Shadows; visible + clip Preserves Them

Both stacks clip the third card at the bottom — the block-axis clip works identically. Only the inline axis differs: the left stack cuts every shadow flat at the dashed border, while the right stack lets them bleed onto the background.

overflow-clip-margin

clip has one more capability hidden lacks: the clip edge can be pushed outward. overflow-clip-margin sets how far outside the padding box content may paint before being clipped — enough room for focus rings, glows, and small decorative bleeds to survive while runaway content is still contained.

.glow-frame {
  overflow: clip;
  overflow-clip-margin: 16px; /* clip 16px outside the padding box */
}
overflow-clip-margin Lets Decorations Survive the Clip

Browser support

overflow: clip itself is supported everywhere (Baseline 2022). overflow-clip-margin is supported in Chromium and Firefox but has not shipped in Safari — treat it as progressive enhancement: the clip still happens, only the extra margin is lost. Current implementations also apply it only when both axes compute to clip; it does not combine with the visible + clip pair.

Quick Reference

ScenarioTechnique
Real scroll area (user or programmatic scrolling)overflow: auto, or hidden + script-driven scrolling
Single-line ellipsisoverflow: hidden; text-overflow: ellipsis; white-space: nowrap
Decorative clipping — no scrolling everoverflow: clip
Clip block axis, keep inline-axis shadows/outlinesoverflow-x: visible; overflow-y: clip
Grid/flex track content painting over siblingsmin-height: 0 (geometry) + overflow-y: clip (paint)
Let glows/focus rings survive a clipoverflow: clip; overflow-clip-margin: 8px

Common AI Mistakes

  • Defaulting to overflow: hidden for any unwanted overflow. hidden creates a scroll container. Fragment navigation, scrollIntoView, and keyboard focus can silently scroll it, shifting the layout with no scrollbar to recover it. Use clip when you only need to cut paint.

  • Assuming overflow-y: hidden leaves the other axis alone. The forcing rule computes the untouched visible axis to auto — a horizontal scroll container you never declared.

  • Stopping at min-height: 0 when grid or flex content overflows a track. The min-size reset fixes track geometry only. Content still paints through the following rows until the item clips it with overflow-y: clip.

  • Fixing paint-through with overflow: hidden and shipping sheared shadows. The shorthand clips both axes, cutting inline-axis box-shadows and outlines flat at the padding edge. Use overflow-x: visible; overflow-y: clip.

  • Expecting overflow-clip-margin to work with hidden. It applies only to clip, and current implementations honor it only when both axes are clip.

  • Expecting a clip box to scroll. clip boxes are not scroll containers: scrollTop assignments are ignored and scrollIntoView cannot reveal clipped content. If anything must ever scroll the box, use auto or hidden.

When to Use

Use hidden when

You need scroll-container semantics without visible scrollbars: the single-line ellipsis recipe (overflow: hidden; text-overflow: ellipsis; white-space: nowrap), script-driven scrollers built on scrollTo, or measuring scrollable content. Accept the side effect that focus and fragment navigation can scroll the box.

Use clip when

You only need to cut paint and nothing should ever scroll: containing decorative bleed, a letterbox stage, print layouts where scroll containers are meaningless, or oversized background flourishes. clip is the cheaper and side-effect-free default for all decorative clipping.

Use the visible + clip pair when

One axis must clip while the other must stay truly visible: app-shell tracks whose content paints through sibling rows, and any container whose children carry inline-axis shadows, glows, or focus outlines that must render complete. visible + clip is the only per-axis combination the forcing rule leaves intact.

References

Revision History

CreatedUpdated