In the browser rendering pipeline, how do the CSS declarations `display: none` and `visibility: hidden` differ in whether the element gets a box, whether it occupies space, and what work the browser redoes when you switch each one back on?
answer
- one removes the box, one hides it
- does the reserved space stay?
- render tree membership versus paint skip
- only one of the two is inherited
- re-show cost: relayout versus repaint
basics
~20 sdisplay: none keeps the element out of the render tree entirely: no box, no space, nothing painted. visibility: hidden still generates a box that occupies its layout space and only skips painting, so revealing it costs a repaint rather than a relayout.
solid answer
~50 sThey act at different stages of the pipeline. `display: none` means the element generates no box at all, so it and its whole subtree never enter the render tree: they reserve no space, following content closes the gap, nothing is painted or hit-tested, and the subtree is dropped from the accessibility tree. `visibility: hidden` still produces a box that goes through layout normally — it reserves its space and contributes to its parent's height — and the browser simply skips painting it. That asymmetry drives the cost: flipping `visibility` back to `visible` repaints a region, while flipping `display` back has to build boxes and lay out the revealed subtree, which can be expensive for a large one. `visibility` is also inherited, so a descendant can declare `visibility: visible` and show through a hidden ancestor; nothing escapes `display: none`, because a box-less ancestor leaves descendants with no box to generate.
code
html · 8 lines<div style="visibility: hidden">
invisible
<span style="visibility: visible">but this span still paints</span>
</div>
<div style="display: none">
<span style="display: block">nothing in here generates a box</span>
</div>go deeper
Be ready to say plainly that display: none takes the element out of the flow so the space disappears, while visibility: hidden leaves the space behind. Naming that one difference confidently is most of the answer at this level.
Explain the mechanism, not just the symptom: display decides whether a box enters the render tree, visibility only suppresses paint. Mention that visibility is inherited and that revealing a display: none subtree costs layout while revealing a hidden one costs a repaint.
Show you think about consequences in a real UI: hit testing, accessibility-tree exposure, layout jump when a section reappears, and why measuring a display: none element gives zeros. Say when you would reserve space deliberately to avoid content shifting.
Own the convention. Decide how the design system hides things — a hidden utility class, the hidden attribute, content-visibility for heavy panels — so the choice is not made ad hoc per component, and be explicit about the accessibility contract each option carries.
## Two different stages To put pixels on screen the browser resolves a computed style for every element, combines the DOM with those styles into a **render tree** of boxes, runs **layout** to give each box a size and position, **paints** the boxes, and composites the result. `display` is consulted when the render tree is built — it decides whether a box exists. `visibility` is consulted at paint time — the box exists and has already been laid out, and the browser just does not draw it. That one-sentence difference explains everything else. ## display: none The element and every descendant generate no boxes: - **No space.** Siblings move up to fill the gap; the parent's auto height shrinks. - **No paint, no hit testing.** Pointer events cannot reach it because there is no box to hit. - **Removed from the accessibility tree**, so screen readers do not announce it, and it is skipped by find-in-page. - **Geometry queries report zeros.** With no box there is nothing to measure, which is why measuring a hidden element before showing it fails. - **Descendants cannot opt out.** `display` is not inherited, but a child of a box-less element still has no parent box to live in, so declaring `display: block` on it changes nothing. - Revealing it is the expensive direction: the browser must construct boxes for the subtree and lay them out, and the surrounding content moves. One useful side effect: a `background-image` on an element that generates no box is generally not fetched, while an `<img>` element's `src` is fetched regardless of whether it renders. The HTML `hidden` attribute is not a separate mechanism — the user-agent stylesheet simply maps it to `display: none`, which is why a page rule with `display: block` silently overrides it. ## visibility: hidden The box is created, laid out, sized and positioned exactly as if it were visible. It reserves its space, contributes to the parent's height and to scrollable overflow, and then paint skips it. It is not hit-testable and it is hidden from the accessibility tree, so it is invisible to both the mouse and a screen reader while remaining a real participant in geometry. Because `visibility` is an inherited property, a descendant can set `visibility: visible` and paint through a hidden ancestor — a genuine capability, not a bug: ```css .panel { visibility: hidden; } .panel .badge { visibility: visible; } ``` On table rows and columns there is a third value, `visibility: collapse`, which removes the row or column much as `display: none` would; elsewhere it behaves like `hidden`. ## opacity: 0 is not the same thing `opacity: 0` keeps the box, keeps the space, **and keeps hit testing** — the element still receives clicks and is still exposed to assistive technology. An invisible element that swallows clicks is almost always an `opacity: 0` element that should have been `visibility: hidden`. ## content-visibility sits between them `content-visibility: hidden` skips the element's contents the way `display: none` does, but keeps the element's own box and preserves the rendering state of the skipped subtree, so showing it again is much cheaper than rebuilding it. Its contents are not reachable by find-in-page while hidden. ## Choosing Hide something without letting the page jump — a spinner slot, a placeholder that must hold its size — with `visibility: hidden`. Remove something from the flow, such as a collapsed section or an inactive tab panel, with `display: none`. For a large subtree you will show and hide repeatedly and want fast re-show, reach for `content-visibility: hidden`. And if the goal is animating something away, remember that `display` and `visibility` were not designed as animation targets the way `opacity` is.
- Can a descendant of a `visibility: hidden` element make itself visible again, and does the same trick work under `display: none`?Yes for visibility: it is an inherited property, so a descendant declaring `visibility: visible` paints normally even though its ancestor does not. Under `display: none` it is impossible — the ancestor generates no box, so no descendant box exists to display, whatever its own `display` value says.
- Where does `content-visibility: hidden` fit between the two?It behaves like `display: none` for the contents — they are not laid out, not painted, and not findable — but the element itself still generates a box and the browser preserves the skipped subtree's rendering state. Re-showing is therefore much cheaper than rebuilding a `display: none` subtree, which makes it a good fit for large panels toggled repeatedly.
- Why is `opacity: 0` a poor substitute for `visibility: hidden` on an interactive element?An `opacity: 0` element keeps its box, its space, its hit-testing and its accessibility-tree entry. It still receives clicks and focus and is still announced by screen readers, so an invisible overlay can swallow input from the content beneath it. `visibility: hidden` drops both hit testing and accessibility exposure while keeping the same reserved space.
saying these in an interview costs you the question
- Says visibility: hidden removes the element from layout
- Treats opacity: 0 and visibility: hidden as interchangeable
- Claims a display: none element can still be measured
- Thinks toggling visibility triggers a full document reflow
- Believes a child can escape display: none with display: block