Visually Hidden
A screen-reader-only content wrapper that visually hides elements while keeping them accessible to assistive technologies. Built on Radix UI.
Basic
A screen-reader-only content wrapper using Radix UI VisuallyHidden. Renders content that is invisible to sighted users but fully accessible to screen readers and other assistive technologies.
There is hidden text below this line (inspect the DOM to see it):
Installation
Install the component via the CLI in one command.
npx @work-rjkashyap/unified-ui add visually-hiddenpnpm dlx @work-rjkashyap/unified-ui add visually-hiddennpx @work-rjkashyap/unified-ui add visually-hiddenbunx @work-rjkashyap/unified-ui add visually-hiddenIf you haven't initialized your project yet, run npx @work-rjkashyap/unified-ui init first. See the CLI docs for details.
Or install the full package
Use this approach if you prefer to install the entire design system as a dependency instead of copying individual components.
npm install @work-rjkashyap/unified-uipnpm add @work-rjkashyap/unified-uiyarn add @work-rjkashyap/unified-uibun add @work-rjkashyap/unified-uiAnatomy
import { VisuallyHidden } from "@work-rjkashyap/unified-ui";Overview
VisuallyHidden is a utility component that hides its children from the visual rendering while keeping them in the DOM for screen readers. This is essential for providing context to assistive technologies without affecting the visual design.
This component uses Radix UI's VisuallyHidden primitive under the hood, which applies CSS that hides the element visually without using display: none or visibility: hidden — both of which would also hide content from screen readers.
Basic Usage
Wrap any text or element in VisuallyHidden to make it invisible but accessible:
<VisuallyHidden>
This text is only announced by screen readers
</VisuallyHidden>Common Patterns
Icon-Only Buttons
When a button only contains an icon, add a VisuallyHidden label so screen readers can announce its purpose:
<button type="button">
<TrashIcon className="size-4" />
<VisuallyHidden>Delete item</VisuallyHidden>
</button>Decorative Elements with Context
Provide additional context for decorative elements:
<div className="flex items-center gap-2">
<span className="size-2 rounded-full bg-green-500" />
<VisuallyHidden>Status: Online</VisuallyHidden>
<span>John Doe</span>
</div>Skip Navigation Links
Create a skip-to-content link that only appears for keyboard and screen reader users:
<VisuallyHidden>
<a href="#main-content">Skip to main content</a>
</VisuallyHidden>Form Labels
When a visual label is not desired but accessibility requires one:
<div>
<VisuallyHidden>
<label htmlFor="search">Search</label>
</VisuallyHidden>
<input id="search" type="search" placeholder="Search..." />
</div>Table Captions
Add an accessible caption to a table without showing it visually:
<table>
<VisuallyHidden as="caption">
Monthly revenue breakdown by product category
</VisuallyHidden>
<thead>{/* ... */}</thead>
<tbody>{/* ... */}</tbody>
</table>Announcements
Provide context for dynamic content updates:
<VisuallyHidden role="status" aria-live="polite">
{results.length} search results found
</VisuallyHidden>When to Use
| Scenario | Use VisuallyHidden? |
|---|---|
| Icon-only button needs a label | ✅ Yes |
| Decorative image needs alt text | ❌ Use alt prop |
| Form input needs a visible label | ❌ Use <Label> |
| Form input needs a hidden label | ✅ Yes |
| Providing context for status badges | ✅ Yes |
| Hiding content from everyone | ❌ Use display: none |
| Skip navigation link | ✅ Yes |
| Additional context for screen readers | ✅ Yes |
Comparison with Alternatives
| Method | Visible | Screen Reader | Use Case |
|---|---|---|---|
VisuallyHidden | ❌ | ✅ | SR-only labels and context |
display: none | ❌ | ❌ | Fully hidden content |
visibility: hidden | ❌ | ❌ | Fully hidden (keeps layout) |
aria-hidden="true" | ✅ | ❌ | Decorative visual elements |
opacity: 0 | ❌ | ✅ | Animation targets |
Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | — | Content to render (hidden visually). |
asChild | boolean | false | Merge props onto the child element via Radix. |
All standard HTML attributes are forwarded to the underlying <span> element, including role, aria-live, aria-label, and id.
How It Works
The component applies a carefully crafted set of CSS properties that remove the element from visual flow while keeping it accessible:
// Simplified version of what Radix applies:
{
position: "absolute",
border: 0,
width: 1,
height: 1,
padding: 0,
margin: -1,
overflow: "hidden",
clip: "rect(0, 0, 0, 0)",
whiteSpace: "nowrap",
wordWrap: "normal",
}This technique is widely recognized as the correct way to hide content visually while maintaining screen reader accessibility, preferred over display: none or visibility: hidden.
Accessibility
- Content is fully accessible to screen readers and other assistive technologies.
- Content is not visible to sighted users and does not affect layout.
- Content is still part of the DOM and can be targeted by
aria-describedbyandaria-labelledby. - Focusable elements inside
VisuallyHidden(like links) will still receive focus and may become briefly visible in some browsers — this is expected behavior for skip links.
Design Tokens
This component does not use any design tokens — it is a pure utility wrapper that applies screen-reader-only CSS styles.