How to Make an Interactive Element Invisible but Accessible – Master.dev Blog
/ BLOG
AccessibilitycontainCSShiddenSkip Link
How to Make an Interactive Element Invisible but Accessible
Daniel Schwarz<br>on<br>July 8, 2026
Let’s say we want to hide some interactive elements until interaction occurs. For example, we want some cards to have share buttons, but we don’t want them to clutter the UI. Other use-cases include skip links, which we can reveal at key moments in the tab order, anchor links (i.e., links to a specific part of a page), which we can reveal when we’re within said part, copy-to-clipboard buttons, which we can reveal when we’re somehow engaged with the content that we want to copy, scroll-to-top buttons, actionbar items of lesser importance — you know, things that might be okay to hide initially because they’re highly contextual or because users will know that they’re there.
This means we need to hide the element while still making it accessible. Well, that’s what the hidden="until-found" attribute-value does, but we also need to reserve the space, since we can’t hover what isn’t there, right? We also don’t want to cause any layout shift.
And that’s exactly where contain-intrinsic-size: auto none comes into it, which remembers the size of a rendered element.
Here’s an example:
CodePen Embed Fallback
The red border outlines the button, which is hidden but accessible (hoverable, focusable, find-in-pageable, and fragment navigable). The hidden=until-found attribute-value makes it find-in-pageable and fragment navigable, while a bit of CSS makes it hoverable and focusable. If you’re ready to see how, let’s begin.
The HTML (hidden=until-found)
In the demo below, I’ve added hidden=until-found to a . This makes the button’s content find-in-pageable (we can reveal the text by searching for “Hidden until found”), fragment navigable (it’ll reveal itself if it matches a fragment identifier/URL hash), and it’s also focusable right off the bat (well…the button’s focusable, not the content).
button hidden="until-found">Hidden until foundbutton>Code language: HTML, XML (xml)
CodePen Embed Fallback
hidden=until-found declares content-visibility: hidden, overwriting the initial value of visible, so it shouldn’t come as a surprise that it toggles the content’s visibility, not the element itself. This is why we see an empty button to begin with, and why the button remains focusable.
We’ll worry about hiding the overall button in a minute. For now, let’s worry about the fact that interacting with it causes layout shift, and that it’s not fully hoverable, which’ll be a bigger issue once we actually hide it. Essentially, we need the button to be in its full-size state with the content hidden.
Other approaches (visibility: hidden, display: none, opacity: 0, width: 0 + height: 0, etc.) aren’t accessible visually, or to screen readers, or to find-in-page. Old-school opacity: 0 is actually the most effective of those because it reserves layout space and makes the content accessible to screen readers, but when it comes to find-in-page, the content is found but not shown. hidden=until-found, which leverages content-visibility, works because it finds the content and toggles it.
The CSS (mostly contain-intrinsic-size: auto none)
But again, how do we reserve the space to make it fully hoverable and avoid layout shift? How do we hover something that isn’t there, exactly? Well, contain-intrinsic-size: auto none saves the last rendered size of an element, so all we need to do is render it, if only for a millisecond. We can do this using a @keyframes animation.
Obviously, we don’t need to do this if we know size of the element already and have declared it, but if we don’t, then we can show the element for a millisecond using a @keyframes animation and use contain-intrinsic-size: auto none to say, “Hey, remember that it’s this size.”
Anyway, we can see our @keyframes animation below, where content-visibility: visible is declared at the start of the animation. We don’t include a to keyframe selector, which means that it’ll animate to the content-visibility: hidden fallback, courtesy of hidden=until-found.
@keyframes render {<br>/* When the animation starts */<br>from {<br>/* Make the content visible */<br>content-visibility: visible;
/* This is already implied<br>to {<br>content-visibility: hidden;<br>*/<br>}Code language: CSS (css)
Now, let’s apply this animation to the button and ensure that its dimensions are saved during the very brief moment that it’s rendered.
animation: 1ms render applies content-visibility: visible during the one-millisecond animation as contain-intrinsic-size: auto none saves the size. However, you’ll almost certainly see the content flicker regardless. From my understanding, this is because of how the Critical Rendering Path (CRP) works, but we can hide the element altogether with opacity: 0, so no worries there.
button {<br>/* This is almost instantaneous */<br>animation: 1ms render;
/* Save the rendered size...