Toggling content visibility
Hiding something has two sides: whether eyes can see it, and whether screen readers and the Tab key can reach it. Each hiding method sets those two sides differently. Pick the wrong one and you get ghosts: things you cannot see but can still Tab to, or things you see but a screen reader skips.
In one minute
- Gone for everyone (menu closed, tab not selected):
hiddenordisplay: none. - Visible but silent (decorative icon):
aria-hidden="true", and never on something focusable. - Silent to eyes but spoken to screen readers (extra label): a "visually hidden" CSS class, often called
sr-only. - Show and hide a section: use
<details>and<summary>. They give you role, name, state, and keyboard for free.
Think of a shop window and a back room. display: none removes the item from the shop entirely. opacity: 0 is like painting the item invisible while it still stands in the aisle: you cannot see it, but you bump into it.
aria-hidden is a sign that says "ignore this" to blind customers while sighted ones still see it.
The accessibility tree is the list of things the browser tells assistive technology about. Hiding methods decide who gets removed from that list.
1. The hiding matrix: try every method
The same link is hidden six different ways below. For each, check four things: can you see it, does it keep its space, does a screen reader read it, and does Tab reach it. The effects are real, not simulated.
Lab: hide the link
Step 1. Pick a method. Press Tab from the "Before" line and note where focus lands.
Before
After
Focus is on: nothing yet
| Question | Answer |
|---|
visibility: hidden keeps the empty space but removes the control from tab order and screen readers. opacity: 0 hides nothing but the pixels.
The "visually hidden" class clips the element to one pixel so sighted users see nothing while screen readers still read it. Never use
left: -9999px as a hiding method for focusable things; focus can go off-screen.
2. The aria-hidden trap
aria-hidden="true" removes an element from the accessibility tree but does not remove it from the tab order.
A button inside it still takes focus, and the screen reader says nothing, or only "button". This breaks the rule that focusable things must have a name and role (WCAG 4.1.2).
Typical cause: an off-screen drawer or a "closed" modal marked aria-hidden while its links still work.
Lab: a ghost button
Step 2. Tab through the area. Switch fixes and see which stay reachable.
Focus is on: nothing yet
Good uses of aria-hidden="true": decorative icons and emoji ( like this one), duplicated visual text, and the page behind an open modal
(though inert is better there). It must never be on or around anything focusable.
3. Native disclosure with details and summary
A disclosure is a control that shows or hides one section. The native <details> element does it with no JavaScript.
The <summary> is the clickable label. The browser supplies the role ("disclosure triangle" or "button"), the state (expanded or collapsed),
and keyboard support (Enter and Space). Compare three builds of the same "Shipping times" toggle.
Lab: three ways to build "Shipping times"
Step 3. Pick a build, then try it with the keyboard: Tab to it, press Enter or Space.
Keep the summary text steady (do not change it between "Show" and "Hide"); the state is announced for you.
Content inside a closed <details> is not in the accessibility tree and not in the tab order. Browser find-in-page can still open it in some browsers.
For your own hidden panels, hidden="until-found" gives the same find-in-page behaviour.
4. A group where one opens at a time
Give several <details> the same name attribute and the browser closes the others when one opens, like an accordion.
Support is good in current browsers; where it is missing, they simply behave as independent disclosures, which is still accessible.
Lab: exclusive group
Step 4. Open the first question, then the second. Does the first close?
Can I edit an order?
Until it is packed. After that, contact support.
Do you ship internationally?
Yes, to the countries listed at checkout.
Exclusive groups are a choice, not a rule. Some users like to compare two answers side by side, so use them only when it helps.
5. Which method for which job?
Lab: pick the job
Step 5. Choose a situation to see the best tool and why.
Common mistakes
| You see | Why it is a problem | Fix |
|---|---|---|
opacity: 0 to close a menu | Links stay in the tab order and the accessibility tree. | hidden or display: none after the fade. |
aria-hidden around a button | Focus lands on a control nobody announces. | Remove aria-hidden or use inert. |
display: none on a label you need | Screen readers lose the name too. | Use the visually hidden class. |
Clickable div toggle | No keyboard, no state. | <details> or a button with aria-expanded. |
| Summary text swaps "Show" / "Hide" | Announced as a different control each time. | Keep one label; the state speaks. |
| Animation ignores reduced motion | Slide-ins can cause discomfort. | Wrap in prefers-reduced-motion. |
- In lab 3, build "details / summary", then open it with Enter and with Space.
- In lab 1, pick opacity: 0 and press Tab from "Before". Where does focus go, and what do you see?
- Turn on a screen reader or use your browser's accessibility panel in devtools and compare what
display: noneandaria-hiddenremove.
Remember
hidden/display: none: gone for everyone.visibility: hidden: gone, but keeps space.opacity: 0hides nothing but pixels.aria-hidden="true": only for decoration, never around focusable things; useinertto block a region.- Prefer
<details>for show/hide, with a steady label.