Difference between display: none and visibility: hidden
display:none removes the element entirely — no box, no space taken, not in layout, not announced to screen readers, can't be focused. visibility:hidden hides it visually but it STILL occupies its space in layout and affects siblings. opacity:0 is a third option (visible to a11y/events). Toggling display triggers reflow.
Both hide an element, but they differ in whether the element still occupies space and participates in the page.
display: none
The element is removed from the render tree entirely, as if it doesn't exist:
- Takes up no space — the layout closes up around it; siblings move in.
- Not in the layout at all.
- Not announced to screen readers, not focusable, doesn't receive events.
- Its children are also gone from rendering.
- Toggling it on/off triggers a reflow (layout recalculation).
visibility: hidden
The element is invisible but still there:
- Still occupies its space — the layout reserves its box; siblings stay put. You get an empty gap.
- Still in the layout — just not painted.
- Not focusable, doesn't receive mouse events.
- By default also hidden from screen readers (though a child can override with
visibility: visible). - Toggling it triggers a repaint, not a reflow — cheaper.
opacity: 0 — the third option
Worth mentioning: opacity: 0 makes the element fully transparent but it still occupies space AND is still interactive — it can be clicked, focused, and is in the accessibility tree. It's also animatable (and composite-only — cheap). visibility is not smoothly animatable; display definitely isn't.
Quick comparison
display:none | visibility:hidden | opacity:0 | |
|---|---|---|---|
| Takes up space | ❌ no | ✅ yes | ✅ yes |
| In layout | ❌ | ✅ | ✅ |
| Clickable / focusable | ❌ | ❌ | ✅ |
| Screen reader | hidden | hidden (overridable) | announced |
| Toggle cost | reflow | repaint | composite (cheapest) |
| Animatable | ❌ | ❌ (binary) | ✅ |
Which to use
display: none— when the element should be completely gone (conditional content, responsive hide). Most common.visibility: hidden— when you want to hide it but preserve the layout space (avoid content jumping) — e.g. a placeholder slot.opacity: 0— for fade animations, or when you need the element still interactive/measurable.
⚠️ For truly hiding from everyone including assistive tech, display:none or visibility:hidden are correct. For "visually hidden but read by screen readers" (skip links), use the .sr-only clip pattern — not these.
The framing
"display: none removes the element from the render tree entirely — no box, no space, not in layout, invisible to screen readers and events, and toggling it causes a reflow. visibility: hidden hides it visually but it still occupies its space and stays in the layout — toggling is just a repaint, cheaper. And opacity: 0 is a third: invisible but still takes space and stays interactive and accessible, plus it's the only one that animates smoothly. So: display:none to fully remove, visibility:hidden to hide-but-keep-the-gap, opacity:0 for fades."
Follow-up questions
- •Which is cheaper to toggle and why?
- •When would you specifically want visibility:hidden over display:none?
- •How does opacity:0 differ from both for accessibility and events?
- •How do you visually hide something but keep it readable by screen readers?
Common mistakes
- •Thinking visibility:hidden frees up the space — it doesn't.
- •Using opacity:0 to 'hide' something that's then still clickable by accident.
- •Using display:none/visibility:hidden for content that should still be screen-reader accessible.
- •Not knowing display toggles cause reflow.
Performance considerations
- •display:none toggling triggers reflow (expensive, cascades); visibility:hidden triggers only repaint; opacity changes can be composite-only (GPU, cheapest) — which is why fades animate opacity.
Edge cases
- •A child setting visibility:visible inside a visibility:hidden parent.
- •display:none breaking CSS transitions (can't animate to/from it).
- •opacity:0 elements still capturing clicks, blocking elements behind them.
- •Lazy content inside display:none — images may not load until shown.
Real-world examples
- •Responsive layouts using display:none to hide nav items per breakpoint.
- •visibility:hidden to reserve space for a tooltip; opacity transitions for modal fade-in.