Visually Hidden
Text for screen readers only: kept in the accessibility tree, drawn nowhere, optionally shown while it holds focus.
c2-visually-hidden keeps its content for assistive technology and draws nothing: the text is read out, can name the control it sits in and can be referenced by aria-labelledby or aria-describedby, but it takes no space in the layout. Use it for the label of an icon-only button, the words a visual cue stands for (“4 out of 5 stars”), a heading the layout makes obvious, or a live-region announcement (put role="status" and aria-live on the element itself).
Add focusable for a link or button only keyboard users need to see: the content appears while focus is inside it and hides again when focus leaves. Revealed, the element applies no styles of its own, so the page styles it like any inline content.
The hiding rules are !important inside the shadow root, so a page rule on the tag cannot bring the content back by accident. hidden (or display: none) is different: it removes the content from the accessibility tree too. The element has no CSS variables, because nothing about drawing nothing is worth tuning.
Import
import '@c2n/components/visually-hidden'The examples below also use @c2n/feather-icons (npm install @c2n/feather-icons).
Usage
The first row is three buttons named “Edit invoice”, “Share invoice” and “Delete invoice”; the second reads “Rated 4 out of 5 stars” and “Read more about server rendering” where the screen shows stars and “Read more”. In the last row nothing is drawn until Tab lands on the link.
A hidden heading gives a screen-reader user a landmark to jump to without adding a visible title the design does not want. Wrap the whole heading, so its margins leave the layout too:
<section aria-labelledby="filters-heading">
<c2-visually-hidden><h2 id="filters-heading">Filters</h2></c2-visually-hidden>
…
</section>An announcement the screen does not need to show goes on the element itself:
<c2-visually-hidden role="status" aria-live="polite">12 results</c2-visually-hidden>Before the element is defined
Until @c2n/components/visually-hidden has loaded, the browser treats the tag as an unknown inline element and draws its text. A page rendered on the server or exported as static HTML (Next.js, Astro, SvelteKit) paints before the module runs, so give the undefined element the same rules once in the app’s stylesheet:
c2-visually-hidden:not(:defined) {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
clip-path: inset(50%) !important;
white-space: nowrap !important;
border: 0 !important;
}