A dropdown panel positioned with `position: absolute` is cut off at the edge of a card whose CSS sets `overflow: hidden`. What decides whether an ancestor's overflow clips a positioned descendant, and what are the ways out?
answer
- clipping follows the containing-block chain
- not every ancestor clips a positioned box
- who is the panel's containing block?
- fixed escapes; the card's relative does not
- z-index is not a clipping lever
basics
~20 sAn element with clipped overflow clips only descendants whose containing block is that element or something inside it. A positioned panel whose containing block is higher up escapes the clip — which is why position: fixed, or mounting the panel at the document root, fixes it.
solid answer
~50 sClipping follows the containing-block chain, not the DOM tree. A box with `overflow` other than `visible` clips its in-flow content and any descendant whose containing block lies at or below it — but a positioned descendant whose containing block is an ancestor *above* the clipping box is not clipped. So the dropdown is cut off because the card is `position: relative` (or otherwise the panel's containing block), which puts the panel inside the clipped area. Options: make the panel `position: fixed`, so its containing block is the viewport and the card's `overflow` no longer applies — assuming no transformed or contained ancestor in between; remove the clip from the card, usually by replacing `overflow: hidden` with something scoped to the element that actually needs it; or mount the panel in a root-level overlay container. Raising `z-index` never helps: clipping happens before stacking is considered.
code
css · 10 lines.card {
position: relative;
overflow: hidden;
}
.card .dropdown {
position: absolute;
top: 100%;
left: 0;
}go deeper
Recognise the symptom: a dropdown cut off at a container edge usually means an ancestor has overflow set to something other than visible. Check the ancestors before touching the panel's own rules.
Explain the containing-block rule that decides clipping, and show why the same card being both position: relative and overflow: hidden is what traps the panel.
Weigh the escapes out loud — fixed positioning versus a root overlay container versus removing the clip — and name the repositioning obligation each one creates in a scrolling layout.
Set the convention: overlays mount in one known place, overflow: hidden is never applied to generic layout containers as a default, and shared components document what they clip.
## The rule, stated precisely When a box has `overflow` other than `visible` — `hidden`, `auto`, `scroll`, or `clip` — its content is clipped to its **padding box**. The subtlety interviewers probe is *which* descendants that clipping covers. The answer is not "all of them": clipping applies to the box's in-flow content and to descendants whose **containing block** is the clipping box itself or a descendant of it. A positioned descendant whose containing block sits **above** the clipping box escapes the clip entirely. That gives a mechanical diagnostic. Take the dropdown panel and ask: what is its containing block? - `position: absolute` → the padding box of the nearest **positioned** ancestor (or one that establishes a containing block via transform, filter, or containment). - `position: fixed` → the viewport, unless some ancestor captures it via transform, filter, perspective, `will-change`, `contain`, or `container-type`. If that containing block is inside the clipping card, the panel is clipped. If it is outside, the panel is drawn over the card's edge, overflow declaration and all. ## Why the card version fails ```css .card { position: relative; /* becomes the panel's containing block */ overflow: hidden; /* and clips it */ } .card .dropdown { position: absolute; top: 100%; } ``` Both declarations are on the same element, so the panel's containing block is the very box doing the clipping. The panel is clipped at the card's padding edge. Note the classic accidental version: `overflow: hidden` was added to the card for an unrelated reason — clipping a rounded image corner, containing floats, or hiding a decorative element — and nobody connected it to the dropdown that appears months later. ## The escapes, and their costs **Switch the panel to `position: fixed`.** Its containing block becomes the viewport, which is above the card, so the card's overflow no longer applies. The cost is that fixed boxes do not move with the card: if the card scrolls, you must reposition the panel yourself, or use anchor positioning where supported. This also fails if any ancestor establishes a containing block for fixed boxes, which converts one bug into another. **Move the panel out of the clipped subtree.** Mount overlays in a container at the end of `<body>`. Nothing above it clips, nothing above it captures, and painting order becomes predictable. This is the general answer for a design system and the reason the portal pattern exists. Cost: the panel loses its natural position next to the trigger and must be positioned deliberately. **Remove the clip.** Often the right fix. `overflow: hidden` is a blunt instrument; if it was added to clip a decorative image, put the clip on the image wrapper instead. If it was added to contain floats, `display: flow-root` does that without clipping. If it was added because a child overflows horizontally, fixing the child's sizing is better than hiding the symptom. **Do not** reach for `z-index`. Clipping is a rasterization boundary applied before any stacking comparison: a clipped box at `z-index: 99999` is still clipped, it is simply clipped and on top. ## The scroll-container variant The same rule explains a second familiar bug. Inside a scrollable list (`overflow: auto`), an absolutely positioned row menu whose containing block is a row inside the list gets clipped at the list's edges. `position: fixed` escapes the clip — and now the menu does not scroll with its row, so it hangs in place while the list moves under it. Whichever escape you choose, you have traded clipping for a positioning obligation, and the honest answer in an interview names that trade rather than presenting one line of CSS as a cure. ## Sanity check in DevTools Select the panel, walk up its ancestors, and note the first ancestor with non-`visible` overflow and the first one that is its containing block. If the containing block is at or below the clipper, you have the bug; the order of those two ancestors is the whole story.
- If the card kept `overflow: hidden` but dropped `position: relative`, would the absolutely positioned dropdown still be clipped?Not by that card. Its containing block would resolve to some positioned ancestor further up — or the initial containing block — which is above the clipper, so the panel is not clipped. It is a real technique, but a fragile one: anyone who later adds `position: relative` to the card silently reintroduces the bug.
- What breaks when you solve this by switching the panel to `position: fixed`?The panel stops moving with its trigger. Scrolling the page or an inner container leaves it stranded, so you owe it repositioning logic or CSS anchor positioning. It also breaks again if any ancestor has a transform, filter, or containment, since that ancestor becomes the containing block for fixed boxes.
- Why is `display: flow-root` sometimes the better replacement for `overflow: hidden` on a container?When `overflow: hidden` was added only to make a container enclose floated children, `display: flow-root` achieves the same by establishing a block formatting context, without clipping anything. Overlays inside keep working, and nothing else about the box changes.
saying these in an interview costs you the question
- Says overflow: hidden clips every descendant unconditionally
- Tries a larger z-index to escape a clip
- Assumes position: fixed always escapes any ancestor
- Adds overflow: visible to the clipped panel itself
- Never checks which element is the containing block