What is Visually hidden?

Visually hidden: Visually hidden is a CSS technique that removes an element from view while keeping it in the accessibility tree, so screen readers still read its text.

Some text should exist only for people who cannot see the screen: the name of an icon-only button, the column header a layout dropped on mobile, the word "Online" behind a green dot, a table caption the design has no room for, a status message that is obvious visually. Visually hidden text is the way to provide it. The element stays in the DOM and in the accessibility tree, but it is shrunk to a 1px box, clipped, and taken out of the layout so nothing on screen changes. Tailwind ships the technique as the sr-only utility, and most design systems have a VisuallyHidden component for it.

The CSS

css
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

Each line has a reason: the absolute position removes it from layout, the 1px box and overflow clipping keep it from painting, white-space: nowrap stops some screen readers from reading a word per line when the 1px width wraps text, and the negative margin cancels the box's footprint. display: none, visibility: hidden and the hidden attribute all remove the element from assistive technology as well, so they are not substitutes. aria-hidden is the reverse case: visible to sighted users, hidden from screen readers.

When to use it

  • Accessible names for icon-only controls, as an alternative to aria-label. Hidden text is translated by translation tools; aria-label often is not.
  • Context sighted users get from layout: "Opens in a new tab", "Step 2 of 4", the heading of a card group, a table caption.
  • [Live region](/glossary/live-region) messages that duplicate what is already visible, such as "Copied".
  • [Skip links](/glossary/skip-link) until they receive focus.
  • Form labels when a placeholder is doing visual duty, though a visible label is better.

Common mistakes

  • Hiding focusable elements permanently. A visually hidden link or button is still focusable, and keyboard users see focus disappear. Reveal it on focus, as skip links do, or remove it from the Tab order.
  • Hiding too much. If screen reader users hear three sentences where sighted users see an icon, the interface is inconsistent. Hidden text should be short and should name, not explain.
  • Applying it to a container with layout children. Children are clipped too, which is rarely the intent; apply it to the element that holds the text.

In MiniDev

VisuallyHidden renders a span with the class above, IconButton takes an aria-label for its name, Spinner exposes its state through a role and a label rather than extra text, and SkipLink uses sr-only with focus:not-sr-only to reappear on focus:

tsx
import { VisuallyHidden } from "@/components/ui/visually-hidden"

export function PresenceDot({ online }: { online: boolean }) {
  return (
    <span className="inline-flex items-center gap-1.5">
      <span aria-hidden className={online ? "size-2 rounded-full bg-success" : "size-2 rounded-full bg-border"} />
      <VisuallyHidden>{online ? "Online" : "Offline"}</VisuallyHidden>
    </span>
  )
}
Visually HiddenuiVisually hidden wrapper that keeps content available to screen readers while hiding it on screen via sr-only. Use it for icon button labels and extra context.npx shadcn@latest add @minidev/visually-hiddenIcon ButtonuiA square React button for icon only actions in small, default and large sizes with every Button variant. Its aria-label prop is required, so it is always named.npx shadcn@latest add @minidev/icon-button