Prompt and Applicable Context
A product page has a sticky header and a card containing a menu button. The menu is absolutely positioned and given an extremely large stacking value. When it opens near the top of the page, the header still paints over it. When it extends beyond the card, its lower portion disappears. A teammate proposes adding another zero to the stacking value.
Explain why that proposal cannot fix both symptoms. Diagnose the example below, distinguish paint order from clipping and positioning geometry, and choose a durable repair for three cases: a menu that can stay inside its component, an overlay that must escape the component, and UI whose semantics call for a modal dialog or popover.
<header class="site-header">Navigation</header>
<main class="page">
<section class="card">
<button type="button">Open menu</button>
<div class="menu">Menu items</div>
</section>
</main>.site-header {
position: sticky;
top: 0;
z-index: 4;
}
.page {
position: relative;
z-index: 1;
}
.card {
position: relative;
overflow: hidden;
transform: translateZ(0);
}
.menu {
position: absolute;
inset-block-start: 100%;
z-index: 999999;
}Assume current evergreen browsers. The answer should preserve deliberate scroll, containment, and accessibility behavior rather than deleting CSS until the screenshot looks correct.
What the Interviewer Evaluates
The first signal is whether the candidate knows the comparison boundary. A stacking context is painted as one atomic unit in its parent context. Descendants can reorder themselves inside that unit, but no descendant stacking value can promote the entire unit above a sibling of its parent. Here, the menu's large value wins inside .page; .page as a whole still participates at level 1, below the header at level 4.
The second signal is classification. “Behind the header” is a paint-order problem. “Cut off at the card edge” is a clipping problem caused by overflow: hidden. A third class often looks similar: an ancestor with a transform establishes a containing block for absolutely and fixed-positioned descendants, so coordinates or fixed behavior may be relative to an unexpected ancestor. A single stacking value does not repair clipping or geometry.
The third signal is knowledge of context creators without turning the answer into a memorized list. Common causes include a positioned element with a non-auto stacking value, fixed or sticky positioning, opacity below one, transforms, filters, perspective, clipping or masking, blending, isolation: isolate, layout or paint containment, container queries, and flex or grid children with a non-auto stacking value. The useful skill is finding the first relevant boundary on each ancestor chain.
The fourth signal is repair judgment. Removing an accidental context, changing the correct ancestor's level, reparenting an overlay, and using the browser top layer solve different contracts. A portal changes DOM ancestry and can escape component clipping, but it does not automatically enter the top layer or add modal, focus, dismissal, or accessible-name behavior.
The final signal is verification. DevTools inspection should be backed by an observable overlap test, scrolling, resize and zoom checks, keyboard use, and coexistence with other overlays. A fix is incomplete if it wins one screenshot but breaks a scroll container, anchors to the wrong coordinate system, or lets an ordinary menu cover a real modal.
Questions to Clarify Before Answering
- Which pixels are wrong? Is the menu painted beneath another element, clipped at a boundary,
positioned at the wrong coordinates, or covered by a top-layer element? These require different fixes.
- Which ancestors create contexts? Inspect both the menu's chain and the covering element's chain.
The decisive comparison is where the chains first become siblings, not necessarily on the two visible elements.
- Is clipping intentional? A card may clip rounded media, and a scroll container may need overflow
behavior. Removing it globally can fix the menu while breaking layout and scrolling.
- What should the overlay be anchored to? Reparenting to a shared overlay root means its coordinates
must be measured relative to that root and updated on scroll, resize, zoom, and layout changes.
- Does the UI need top-layer semantics? A modal dialog should block outside interaction and manage
focus. A lightweight menu may only need an overlay root. A suitable popover may use the platform's popover behavior and top-layer placement.
- Which overlays may coexist? Define whether menus, tooltips, sticky navigation, drawers, and modals
can overlap. A small set of named layers is easier to reason about than arbitrary six-digit values.
- What browser and component contracts apply? Current platform primitives may be preferred; an older
webview or an established design-system primitive may change the implementation choice.
30-Second Answer Framework
“I would classify the failure before changing values. The menu's 999999 is compared only inside its own stacking context. Its ancestor .page is level 1, so the whole subtree remains below the sticky header at level 4. Separately, the card's hidden overflow clips the menu, and its transform can change the menu's containing block. I would inspect the ancestor chains and the actual topmost element at the overlap point. If the menu can stay local, I would remove an accidental context or raise the correct ancestor and handle clipping locally. If it must escape, I would render it in a shared overlay root and recompute anchor geometry. If the interaction is a modal or suitable popover, I would use the browser top layer and implement the required focus and dismissal contract. Then I would test scroll, resize, zoom, keyboard interaction, and coexistence with other overlays.”
Step-by-Step Deep Dive
Start by separating the four mechanisms. They can fail together, but they are not interchangeable:
PAINT ORDER
Which painted box is above another?
Controlled by the stacking-context tree and paint order inside each context.
CLIPPING
Which pixels are allowed to appear?
Controlled by overflow, clip paths, masks, and paint containment.
GEOMETRY
Which box supplies the coordinate system?
A transform can establish the containing block for absolute and fixed descendants.
TOP LAYER
Is the element outside normal document stacking?
Modal dialogs and shown popovers can be placed in the browser-managed top layer.Build the context tree for the example instead of comparing the two visible numbers. The sticky header creates a context at level 4. The positioned .page creates a sibling context at level 1. The card's transform creates another context within .page. The menu's large value only orders it inside that subtree. Conceptually, the comparison is header: 4 versus page: 1; it is never header: 4 versus menu: 999999.
Next, inspect clipping independently. The menu is a descendant of .card, so pixels outside the card's padding box are clipped by hidden overflow. Stacking changes paint order; they do not expand a clipping region. Raising .page, removing the transform, or changing the menu's value can put it above the header and still leave its lower portion cut off.
Then inspect the containing block. The menu is absolute, so the positioned card already supplies its coordinates. The transform is redundant for this particular menu, but it becomes important if a future implementation switches the menu to position: fixed: a transformed ancestor can make that “fixed” element behave relative to the ancestor instead of the viewport. This is why changing absolute to fixed is not a reliable escape hatch.
Use browser inspection to confirm the hypothesis rather than guessing. Inspect computed styles on every ancestor until the first context creator and clipping ancestor are found. Compare the context chains of the menu and the header. At a coordinate where they overlap, query what hit testing considers topmost:
function inspectOverlap(x, y) {
const stack = document.elementsFromPoint(x, y)
return stack.map((element) => ({
element,
position: getComputedStyle(element).position,
zIndex: getComputedStyle(element).zIndex,
overflow: getComputedStyle(element).overflow,
transform: getComputedStyle(element).transform,
}))
}elementsFromPoint() is convenient for seeing the hit-test stack; elementFromPoint() returns only the topmost eligible element. Hit testing is supporting evidence, not a complete stacking-context debugger: elements with pointer-events: none may be skipped, and clipping or off-screen geometry still needs layout inspection.
Choose the smallest repair that matches the ownership boundary.
For a local menu, remove an accidental context if it has no required visual or containment effect. If the page genuinely needs its context, compare and change the page-level token because that is the sibling being ordered against the header. If card clipping is only for media, place that clipping on a nested media wrapper rather than the whole interactive card.
:root {
--layer-content: 0;
--layer-sticky: 20;
--layer-overlay: 40;
}
.page {
position: relative;
z-index: var(--layer-content);
}
.site-header {
z-index: var(--layer-sticky);
}
.card-media {
overflow: hidden;
border-radius: 1rem;
}
.menu {
z-index: var(--layer-overlay);
}This only works when the intended contract allows the page subtree to remain below the header and the menu does not need to cross that boundary. If the menu must cover the header, the layer decision belongs on a common ancestor or the overlay must move to a shared root.
For a reusable overlay that must escape local contexts and clipping, render it under a shared overlay root near the document root. Preserve the trigger as its logical owner, keep accessible relationships, and calculate the panel from the trigger's rectangle. Recalculate when scrolling, resizing, zooming, or layout changes alter either rectangle. Collision handling must account for viewport edges and writing direction. Reparenting without geometry ownership trades a clipping bug for a drifting-menu bug.
For a real modal dialog, prefer the native dialog element opened modally or an established accessible primitive that provides the same contract. For a suitable non-modal interaction, the popover API can put shown content in the top layer. Top-layer entries render above ordinary document contexts; later entries are above earlier ones, and each has its own root context. Ancestor overflow, opacity, masks, and transforms do not pull a top-layer element back into the ancestor's document context.
Top layer is not a universal “stronger z-index.” It is an ordered browser-managed layer with semantic entry and exit mechanisms. A modal still needs a useful name, deliberate initial focus, contained interaction, dismissal policy, and focus restoration. A menu still needs correct menu or disclosure semantics and keyboard behavior. Choosing the layer never replaces choosing the interaction model.
Validate the fix as a matrix:
- Open near every viewport edge and inside nested scroll containers.
- Scroll the page and the nearest container; verify the panel stays anchored or closes by policy.
- Resize, zoom to 200%, and test narrow and wide layouts.
- Navigate by keyboard; verify order, visible focus, Escape, and focus return match the component.
- Open it with sticky navigation, a drawer, a tooltip, and a real modal already present.
- Check hit testing at obscured pixels and confirm no invisible overlay intercepts unrelated controls.
- Test long translated labels and both left-to-right and right-to-left layouts when supported.
- Confirm that deliberate overflow, containment, compositing, and scroll behavior still work.
High-Quality Sample Answer
“The large value is being compared at the wrong level. The menu is inside the .page stacking context, which participates at level 1. The header is a sibling context at level 4. The browser paints the page subtree atomically below the header, so a descendant value of 999999 cannot escape and compete with the header.
There is also a separate clipping failure. The card's hidden overflow removes pixels outside its boundary; z-index changes order but cannot enlarge that clip. The card's transform creates another context and can establish a containing block for absolute and fixed descendants, so switching to fixed positioning may still use an unexpected coordinate system.
I would inspect both ancestor chains in DevTools, identify the first context and clipping creators, and check the hit-test stack at an overlap coordinate. For a local menu, I would remove only an accidental context, move clipping to a media wrapper, or adjust the named layer on the common ancestor that is actually being compared. I would not keep increasing a leaf value.
If the menu is contractually allowed to cross the component and header boundaries, I would render it in a shared overlay root. That means the overlay system owns measurement, collision handling, and updates on scroll, resize, zoom, and layout change. A portal helps with ancestry and clipping, but it does not create top-layer or accessibility behavior.
If the interaction is a modal dialog, I would use the native modal dialog behavior or a tested accessible primitive. If it is a suitable popover, I would consider the platform popover behavior. Both can use the browser top layer, but I would still implement the correct accessible name, focus, keyboard, dismissal, and restoration contract. Finally, I would verify scroll containers, viewport edges, 200% zoom, keyboard use, translated layouts, hit testing, and coexistence with other overlays.”
Common Mistakes
- Increasing a descendant value indefinitely → it remains trapped in a lower parent context →
Compare the first sibling contexts and change the owner of that layer decision.
- Treating every hidden pixel as a stacking bug → overflow or paint containment still clips it →
Inspect clipping ancestors separately from paint order.
- Changing absolute positioning to fixed → a transformed ancestor may still define the containing
block → Trace the coordinate system before choosing a position mode.
- Removing every transform or overflow declaration → rounded media, scrolling, containment, or
rendering behavior breaks → **Remove only accidental triggers or narrow them to the element that needs them.**
- Portaling every overlay to the body → anchoring, collision, ownership, and focus problems replace
the original bug → Use a shared overlay system with explicit geometry and interaction contracts.
- Assuming a portal is the top layer → ordinary document contexts can still cover it → **Use a
platform top-layer primitive when its semantics fit.**
- Using a modal dialog for every floating panel → focus and outside interaction become unnecessarily
blocked → Choose modal, popover, menu, tooltip, or inline disclosure from behavior first.
- Creating arbitrary global values → components enter an escalating layering contest → **Use a small
set of named layers and keep local values local.**
- Verifying only one screenshot → scroll, zoom, keyboard, or another overlay reintroduces the failure
→ Test the overlap and interaction matrix.
Follow-Up Questions and Responses
Does a larger z-index always place an element above a smaller one?
Only when the values participate in the same relevant stacking context comparison. Descendants are ordered within their context, and that entire context is then ordered in its parent. Compare context chains, not isolated computed values.
Which CSS properties commonly create a stacking context?
The root, fixed or sticky positioning, positioned elements with a non-auto stacking value, opacity below one, transforms, filters, perspective, clipping or masking, blending, isolation, selected containment and container-query properties, and flex or grid children with a non-auto stacking value are common causes. The exact list evolves, so inspection should identify the computed trigger instead of relying only on memory.
Why can an element be above its sibling yet still look cut off?
Paint order decides which allowed pixels are painted last. A clipping region decides which pixels are allowed to exist. Winning paint order cannot recover pixels removed by an ancestor's overflow, clip path, mask, or paint containment.
Will position: fixed always escape an overflow-hidden ancestor?
No. Fixed positioning changes normal containing-block behavior, but transforms and related properties on ancestors can establish the containing block. Other clipping mechanisms can still apply. Trace geometry and clipping instead of using fixed positioning as a universal escape.
Does rendering through a portal solve the whole problem?
It can escape local DOM ancestry, contexts, and clipping if the target root is outside them. It also requires new ownership of measurement, scroll updates, collision handling, event relationships, focus, and dismissal. It remains in ordinary document stacking unless the primitive itself enters the top layer.
How is the top layer different from a very high global layer token?
Top-layer elements are painted in a browser-managed ordered layer above ordinary document content and create root stacking contexts. Entry order determines order within that layer. A high global token still participates in the document's context tree and can remain trapped by an ancestor.
Should a team ban all local z-index values?
No. Local values are useful for ordering parts inside one component, such as a badge above an image. Use a small named scale only at shared boundaries such as sticky navigation, reusable overlays, and drawers. The boundary, not the number's size, determines whether a value is global.
How would you prevent this class of regression?
Document the few shared layer owners, centralize reusable overlay placement, and add interaction stories that combine sticky content, scroll containers, menus, and modals. Verify edge placement, scroll, zoom, keyboard behavior, and another active overlay; a static snapshot alone cannot prove the contract.