Problem Solved

CSS · display none vs visibility

Home › Courses › CSS › Problems › display: none vs visibility: hidden
🎨 Concept · 2 min read

display: none vs visibility: hidden.
Space or no space.

Both hide an element. Both remove it from screen readers. The difference is what happens to the layout: display: none removes the element entirely — it takes no space, other content shifts up. visibility: hidden hides the element but preserves its space — the box is invisible, but the layout doesn't move. This page covers when to use each, why visibility: hidden is animatable, and the third option — opacity: 0 — that most developers don't know about.

Short answer: display: none removes the element from flow and from the accessibility tree. visibility: hidden hides the element but keeps its space and still removes it from the accessibility tree. Use display: none when the element should be gone. Use visibility: hidden when you want the layout preserved. Use opacity: 0 when you want the element invisible but still accessible (screen readers announce it). Use a visually-hidden class when you want it hidden visually but available to assistive tech.

✅ The four hiding methods

Each one hides differently.

display: none

No space, no render, no accessibility. The layout reflows as if the element doesn't exist.

visibility: hidden

Space preserved, invisible, not in accessibility tree. Layout doesn't shift.

opacity: 0

Invisible but still in flow and accessibility tree. Screen readers announce it. Clickable unless pointer-events: none.

Visually-hidden class

Moved off-screen (1px), still accessible. For "skip to content" links and labels without visible text.

✅ Before and after

The same list, two hiding methods.

display-none.css
.list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.item--hidden {
  display: none;
}
HTML:
┌─────────────────┐
│ Item 1          │
├─────────────────┤
│ Item 2 (hidden) │ ← display: none
├─────────────────┤
│ Item 3          │
└─────────────────┘

Visual result:
┌─────────────────┐
│ Item 1          │
├─────────────────┤ ← no gap, no space
│ Item 3          │
└─────────────────┘

✅ Item 2 is gone.
✅ Item 3 has moved up to fill the space.
✅ The layout looks like a 2-item list.
✅ Screen readers see 2 items.
✅ No animation possible — the element is removed.
visibility-hidden.css
.list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.item--hidden {
  visibility: hidden;
}
HTML:
┌─────────────────┐
│ Item 1          │
├─────────────────┤
│ Item 2 (hidden) │ ← visibility: hidden
├─────────────────┤
│ Item 3          │
└─────────────────┘

Visual result:
┌─────────────────┐
│ Item 1          │
├─────────────────┤
│                 │ ← invisible, but space kept
├─────────────────┤
│ Item 3          │
└─────────────────┘

✅ Item 2 is invisible.
✅ The space is preserved — Item 3 doesn't move.
✅ Screen readers still skip Item 2.
✅ Animatable — visibility participates in transitions.
✅ The layout looks like a 3-item list with a blank row.

✅ The visually-hidden pattern

For content that should be announced, not shown.

visually-hidden.css
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Use: */
<button>
  <span class="sr-only">Search</span>
  <svg aria-hidden="true">...</svg>
</button>
When to use sr-only:

✅ Icon-only buttons — label is visually hidden
✅ "Skip to content" links — appear on focus
✅ Form labels hidden by design but required
✅ Section headings that are visually redundant

When NOT to use:

❌ display: none — removes from accessibility tree
❌ visibility: hidden — removes from accessibility tree
❌ opacity: 0 — screen readers announce, but the element still takes space (confusing for sighted users)

sr-only is the ONLY correct way to hide visually while keeping the content available to screen readers.

⚠️ Also add :focus-visible styles to make skip links appear on keyboard focus.

Don't use display: none to hide labels or icons that need to be announced. Both display: none and visibility: hidden remove the element from the accessibility tree — screen readers skip them entirely. This is correct for "this element is not present right now" but wrong for "this element is visually hidden but still available to assistive tech." Icon-only buttons, "skip to content" links, and form labels without visible text all need the visually-hidden pattern, not display: none. If you're using display: none on a label, the input has no accessible name and screen reader users can't identify it.

⚠️ Common mistakes

The hiding mistakes that cost real time.

1. Using visibility: hidden when you want the space gone

The element still occupies layout. Fix: display: none.

2. Using display: none when you want to animate

Not animatable. Fix: visibility: hidden or animate opacity + display together in JS.

3. display: none on a label

Input loses its accessible name. Fix: sr-only class.

4. visibility: hidden on a focusable element

Still focusable via keyboard — invisible but user sees focus ring on nothing. Fix: use display: none, or remove from tab order.

5. opacity: 0 and expecting it to hide from screen readers

Still announced. Fix: display: none or visibility: hidden for hiding from all.

6. Using opacity: 0 without pointer-events: none

Invisible element still intercepts clicks. Fix: add pointer-events: none.

7. Thinking visibility: hidden is accessible

It's not — screen readers skip it. Fix: sr-only.

8. Hiding content for SEO

Google treats hidden content as less important, or as spam if manipulative. Fix: hide only legitimate UI state.

❓ Frequently asked

What is the difference between display: none and visibility: hidden?

display: none removes the element entirely — it takes no space, doesn't render, and isn't announced by screen readers. visibility: hidden hides the element but keeps its space in the layout. Screen readers also skip visibility: hidden elements. Neither is focusable. Use display: none when you want the element and its space gone. Use visibility: hidden when you want the space preserved.

Does visibility: hidden hide from screen readers?

Yes. Both display: none and visibility: hidden remove the element from the accessibility tree. Screen readers skip both. If you want an element hidden visually but still announced to screen readers, use neither — use a visually-hidden class (position: absolute, width: 1px, height: 1px, overflow: hidden, clip) instead.

Which one can be animated?

visibility: hidden. It's a discrete property but participates in transitions — you can transition from visible to hidden with a delay, or use visibility to control when an animation starts and stops. display: none is not animatable — the element is removed from the render tree, so there's nothing to animate.

Why would I use visibility: hidden instead of opacity: 0?

Two differences. (1) opacity: 0 keeps the element in the accessibility tree — screen readers announce it. visibility: hidden removes it. (2) opacity can be animated smoothly; visibility is discrete. For pure visual hiding with no layout change, opacity: 0 is more flexible. For hiding from assistive tech, visibility: hidden is correct.

Does display: none affect SEO?

Indirectly. Google has warned that hidden content may be weighted less. In practice, Google is smart enough to understand legitimate uses (tab panels, accordions, mobile menus), and most sites use display: none for these without penalty. But if you stuff keywords in a hidden div, expect it to be treated as spam.

What is a visually-hidden class?

A CSS pattern that hides an element visually but keeps it available to screen readers. Common implementation: position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;. Use it for 'skip to content' links, form labels when the design doesn't show them, and headings that are visually redundant but structurally important.

🔗 Related

← All CSS problems
Problem solved · CSS