CSS Hide Elements: display vs visibility vs opacity
There are three basic ways to hide an element with CSS: display, visibility and opacity.
The short answer: use display: none when you want the space to collapse, visibility: hidden when you want to keep the space, and opacity: 0 when you want a fade.
All three end with the element invisible, so they get mixed up. What differs is the effect on layout, whether you can animate the change, and whether keyboard users and screen readers can still reach the element. This article lays out those differences in a table, with the code for each case and four live demos.
What is the difference between display, visibility and opacity?
The short answer: they differ on two decisive points, whether the space remains and whether the element can still be operated. Only the visual result is shared.
| Behaviour | display: none |
visibility: hidden |
opacity: 0 |
|---|---|---|---|
| Space when hidden | Collapses | Remains | Remains |
transition |
Possible with extra properties (below) | Flips between 0 and 1 | Animates smoothly |
| Still clickable? | No | No | Yes |
| Reachable with Tab? | No | No | Yes |
| Read by screen readers? | No | No | Yes |
| Can a child be shown again? | No | Yes (visibility: visible) |
No |
The row to watch is opacity: 0. It only makes the element transparent, so the element stays on the page. That is why a menu you thought was closed can still be selected with Tab or read aloud by a screen reader.
Use display: none when you want the space to collapse
The short answer: the element leaves layout entirely and everything after it moves up.
.box {
display: block;
}
.box.is-hidden {
display: none;
}
Can you animate display: none?
The short answer: yes, now. Combine transition-behavior: allow-discrete with @starting-style.
For years transition had no effect on display at all. That changed with these two features.
.box {
display: block;
opacity: 1;
transition: opacity 0.3s, display 0.3s;
transition-behavior: allow-discrete;
}
.box.is-hidden {
display: none;
opacity: 0;
}
/* Starting value used when the element appears */
@starting-style {
.box {
opacity: 0;
}
}
Both features reached Baseline in August 2024. They work in Chrome and Edge 117 and later, Safari 17.5 and later, and Firefox 129 and later. Browsers without support simply skip the animation; showing and hiding still works correctly, so you can ship this without a fallback.
When is display: none the right choice?
The short answer: whenever collapsed content should take up no room at all.
Accordions and the contents of a hamburger menu are the standard case. If the space stayed, a closed accordion would leave a large gap underneath it.
Use visibility: hidden when you want to keep the space
The short answer: the element’s box is still reserved, so nothing around it moves.
.box {
visibility: visible;
}
.box.is-hidden {
visibility: hidden;
}
Only visibility lets you show a child again
The short answer: visibility is inherited, so you can keep a parent hidden and set visibility: visible on one child.
.parent {
visibility: hidden;
}
.parent .child {
visibility: visible; /* shown even though the parent is hidden */
}
Neither display nor opacity can do this.
When is visibility: hidden the right choice?
The short answer: for table rows or grid cells where removing the space would break the layout.
It also drops the element out of keyboard navigation and the accessibility tree, so it is a safe way to hide a closed menu.
Use opacity: 0 when you want a fade
The short answer: it is the only one of the three that has intermediate states, so it is the one that fades.
.box {
opacity: 1;
transition: opacity 0.3s ease;
}
.box.is-hidden {
opacity: 0;
}
opacity: 0 on its own is still clickable
The short answer: a transparent element still sits on the page and can steal clicks from the button underneath it.
This is the trap. A modal backdrop faded to opacity: 0 will keep swallowing clicks unless you do something about it.
Add pointer-events: none, or pair it with visibility. Choose the second if you also want the element out of keyboard navigation and screen readers.
.box {
opacity: 1;
visibility: visible;
transition: opacity 0.3s ease, visibility 0.3s;
}
.box.is-hidden {
opacity: 0;
visibility: hidden;
}
visibility has no intermediate value, but listing it in transition delays the switch until the opacity animation finishes. That stops the element from vanishing halfway through the fade.
Four demos you can try
The short answer: combining “does the space collapse” with “is there an animation” gives four patterns.
1. Space collapses, with animation
See the Pen
rNadzmZ by りん@webコーダー (@rinblog0408)
on CodePen.
The space closes up and the change is animated. Large blocks that disappear without warning are jarring, so the bigger the element, the more an animation helps.
2. Space collapses, no animation
See the Pen
MWYVvLz by りん@webコーダー (@rinblog0408)
on CodePen.
The space closes up and the switch is instant. For small elements and form validation messages, this is enough.
3. Space remains, with animation
See the Pen
xxbWXEw by りん@webコーダー (@rinblog0408)
on CodePen.
The space stays and the element fades. Pick this when the surrounding layout must not move by even a pixel. It also suits swapping content on hover.
4. Space remains, no animation
See the Pen
oNgqGWY by りん@webコーダー (@rinblog0408)
on CodePen.
The space stays and the switch is instant. Useful where a shift in position would make things harder to read, such as swapping cell contents in a table.
So which one should you use?
The short answer: decide whether the space should collapse first, and the choice narrows to one.
| What you want | What to use |
|---|---|
| Collapse when closed (accordion, menu) | display: none |
| Keep the space when closed (table, grid) | visibility: hidden |
| Fade out (modal, tooltip) | opacity: 0 + visibility: hidden |
| Hide visually but keep it announced | opacity: 0 alone |
The last row has narrow uses. An element that is invisible yet still read aloud is usually a bug, so pair it with visibility unless you specifically want that.
You can do all of this with JavaScript or jQuery instead. Keeping it in CSS means the state change is only ever adding or removing a class, which I find easier to maintain. If you do need to react to a class change in script, how to detect class changes and trigger events in JavaScript covers that side. For menus specifically, 5 simple ways to implement a dropdown menu with jQuery uses these same show and hide patterns.