Up front: these are my notes from practicing
hidden="until-found". My tests were run in Chromium, and support in other browsers changes quickly. If I've gotten something wrong, corrections welcome.
Intro
In my modern CSS & HTML cheat sheet, hidden="until-found" was another box I hadn't ticked. I mentioned it in my last post on popover's auto, manual and hint, saying it "falls back more safely," so this time I wanted to check that properly.
First, the problem it solves. Collapsed content is everywhere: FAQs, accordions, "show more" sections in long articles. When people look for something, they naturally press Ctrl+F (โ+F on Mac), and the browser tells them there's no match.
But the content is right there on the page, just collapsed. For the user, content that search can't find might as well not exist.
Here's what your browser supports; the demos below rely on it:
Your browser's support
hidden="until-found"Detecting (requires JavaScript)
Try it first: four ways to collapse content. Which can search find?
As before, let's play first. The four blocks below are collapsed four different ways, and each is labeled with a keyword to search for (each keyword only appears inside its own block). Press Ctrl+F and try them one by one:
Live demo: press Ctrl+F (โ+F on Mac) and search for each block's keyword
hidden
<div hidden>Keyword to search:
State: collapsed
height: 0
height: 0; overflow: hiddenKeyword to search:
This one is hidden with a height of 0, and it contains a penguin.
State: collapsed
hidden="until-found"
<div hidden="until-found">Keyword to search:
This one is hidden with until-found, and it contains a giraffe.
State: collapsed
until-found + a common reset
[hidden] { display: none !important }Keyword to search:
This one uses until-found, but a reset overrides it, and it contains a koala.
State: collapsed
This is an interactive demo. Try it on the web page.
What you should see:
| Collapsed with | Can search find it? | What happens |
|---|---|---|
hidden | No | โ |
height: 0; overflow: hidden | Yes | The browser reports a match, but nothing is visible, which confuses people even more |
hidden="until-found" | Yes | Expands automatically and scrolls to the text |
until-found + a common reset | No | The reset undoes it; more on that below |
If you see something different, it's probably a browser support difference, and I'd love to hear about it.
How does it work?
The markup is simple. Change the value of hidden to until-found:
<div hidden="until-found">
Collapsed, but findable
</div>
Here's the mechanism:
- The browser doesn't hide it with
display: nonebut withcontent-visibility: hidden. The content isn't shown, but its text still takes part in in-page search. - When search matches, or a URL's
#idpoints at an element inside, the browser first fires abeforematchevent, then removes thehiddenattribute itself, then scrolls there. - While collapsed, the content isn't in the accessibility tree, and buttons inside can't be reached with Tab. I tested this in Chromium; it behaves like a plain
hidden, so screen readers won't read out content nobody can see.
In other words, it only handles "expand when found." Expanding and collapsing with your own button is still up to you.
An FAQ demo: events, deep links and aria-expanded
Below is an FAQ built with until-found. Each answer is hidden="until-found" while collapsed, and the event log shows when beforematch fires:
Live demo: FAQ
You can request a return within 7 days of delivery, as long as the item is in its original packaging.
Yes. Enter your company tax ID in the invoice field at checkout.
Orders over $30 ship free; smaller orders have a flat $3 handling fee.
Deep link: jump straight to a paragraph inside answer 3
Event log (newest first)
- No events yet. Try Ctrl+F for a word in an answer, or use the link above.
This is an interactive demo. Try it on the web page.
Things to try:
- Press Ctrl+F and search for "ship free." This article contains the phrase too, so press Enter once more to jump to the next match, and the third question expands on its own.
- Press Collapse all, then use the deep link in the demo. It expands and scrolls there too, which means you can share a link to a single FAQ answer and people land right on it.
- Uncheck "Sync aria-expanded" and search again.
Step 3 is the accessibility issue I most want to flag: the browser only removes hidden; it doesn't update your button's aria-expanded. The content is open, but the button still says false, so screen reader users hear the wrong state.
So always listen for beforematch and sync the state back:
panel.addEventListener("beforematch", () => {
button.setAttribute("aria-expanded", "true");
});
Trap 1: el.hidden = !el.hidden quietly turns it back into a plain hidden
I found this one while testing the demos. A common way to toggle visibility looks like this:
button.addEventListener("click", () => {
panel.hidden = !panel.hidden;
});
The problem is that while collapsed, panel.hidden reads as the string "until-found", not true. So here's what happens:
- It starts as
hidden="until-found";!panel.hiddenisfalse, so it expands. Fine. - Click again to collapse:
!panel.hiddenistrue, and the attribute becomeshidden="", a plain hidden.
After one open-and-close, the content can never be found by search again, and nothing on screen looks any different. A safer approach is to drive it from your own state and write until-found back explicitly when collapsing:
let open = false;
button.addEventListener("click", () => {
open = !open;
button.setAttribute("aria-expanded", String(open));
if (open) {
panel.removeAttribute("hidden");
} else {
panel.setAttribute("hidden", "until-found");
}
});
Trap 2: a [hidden] rule in your CSS reset breaks it
Plenty of CSS resets and projects include this line to make sure hidden always hides:
[hidden] {
display: none !important;
}
But hidden="until-found" matches [hidden] too, so it gets forced to display: none. Content that isn't rendered can't be found by in-page search, which is the last block in the comparison demo above.
Interestingly, in my tests an #id deep link still expanded it in this case, so if you only test links, it's easy to think everything works. The fix is to exclude until-found:
[hidden]:not([hidden="until-found" i]) {
display: none !important;
}
Two smaller details:
- The box is still there while collapsed:
content-visibility: hiddenonly skips painting the contents. The element's own padding, border and background remain. If your panel has padding, remove it while collapsed, or you'll get an empty strip. - Mind the Shadow DOM in web components:
beforematchbubbles, but it doesn't cross out of a shadow root, so attach the listener to an element inside it.
Trap 3: adding inert while collapsed makes it unfindable
Some components don't just hide content when collapsed; they also add inert so keyboards and screen readers can't reach it. The accordion and tree in my own UI Kit work that way, and that's exactly where adopting until-found got stuck.
The problem is that inert content is excluded from find-in-page; that's how the HTML spec defines it. So hidden="until-found" plus inert is just as unfindable with Ctrl+F, which defeats the purpose. In practice, search couldn't find the content, and while links did expand the panel, nothing inside could be used (see the next paragraph).
I also tested #id deep links in Chromium 141 and 153: the panel does expand, but inert isn't removed, so nothing inside can be focused and screen readers can't read it. You can see it, but you can't use it.
With until-found you don't need inert at all. In my tests, collapsed content is already out of the Tab order and out of the accessibility tree, which is everything inert was there to do:
<!-- Don't do this -->
<div hidden="until-found" inert>โฆ</div>
<!-- This is enough -->
<div hidden="until-found">โฆ</div>
If an existing component collapses with inert, remove inert from both the collapse and expand paths when you switch it to until-found.
What happens without support?
This time the fallback is safe. A browser that doesn't recognize until-found treats it as a plain hidden: the content stays hidden and simply can't be found by search, and nothing breaks. Compared with popover="hint", which degrades to manual and can't even be dismissed, you can use this one confidently as a progressive enhancement.
To detect it:
const supportsUntilFound = "onbeforematch" in HTMLElement.prototype;
Chromium-based browsers have supported until-found for a long time; Firefox and Safari are changing faster, so check caniuse before shipping.
What about <details>?
For a simple show/hide, native <details> also expands automatically when in-page search matches inside it (in Chromium-based browsers), and you don't have to build the button or state yourself.
Here's how I choose:
- A simple "show more": reach for
<details>first; it's the least work. - An accordion that needs a heading hierarchy and custom structure (for example a heading wrapping a button that controls a panel via
aria-controls), or an existing component that can't easily become<details>: usehidden="until-found".
Conclusion
In a few lines:
- Collapsed isn't the same as gone. Content hidden with
hiddendoesn't exist as far as search is concerned. hidden="until-found"keeps content collapsed but findable, expands it when found, and works with deep links too.- Listen for
beforematchto syncaria-expanded, don't toggle withel.hidden = !el.hidden, and check that your CSS reset isn't overriding it. - Don't add
inertwhile collapsed, or search still can't find it;until-foundalready keeps the content out of the Tab order and the accessibility tree. - Without support it falls back to a plain
hidden, so it degrades safely.
What this attribute fixes is the frustration of knowing something is on the page and still not being able to find it. The longer I work on accessibility, the more I see that many problems aren't about features that can't be built, but about information hidden where people can't reach it. This is a good example.
One more box ticked on my list. How much collapsed content does your site have? I'd love to hear about it.
Related reading
- Popover auto, manual and hint: What's Actually the Difference? I Built Demos to Find Out
- Modern CSS & HTML Cheat Sheet: 115 Features, Support & What to Try Next