cswork / libraryOpen practice workspace

Articles

CSS Stacking Context Interview Questions: Why z-index Does Not Fix Your Modal

Solve CSS stacking context interview questions: explain why z-index fails, trace ancestors, compare portals and dialog, and test modal focus and clipping.

If a modal stays behind a header after you give it z-index: 999999, inspect its ancestors. The modal may live inside a stacking context that is painted below the header's context. Increasing a descendant's number changes its order locally; it does not let the descendant escape its ancestor's position in the surrounding stack.

This CSS stacking context interview guide turns that rule into a debugging procedure. Official facts come from MDN, React, and the W3C's accessibility guidance. Candidate-report boundary: the practice links below are not evidence that every employer asks the same modal question. Our recommendations and examples are original preparation exercises built around those documented browser behaviors.

Your answer should distinguish stacking order, clipping, positioning, and interaction instead of treating every overlay problem as one CSS property.

What is a stacking context?

A stacking context groups elements whose painting order is resolved together. Once its descendants are ordered, the context participates as a unit in its parent context. MDN's stacking context guide describes this nested behavior.

The useful comparison is between peers at the same level. A modal's local index and an unrelated header's index may not be comparable directly. First find the ancestor contexts that compete with one another.

This differs from selector specificity. Specificity helps determine which declaration wins in the cascade. Stacking determines how boxes are painted after styling and layout rules apply. A declaration can win, appear in computed styles, and still fail to produce the visual order you expected.

It also differs from DOM depth. A deeply nested element does not automatically appear behind a shallow one. Its relevant ancestors, context-creating properties, stack levels, and painting rules determine the result.

Walk through a modal that cannot escape

Consider this original minimal example. The header and application area are siblings:

<header class="site-header">Account navigation</header>
<main class="workspace">
  <div class="overlay">Edit profile</div>
</main>
.site-header {
  position: relative;
  z-index: 2;
}
.workspace {
  position: relative;
  z-index: 1;
}
.overlay {
  position: fixed;
  inset: 0;
  z-index: 999999;
}

Both positioned siblings establish contexts because their z-index values are not auto. The workspace context sits at level 1; the header context sits at level 2. The overlay's large value is resolved inside the workspace. It remains below the header where their painted regions overlap.

The explanation should name those three relationships. Saying “z-index is broken” offers no testable mechanism. Saying “the modal is inside the lower sibling context” predicts why changing only its own number will not help.

A useful shorthand is a path such as workspace 1, overlay 999999 versus header 2. Compare the first differing ancestor level, not the largest number anywhere in either path. This is a reasoning aid for this example, not a complete numeric encoding of all CSS painting rules.

Now remove the workspace's non-auto index. If no other property establishes that context, the overlay can participate in a different surrounding context. That qualification matters: a remaining transform, opacity setting, or containment rule can preserve the isolation.

Which properties should you inspect on ancestors?

Start with the properties actually present. Common context creators include positioned elements with non-auto z-index, fixed or sticky positioning, opacity below 1, transforms, and isolation: isolate. Flex and grid items can also create contexts with non-auto z-index without needing the same positioning setup as an ordinary block.

MDN documents additional triggers, including certain containment, filtering, and will-change cases. Treat a memorized list as a starting point; check the current property semantics when you encounter an unfamiliar trigger.

In a debugging interview, inspect the modal and walk toward the root. Record each ancestor that creates a context and why. Do the same for the element covering it. The first relevant sibling-context comparison usually explains where the local number stopped being useful.

Do not remove every transform or isolation rule blindly. A transform may drive an animation; isolation may deliberately contain blending. The smallest correct repair preserves the original feature while moving the overlay into the intended layer ownership.

A design-system layer scale helps only after the architecture is correct. Named values such as dropdown, header, and modal cannot make descendants cross context boundaries. Document where those values are expected to compete.

Why is position fixed not a universal escape hatch?

Fixed positioning addresses positioning behavior; it does not dissolve ancestor stacking contexts. In the example above, the fixed overlay still belongs to the workspace's context.

Containing blocks are a separate concern. A transformed ancestor can establish the containing block for fixed descendants, changing what their offsets are relative to. MDN's position reference describes this departure from the usual viewport-based behavior.

That can create two symptoms at once: the overlay appears below a sibling and occupies the wrong area. Raising a layer value may address neither. Inspect dimensions and the containing block as well as the stacking tree.

For an original follow-up, add a transform to a scrolling panel and place the overlay inside it. Ask whether the overlay should cover the panel or the entire viewport. The correct owner depends on that product requirement. A panel-local menu and an application-wide blocking dialog should not automatically share the same mounting strategy.

How do you distinguish clipping from being covered?

An overlay may be painted above nearby content but clipped by an ancestor's overflow behavior. A higher stacking level does not generally undo that clipping relationship. MDN's overflow reference explains how overflow handling controls content outside a box.

Do not assume overflow: hidden itself is always a stacking-context trigger. Clipping and stacking are different mechanisms, even when the same ancestor has properties affecting both. Inspect the computed styles and geometry that explain the actual symptom.

Observed symptomFirst mechanism to investigate
Header covers modal despite huge indexCompeting ancestor stacking contexts
Menu ends exactly at a container edgeOverflow or another clipping mechanism
Fixed overlay moves with a panelFixed containing block established by an ancestor
Overlay is visible but clicks hit elsewhereHit testing, pointer behavior, or another covering element
Mouse works but keyboard escapes the modalFocus and modal interaction management

This table is a triage guide, not a diagnosis from appearance alone. For example, clipping can also come from a clip path. Confirm one hypothesis by changing one relevant declaration in a disposable reproduction, then check whether the predicted symptom changes.

When is a portal the right repair?

A portal renders content into a different DOM location. In React, createPortal changes the physical placement while preserving the React relationship: context remains available and events propagate according to the React tree.

An application overlay root near the document body can place a modal outside a clipping or low-stacking ancestor. That is useful when the component remains logically owned by a feature but must paint at application scope.

However, a portal is not automatically in the browser's top layer. Its destination can still have styling and context constraints. Inspect the destination and its ancestors rather than declaring that “portals ignore CSS.”

Moving DOM placement can also change inherited styles, CSS selectors, containing blocks, and relationships with form elements. React ownership does not preserve every DOM relationship. Test those consequences explicitly, especially when an existing component relies on ancestor selectors or container sizing.

Event propagation deserves a separate check. A click inside a portal may still reach a React ancestor handler. If that handler closes the overlay or selects a row, moving the DOM node alone may not fix the interaction. Decide where the event should be handled and avoid broad propagation changes without a reason.

What does native dialog showModal change?

Calling showModal() on a dialog places it in the top layer and displays its backdrop. Other content in the same document becomes inert while the modal is open. MDN documents this behavior in the showModal reference.

This is different from setting a very large author-defined z-index. It is also different from merely adding the open attribute or calling show(), which does not establish the same modal behavior.

<button id="edit">Edit profile</button>
<dialog id="profile" aria-labelledby="profile-title">
  <h2 id="profile-title">Edit profile</h2>
  <form method="dialog">
    <button>Close</button>
  </form>
</dialog>
<script>
  const dialog = document.querySelector("#profile");
  document.querySelector("#edit").addEventListener("click", () => {
    dialog.showModal();
  });
</script>

This small example demonstrates opening and closing, not a complete account-editing flow. Real forms need appropriate validation, submission handling, naming, and cancellation behavior. A dialog inside an iframe affects its own document's modal interaction; it does not take over the containing page's entire interface.

Prefer evaluating the native primitive before recreating all modal behavior yourself. If a custom implementation is necessary, state the requirements it must meet and the additional behavior your code will own.

Painting above the page is only part of a modal

The WAI-ARIA modal dialog pattern describes focus moving inside, a contained tab sequence, Escape handling, accessible naming, and sensible focus restoration. These requirements concern interaction and accessibility, not just visual depth.

Our recommended QA sequence is to open from a keyboard trigger, inspect initial focus, traverse forward and backward, close, and verify where focus returns. Repeat after resizing and with content taller than the viewport. The user must be able to reach controls without the page trapping content off-screen.

Do not use aria-modal="true" as a substitute for modal behavior. The attribute communicates semantics; it does not itself implement focus containment or block background interaction. A visually dim backdrop is also insufficient if keyboard users can still activate controls behind it.

For stacked overlays, decide which layer owns Escape and focus. Closing a child confirmation should not unexpectedly close its parent or send focus into an inert region. That requirement should influence the component architecture before you add another index token.

How should you present the debugging answer?

Start with the visible failure, then identify the relevant ancestor relationship. Show the smallest reproduction and explain what each proposed change would prove. Keep the repair proportional to the intended overlay scope.

Repair choiceWhy it may workWhat still needs verification
Remove an unnecessary context triggerLets descendants participate in the intended contextAnimation, blending, and neighboring order
Change the competing ancestor's layerReorders the whole feature contextOther descendants now move with it
Move to an overlay root or portalEscapes an unsuitable DOM ancestorInheritance, events, positioning, and focus
Use modal dialog behaviorUses top-layer and modal platform behaviorNaming, content, cancellation, and supported browsers

Avoid claiming one repair is always correct. Raising the entire workspace can place unrelated tooltips above navigation. Moving a small contextual menu to the body can introduce positioning work during scroll. The strongest answer connects the choice to the feature's intended boundaries.

End with evidence: the covering header no longer obscures the dialog, the clipping reproduction is resolved, keyboard behavior works, and the tested viewport range has no unreachable controls. If you have only reasoned through the example, say so rather than inventing a browser test result.

Practice with concrete UI requirements

These verified records provide adjacent frontend exercises. The stacking and interaction extensions are our recommendations; the table does not predict an employer's exact questions.

QuestionSuggested overlay-focused drill
Design a Reusable Dropdown Menu ComponentDefine layer ownership inside a scrolling container
Explain CSS specificity and selector precedenceSeparate winning declarations from painting order
Implement Hook and Modify UI InteractionsVerify modal state, opening, and closing
Build a Web Calculator That Matches a Reference AppTest geometry and responsive behavior under constraints
Implement a Reusable Dropdown ComponentExplain positioning, dismissal, and keyboard requirements

Choose a Frontend Engineer practice question, draw its stacking-context ancestors, and explain one repair without changing a random large number. Then verify that the interface remains usable, not merely visible.

Sources and Further Reading

Research checked September 14, 2026. Verify behavior in the browsers and component environment used by your project.