The CSS visibility property

The visibility property controls whether an element is painted (drawn) on the page. It’s like telling the browser: “keep this element in the layout… but don’t show it (and don’t show its children either).”

The key detail: visibility affects rendering, not layout. So when something becomes hidden, it usually still keeps its space. (That difference becomes super important when we compare it to display later.)

What visibility does and does not do

  • It hides or shows the element visually.
  • It usually keeps the element’s layout space. The element still “occupies” its slot.
  • It hides the element and its descendants. If a parent is visibility: hidden, children are not visible either.
  • It can be inherited. (More on that in the values section.)
  • It does not animate smoothly by itself. It’s a discrete property, like a light switch.
.demo-box {
  visibility: visible;
}
.demo-box {
  visibility: hidden;
}
*,
::before,
::after {
  box-sizing: border-box;
}

.stage {
  display: grid;
  gap: 12px;
  padding: 16px;
  max-width: 720px;
}

.row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 12px;
  padding: 12px;
  border: 2px dashed #bbb;
  border-radius: 12px;
  background: #fafafa;
}

.demo-box {
  width: 160px;
  height: 64px;
  border: 3px solid #111;
  border-radius: 12px;
  background: #f2f2f2;
  display: grid;
  place-items: center;
  font-family: ui-monospace, SFMono-Regular, monospace;
}

.note {
  font-family: system-ui, -apple-system, Segoe UI;
  font-size: 14px;
  line-height: 1.4;
  color: #333;
}
This row keeps its layout. Toggle snippets and watch the dashed container stay the same size.
Box

CSS visibility options and values

The commonly used values are: visible, hidden, and collapse.

And since this is CSS, you also have the “global-ish” values: inherit, initial, unset, and revert.

visibility: visible

Default behavior: the element is painted normally.

visibility: hidden

The element becomes invisible, but it typically still takes up space in the layout. Think “ghost furniture”: the chair is still in the room, you just can’t see it.

visibility: collapse

This one is special and historically tied to table layout. In tables, collapse can remove a row or column’s visual box and reclaim space. Outside tables, browser behavior can be inconsistent, and it often behaves like hidden.

visibility:
.demo-box {
  visibility: visible;
}
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
  display: grid;
  gap: 12px;
  padding: 16px;
  max-width: 720px;
}

.info {
  font-family: system-ui, -apple-system, Segoe UI;
  font-size: 14px;
  line-height: 1.45;
  color: #333;
}

.panel {
  display: grid;
  gap: 12px;
  padding: 12px;
  border: 2px solid #111;
  border-radius: 14px;
  background: #fff;
}

.demo-box {
  width: 180px;
  height: 64px;
  border: 3px solid #111;
  border-radius: 12px;
  background: #f2f2f2;
  display: grid;
  place-items: center;
  font-family: ui-monospace, SFMono-Regular, monospace;
}

.layout-hint {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 12px;
  align-items: center;
  padding: 10px;
  border: 2px dashed #bbb;
  border-radius: 12px;
  background: #fafafa;
}
Use the radios. On regular elements, collapse often acts like hidden. Tables are where collapse gets interesting.
This dashed container shows layout space.
Peekaboo

Global values: inherit, initial, unset, revert

  • inherit: takes the computed value from the parent.
  • initial: sets to the CSS initial value (for visibility, that’s visible).
  • unset: acts like inherit if the property is inherited (and visibility is inherited), otherwise behaves like initial.
  • revert: resets to the value from a previous cascade origin (often feels like “go back to browser / user-agent defaults”).

CSS visibility none vs hidden

Tiny but important correction: there is no visibility: none value. If you see “visibility none” in a blog post or in someone’s notes, they almost always mean one of these:

  • visibility: hidden (keeps layout space)
  • display: none (removes the element from layout)
  • opacity: 0 (invisible but still takes space and can still be clickable unless you prevent it)

So in CSS land, your “visibility none” mental model should map to visibility: hidden.

CSS visibility vs display

This is the big showdown. These two can both “hide” things, but they hide them in very different ways.

The one-sentence difference

  • visibility: hidden: the element is not painted, but it keeps its layout space.
  • display: none: the element is removed from the layout entirely (as if it does not exist for layout).
.demo-box {
  visibility: visible;
}
.demo-box {
  visibility: hidden;
}
.demo-box {
  display: none;
}
.demo-box {
  opacity: 0;
}
*,
::before,
::after {
  box-sizing: border-box;
}

.grid {
  display: grid;
  gap: 12px;
  padding: 16px;
  max-width: 760px;
}

.card {
  border: 2px solid #111;
  border-radius: 14px;
  background: #fff;
  padding: 12px;
  display: grid;
  gap: 10px;
}

.title {
  font-family: system-ui, -apple-system, Segoe UI;
  font-weight: 700;
  font-size: 16px;
}

.row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px;
  border: 2px dashed #bbb;
  border-radius: 12px;
  background: #fafafa;
}

.demo-box {
  width: 120px;
  height: 56px;
  border: 3px solid #111;
  border-radius: 12px;
  background: #f2f2f2;
  display: grid;
  place-items: center;
  font-family: ui-monospace, SFMono-Regular, monospace;
}

.pill {
  display: inline-block;
  padding: 6px 10px;
  border-radius: 999px;
  border: 2px solid #111;
  background: #fff;
  font-family: ui-monospace, SFMono-Regular, monospace;
  font-size: 13px;
}
Toggle snippets and watch the dashed row:
Left content
Box
Right content

Interactions, focus, and accessibility

When you hide things, you’re not just playing with pixels. You’re also deciding what users can click, tab to, and interact with.

  • display: none: the element is removed from layout and is not rendered. It also won’t be focusable via keyboard navigation because it’s effectively not there.
  • visibility: hidden: the element is not visible. It also won’t be clickable because there’s nothing painted to interact with, and it won’t be focusable.
  • opacity: 0: the element is invisible, but it still exists visually in the layout and can still receive pointer events unless you add pointer-events: none. This is a classic “why is my invisible element blocking clicks?” moment.

CSS visibility: collapse

visibility: collapse is the “table nerd” of the visibility family. It was designed mainly for table rows and columns.

collapse in tables

In a table, collapsing a row can reclaim space in a way that visibility: hidden usually doesn’t. (Hidden keeps the space; collapsed is meant to behave more like “remove the row/column box.”)

tr:nth-child(3) {
  visibility: hidden;
}
tr:nth-child(3) {
  visibility: collapse;
}
*,
::before,
::after {
  box-sizing: border-box;
}

.shell {
  padding: 16px;
  max-width: 760px;
  display: grid;
  gap: 12px;
}

p {
  margin: 0;
  font-family: system-ui, -apple-system, Segoe UI;
  font-size: 14px;
  line-height: 1.45;
  color: #333;
}

table {
  border-collapse: collapse;
  width: 100%;
  border: 2px solid #111;
  border-radius: 12px;
  overflow: hidden;
  background: #fff;
}

th,
td {
  border: 1px solid #222;
  padding: 10px 12px;
  text-align: left;
  font-family: ui-monospace, SFMono-Regular, monospace;
  font-size: 14px;
}

thead th {
  background: #f2f2f2;
  font-weight: 700;
}

Switch snippets. In tables, collapse is the value that may actually collapse row/column space. hidden tends to keep the row's slot.

Item Status Notes
Alpha OK Visible row
Beta OK Visible row
Gamma Hidden? This is the row we toggle
Delta OK Visible row

collapse outside tables

Outside tables, visibility: collapse is not consistently special across all element types and browsers. In many real-world cases, it behaves like visibility: hidden.

Practical advice: use collapse for tables only. For everything else, use hidden or display: none depending on whether you want to keep layout space.

CSS visibility transition

Here’s the slightly annoying truth: visibility doesn’t transition smoothly. It’s a discrete property: it jumps from one state to another.

But we can still use transitions with it in a clever way: we transition opacity smoothly, and then toggle visibility at the end. This gives you a nice fade-out where the element becomes non-visible and non-interactive.

The classic fade pattern: opacity + visibility

This pattern is everywhere in dropdowns, tooltips, modals, and “read more” sections:

  • Animate opacity for smooth fading.
  • Toggle visibility so the element is actually hidden when not active.
  • Optionally toggle pointer-events so invisible things don’t block clicks.
.stage:hover .popover {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity 250ms ease, visibility 0s linear 0s;
}

.popover {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 250ms ease, visibility 0s linear 250ms;
}
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
  padding: 16px;
  max-width: 760px;
  font-family: system-ui, -apple-system, Segoe UI;
  color: #111;
}

.stage {
  position: relative;
  width: min(520px, 100%);
  padding: 20px;
  border: 2px solid #111;
  border-radius: 16px;
  background: #fff;
  display: grid;
  gap: 12px;
}

.button {
  display: inline-block;
  width: fit-content;
  padding: 10px 14px;
  border: 2px solid #111;
  border-radius: 12px;
  background: #f2f2f2;
  font-weight: 700;
}

.popover {
  position: absolute;
  top: 64px;
  left: 20px;
  width: min(320px, 90%);
  padding: 12px;
  border: 2px solid #111;
  border-radius: 14px;
  background: #fafafa;
  box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
}

.popover p {
  margin: 0;
  font-size: 14px;
  line-height: 1.45;
  color: #222;
}
Hover this area

I fade in on hover with opacity, and use visibility so I stop existing for interaction when hidden.

The important part is the delayed transition for visibility when closing: we let opacity fade to 0 first, then we flip visibility to hidden.

Learn more about transitions in the CSS Transition Interactive Tutorial.

CSS visibility animation

Just like transitions, visibility doesn’t animate smoothly. But you can still use it inside animations as a “switch” at a key moment.

Animation pattern: fade and pop

Here’s a common pattern: animate opacity and transform, while toggling visibility at the right times.

.toast {
  animation: pop-in 600ms ease forwards;
}

@keyframes pop-in {
0% {
opacity: 0;
transform: translateY(12px) scale(0.98);
visibility: hidden;
}
1% {
visibility: visible;
}
100% {
opacity: 1;
transform: translateY(0px) scale(1);
visibility: visible;
}
} 
.toast {
  animation: pop-out 600ms ease forwards;
}

@keyframes pop-out {
  0% {
    opacity: 1;
    transform: translateY(0px) scale(1);
    visibility: visible;
  }
  99% {
    visibility: visible;
  }
  100% {
    opacity: 0;
    transform: translateY(12px) scale(0.98);
    visibility: hidden;
  }
}
*,
::before,
::after {
  box-sizing: border-box;
}

.scene {
  padding: 16px;
  max-width: 760px;
  display: grid;
  gap: 12px;
  font-family: system-ui, -apple-system, Segoe UI;
}

.hint {
  font-size: 14px;
  line-height: 1.45;
  color: #333;
  margin: 0;
}

.toast {
  width: min(520px, 100%);
  padding: 14px 14px;
  border: 2px solid #111;
  border-radius: 14px;
  background: #fff;
  box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
}

.toast strong {
  display: block;
  margin-bottom: 6px;
}

Switch snippets: the first animates in, the second animates out. visibility is used as a discrete switch inside the keyframes.

Toast message This is a pop-in / pop-out animation using opacity and transform.

Learn more about animations in the CSS Animation Interactive Tutorial.

Common visibility gotchas and debugging

“My hidden element still takes up space”

That’s expected with visibility: hidden. If you need the space to collapse, you want display: none (or a layout change like setting height/margins to 0, depending on the UI).

“My invisible element is blocking clicks”

That’s typically opacity: 0 without pointer-events: none. If you’re using opacity to hide things, add pointer-events: none in the hidden state.

“Why not just always use display: none?”

  • Use display: none when you truly want the element gone from layout.
  • Use visibility: hidden when you want to reserve space (like hiding an icon but keeping alignment).
  • Use opacity (with pointer-events and often visibility) when you want smooth transitions.

A quick mental cheat sheet

  • visibility: hidden = invisible, but keeps its “seat” in the layout.
  • display: none = removed from layout, no seat, no show.
  • opacity: 0 = transparent, still there, can still block clicks unless you prevent it.
  • visibility: collapse = mainly useful for table rows/columns.

CSS Visibility Conclusion

The visibility property is simple, but it’s a surprisingly powerful layout-and-interaction tool once you know the rules: hidden keeps space, display: none removes space, and animations usually need opacity (with visibility as the “final switch”).