Visually Hidden
Free · MITHeadless, accessible, and styled entirely through the attribute surface below — bring your own CSS system, or start from the reference theme.
Install
Without a flag you get no styling at all — plain HTML carrying the attribute surface below. --theme copies the same theme in two dialects;--bridge lumos re-points either one onto Lumos for Astro's tokens (it needs a theme to re-point, and Lumos itself, already in your project).
Unstyled — behavior only, you write the CSS
npx astrocraft-ui add utility/visually-hiddenPlain-CSS theme — no build step
npx astrocraft-ui add utility/visually-hidden --theme cssTailwind theme — needs Tailwind v4
npx astrocraft-ui add utility/visually-hidden --theme tailwindPlain-CSS theme, re-pointed onto Lumos tokens
npx astrocraft-ui add utility/visually-hidden --theme css --bridge lumosTailwind theme, re-pointed onto Lumos tokens
npx astrocraft-ui add utility/visually-hidden --theme tailwind --bridge lumosLive demo
VisuallyHidden & LiveRegion
Neither one has anything to look at, which is the point. The button below is labelled Delete invoice 4021 for a screen reader and shows only an icon; the announcer is a LiveRegion wrapped in a VisuallyHidden, rendered once and left empty, because aria-live is only honoured on a region that was already in the document when its contents changed.
Press Announce twice. A live region only speaks when its contents CHANGE, so the second press would be silence — the primitive empties the region first, in a later task, which is what makes the repeat a change.
Attribute surface
With no class-merging machinery, these attributes are the whole extension point. State is a data-attribute or a native attribute; visibility is the hiddenattribute. Generated from source by pnpm slots.
| Component | Slots | Variants | Runtime state | Native state |
|---|---|---|---|---|
| `VisuallyHidden.astro` | `visually-hidden` | — | — | — |
Source
What the command copies — 2 files, alias-free so the copy lands standing alone. This is the whole component; there is nothing else.
---
// src/components/ui/utility/visually-hidden/VisuallyHidden.astro — headless primitive (see ../../README.md).
// The clip-path recipe, once, correct. Text that must be READ but not SEEN: the name of an icon-only
// button, the units after a number, the "opens in a new tab" a link owes its reader.
//
// The two obvious ways to hide something both destroy the point — `display: none` and
// `visibility: hidden` remove the element from the accessibility tree along with the screen, so the
// text is hidden from exactly the reader it was written for. This is the version that is not.
//
// The rule lives in structure.css beside the library's other visually-hidden elements, because it is
// an accessibility mechanism and not a look: a theme that dropped it would turn every one of them
// into visible clutter, silently.
//
// `as` exists because a <span> may not legally contain block content, and this often wraps a
// paragraph or a list. Typed as Reveal types its own — a tag name, not a variant, so it renders as
// the ELEMENT and never as a `data-as` attribute.
import "../../../../styles/structure.css";
import type { HTMLAttributes } from "astro/types";
type Props = HTMLAttributes<"span"> & {
/** Render element. A <span> may not contain block content. */
as?: keyof HTMLElementTagNameMap;
};
const { as: Tag = "span", class: className, ...rest } = Astro.props;
---
<Tag class={className} data-slot="visually-hidden" {...rest}><slot /></Tag>
import VisuallyHidden from "./VisuallyHidden.astro";
export { VisuallyHidden };
export default VisuallyHidden;
What you get
The component source, copied into your project by npx astrocraft-ui add utility/visually-hidden — no package dependency, no CSS to fight. Whatever the paid blocks compose, this is it.