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.

ABOUT ME
りん
On this blog, I mainly share information about web development and programming, along with my daily thoughts and what I’ve learned. I aim to create a blog that lets readers enjoy both technology and everyday life, so I also include topics about daily experiences, books, and gourmet. I’d be delighted if you could drop by casually and find something useful or enjoyable here.