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

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: nonefrees it; neighbors close up.visibility: hiddenkeeps 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 givenvisibility: visible— and it appears inside its invisible parent. Nothing overridesdisplay: noneon descendants. - Animation.
visibilitycan flip at the end of an animation. Hence the classic fade-out:opacityandvisibilityin onetransition. 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: noneor thehiddenattribute. - Keep the space so the layout doesn't jump: a loading icon inside a button, a badge →
visibility: hidden. - Fade in and out smoothly →
opacityplusvisibilityin atransition. - Hide from eyes, keep for screen readers → the
visually-hiddenclass. - Show to eyes, hide from screen readers →
aria-hidden="true", just not on anything focusable. - Disable a whole block behind a modal →
inert.
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.
Short story-lessons, an agent simulator and daily practice — in our mobile app. Free.
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().





