
If you’ve ever needed to hide an element on a page, you’ve probably reached for one of these two CSS properties. At first glance they seem identical – the element disappears. But under the hood, they solve the problem in fundamentally different ways, and choosing the wrong one can cause subtle, frustrating layout bugs.
The key distinction: display: none removes the element entirely from the document flow, as if it never existed. visibility: hidden hides the element visually but preserves its space in the layout.
display: none – gone without a trace
When you apply display: none to an element, the browser completely removes it from the document’s rendering tree. This means the element is not just hidden visually – it no longer participates in layout calculations at all. The space it would have occupied disappears instantly, so surrounding elements shift to fill the gap as though the element were never present. Margins, padding, and dimensions associated with that element are ignored, and any child elements are also excluded from rendering. Because it is effectively taken out of the flow, the page reflows to accommodate the change, which can affect positioning, alignment, and even performance if large sections of the layout are toggled on and off.
.hidden-box {
display: none; /* element is fully removed from flow */
}
display: none – interactive demo
Notice how when Box B is hidden, Box C immediately shifts upward to occupy the freed space. Because the hidden element no longer contributes to layout, the browser recalculates positions and closes the gap as if Box B were never there. There’s no reserved placeholder, no invisible footprint left behind – just a seamless reflow of the surrounding content. This behaviour makes display: none especially useful for conditionally rendered interface components such as modals, dropdown menus, tab panels, or expandable sections, where you want elements to appear and disappear without leaving awkward spacing or disrupting the overall structure of the page.
display: none are also removed from the accessibility tree and cannot be focused by keyboard navigation.
visibility: hidden – the invisible ghost
visibility: hidden hides the element’s visual output while keeping the element itself in the layout. Unlike display: none, the browser still calculates its dimensions, margins, and position, so the space it would normally occupy remains reserved. The element becomes invisible rather than removed, almost like a transparent placeholder sitting in the same spot. Because it continues to participate in the document flow, surrounding elements do not shift to fill the gap, which can be useful when you need to temporarily conceal content without affecting alignment or causing layout changes.
.ghost-box {
visibility: hidden; /* invisible, but space is preserved */
}
/* Children can override it! */
.ghost-box .child {
visibility: visible; /* this child will show through */
}
visibility: hidden – ghost demo
See it? Box B disappears from view, yet the space it occupied is still preserved – Box C remains anchored in the same position. Because the element is only made invisible rather than removed, the browser keeps its dimensions in the layout, preventing any reflow or sudden movement of surrounding content. This results in a stable, non-jumping interface, which can be particularly helpful when toggling temporary states, preparing content for animations, or hiding elements that are expected to reappear quickly without disrupting the user’s sense of spatial continuity.
display: none, you can override visibility: hidden on child elements by setting visibility: visible on them. Child elements can “show through” a hidden parent.
Side-by-side comparison
Here’s a quick reference for the key differences:
| Property | display: none | visibility: hidden |
|---|---|---|
| Preserves layout space | ✗ No | ✓ Yes |
| Visible on screen | ✗ No | ✗ No |
| Accessible to screen readers | ✗ No | ✗ No (usually) |
| Receives pointer events | ✗ No | ✗ No |
| Children can override | ✗ No | ✓ Yes |
| Animatable with CSS transitions | ✗ Not directly | ✓ Yes |
| Triggers layout reflow | ✓ Yes (on toggle) | ✗ No |
| Common use case | Modals, tabs, conditional UI | Stable layouts, fade-in animations |
Which one should you use?
Use display: none when you want the element to genuinely not take up space – conditional rendering, toggleable panels, menu items, and anything where reflowing the layout is acceptable or desired.
Use visibility: hidden when you need the space to be held in place – for example, when you’re fading an element in and out and don’t want the surrounding content to jump around. It’s also handy when building skeleton loaders or placeholder layouts.
For smooth fade animations, a common pattern combines both:
.fade-element {
visibility: visible;
opacity: 1;
transition: opacity 0.3s ease, visibility 0.3s;
}
.fade-element.hidden {
visibility: hidden;
opacity: 0;
/* opacity animates smoothly; visibility snaps at the end */
}
This lets the opacity transition animate, while visibility: hidden ensures the element becomes fully non-interactive at the end of the transition (unlike opacity: 0 alone, which is invisible but still clickable).
Dig deeper
Now that you understand the difference, it’s worth exploring the full picture. The display property goes far beyond none – it’s the foundation of Flexbox and Grid. And the visibility property has a third value, collapse, which behaves specially inside tables and subgrids. If you’re reaching for visibility: hidden just to stop clicks, you might also want pointer-events. And if smooth fade-outs are your goal, opacity paired with visibility is the go-to pattern.
