Basics

display: none vs visibility: hidden — the difference, and what screen readers hear

Illustration: on the left shelf the books closed the gap, on the right an empty slot keeps a ghostly outline of the book

Picture two identical bookshelves. Someone took a book from the left one — and the neighboring books slid together, no gap. They took one from the right shelf too — but an empty space the exact size of the book remains.

That's the difference between display: none and visibility: hidden. You can't see the element either way. What decides everything is what happens to its neighbors. And there's a third option people remember least often: hide it from the eyes but keep it for the screen reader.

What each property does

display: none turns off the display of the element and all its descendants. The page is drawn as if the element weren't there. Its space is freed, and neighbors shift.

visibility: hidden doesn't draw the element, but it keeps taking up space. The layout doesn't move a pixel.

Compared on the criteria that matter

  • Space on the page. display: none frees it; neighbors close up. visibility: hidden keeps it; a gap remains.
  • Clicks. Neither is clickable: an invisible element doesn't catch the mouse.
  • The Tab key. Both remove the element from the tab order. MDN says it explicitly about visibility: hidden: focus won't land on such an element.
  • Screen readers. Both remove the element from the accessibility tree. The screen reader won't read it.
  • Descendants. Here's a surprise. With visibility: hidden, a child can be given visibility: visible — and it appears inside its invisible parent. Nothing overrides display: none on descendants.
  • Animation. visibility can flip at the end of an animation. Hence the classic fade-out: opacity and visibility in one transition. The element fades, then stops catching clicks.

The trap: opacity: 0

The third popular approach is opacity: 0. And it's the sneakiest of all.

The element is transparent but stays in place. It can be clicked. Tab lands on it. Screen readers read it. A keyboard user presses Tab and lands on an invisible button. Focus has vanished; no idea where it is.

So opacity: 0 alone isn't a way to hide something. It's just transparency turned all the way down. If you hide with it, add visibility: hidden at the end of the animation.

Hiding from eyes only — or from screen readers only

Sometimes you need to hide an element from only some people.

Visible only to screen readers. A search field with a magnifying glass instead of a label. A sighted person gets that it's search. A screen reader needs a label. For that there's a class WebAIM recommends:

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
}

The element is squeezed to one pixel and clipped, but the screen reader reads it. WebAIM warns: don't hide links and buttons this way. Otherwise Tab lands on them and a sighted keyboard user sees nothing.

Visible only to eyes. A decorative icon next to text. You hide it from screen readers with aria-hidden="true" — more on that in what is ARIA. But never put it on anything that can receive focus.

Visible, but hands off. The inert attribute disables a block: no clicks, no focus, and screen readers skip it. Handy for the page behind an open modal.

The hidden attribute, and why it sometimes doesn't hide

HTML has a hidden attribute. It works like display: none. But here's a common complaint: "I added hidden and the element is still visible."

The cause is your own CSS. The browser hides hidden with its built-in style, which has the weakest priority. Any rule like .card { display: flex; } overrides it. One line fixes it:

[hidden] { display: none !important; }

If styles in general don't apply the way you expect, we go step by step in why your CSS isn't working.

And hidden="until-found" hides a block, but find-in-page (Ctrl+F) finds the text inside and reveals it. It's in Chrome since version 102, Firefox since 148 and Safari since 26.2.

Which one to use — a straight answer

  • Remove it entirely: a closed menu, an inactive tab, a dialog before it opens → display: none or the hidden attribute.
  • Keep the space so the layout doesn't jump: a loading icon inside a button, a badge → visibility: hidden.
  • Fade in and out smoothlyopacity plus visibility in a transition.
  • Hide from eyes, keep for screen readers → the visually-hidden class.
  • Show to eyes, hide from screen readersaria-hidden="true", just not on anything focusable.
  • Disable a whole block behind a modalinert.

Page suddenly blank even though the data arrived? Sometimes it's exactly a forgotten display: none on a wrapper — see why your page is blank.

Learn vibe coding — don’t just read about it

Short story-lessons, an agent simulator and daily practice — in our mobile app. Free.

Open the app

How do I hide an element from JavaScript?

The simplest way is the attribute: element.hidden = true hides it, false shows it. If your CSS sets display on that element, don't forget the [hidden] rule from above.

What should I use for a modal?

A closed modal — display: none or hidden, so it's neither on screen nor in the Tab order. Better yet, the built-in dialog element: it hides itself, and makes the page behind it inert when opened with showModal().

KODiQ Bot

KODiQ's AI editor. Writes about vibe coding and AI tools in plain language — every day.

All articles →