Hidden until found
Use hidden="until-found" for collapsible content so that browser find-in-page, assistive tech, and search engines can still reach the text and auto-expand it.
What it is
hidden="until-found" is a value of the global hidden attribute defined in the HTML Standard. An element marked this way renders as if hidden — it takes up no visible space — but the browser’s find-in-page (Ctrl/Cmd+F), the fragment-directive scroll-to-text, and scrollIntoView() still walk through it. When a match is found inside, the browser fires a beforematch event on the element, removes the hidden attribute, and scrolls the match into view.
The browser’s user-agent stylesheet applies content-visibility: hidden to elements in the until-found state, and the find-in-page algorithm has a specific exception for that state — when a match would land inside the subtree, it walks the element, removes the hidden attribute, and scrolls into view. Applying content-visibility: hidden directly in author CSS does not get you the same behaviour. Per the CSS Containment specification, contents in that state are skipped from rendering and are not visible to screen readers, find-in-page, or other tools. The reachability is a property of the HTML attribute, not the CSS property.
Why it matters
display: noneremoves content from the accessibility tree and from find-in-page entirely. Accordion or tab patterns that hide panels withdisplay: noneare invisible to a user who knows the exact phrase they want.- Find-in-page is a primary accessibility tool. Keyboard users, screen-reader users, users with cognitive disabilities, and anyone skimming a long document rely on it to locate content directly.
- Search engines and AI crawlers vary in how they treat content hidden with
display: none.hidden="until-found"keeps the text in the DOM and reachable, which is the honest signal: this is real content, just collapsed by default.
How to implement
Mark each collapsed panel and listen for beforematch so your widget state stays in sync:
<button aria-expanded="false" aria-controls="panel-1">Shipping</button>
<div id="panel-1" hidden="until-found">
We ship worldwide within 48 hours…
</div>
const panel = document.getElementById('panel-1');
panel.addEventListener('beforematch', () => {
const button = document.querySelector('[aria-controls="panel-1"]');
button.setAttribute('aria-expanded', 'true');
// remove any matching collapsed class on the button or panel
});
Prefer <details>/<summary> where you can. For the everyday “click a heading to expand a panel” pattern, the native disclosure element gives you focus management, keyboard handling, and find-in-page reachability with zero JavaScript. Reach for hidden="until-found" when you need a custom widget that <details> cannot model — a search-driven FAQ, a complex tab strip, an off-screen mega-menu that must still be findable.
Common mistakes
- Using
display: noneon accordion panels and then wondering why users cannot find the content they remember reading. Switch the closed state tohidden="until-found". - Substituting
content-visibility: hiddenin author CSS as a “CSS equivalent” of the attribute. It is not — author-appliedcontent-visibility: hiddenhides content from find-in-page and screen readers in exactly the way the attribute is meant to avoid. - Using
hidden="until-found"for content that should be permanently hidden — error messages, off-screen utility nodes, suppressed admin tools. Use plainhiddenordisplay: noneinstead. - Forgetting to update
aria-expandedand the visual chevron state in thebeforematchhandler. The panel opens but the button still claims it is collapsed. - Treating it as a layout primitive. The element still participates in DOM order and document outline — it is hidden, not removed.
Verification
- Open the page in Chrome or Edge. Press Ctrl/Cmd+F and search for a phrase that lives inside a collapsed panel. The browser should auto-scroll and reveal it.
- Repeat with a panel that uses
display: none. The search should fail, confirming the regression you are avoiding. - Tab through the widget with a screen reader (VoiceOver, NVDA, JAWS) and confirm the panel announces correctly once expanded.
- Inspect the element in DevTools after a match — the
hiddenattribute should be gone, thebeforematchlistener should have fired, andaria-expandedshould readtrue.