// preview in
saved for every page

VisuallyHidden

Text that screen readers read but the screen doesn't show. Use it to add context that sighted people get from the layout, like which invoice an "Edit" link belongs to, or to announce a change.

Pattern Visually hidden content (clip pattern)Server componentSource ↗

Installation

Set up Camaleo once (Installation), then import it. It has no state, so it renders on the server.

import { VisuallyHidden } from "@camaleo/react";

Anatomy

One <span> shrunk to 1 × 1 px and clipped. It stays in the page and in the accessibility tree, so screen readers and translation tools still get the text.

Why not display: none or hidden?

display: none, visibility: hidden and the hidden attribute remove the element from the accessibility tree as well as from the screen: nobody gets the text. aria-hidden="true" does the opposite of what you want here: it stays on screen and is skipped by screen readers. VisuallyHidden is the one way to keep text for assistive technology only.

Examples

Every example is a real file you can copy. Turn on a screen reader (VoiceOver, NVDA or TalkBack) to hear the hidden text, or inspect the accessibility tree in your browser's dev tools.

minimal · light · #3fa34d
Ways to hide content and who still gets it
TechniqueOn screenScreen reader
VisuallyHiddenNoYes
display: noneNoNo
hidden attributeNoNo
aria-hidden="true"YesNo

Four ways to hide

Only VisuallyHidden takes text off the screen and keeps it for screen readers. display: none and hidden hide it from everyone; aria-hidden hides it only from screen readers.

aria-label or hidden text

For an IconButton, aria-label is enough. When part of the name is visible, add the rest as hidden text: it is real text, so browser translation translates it too.

Cerrado beans, 500 g$9.80Cart: 2 items

Announcing a change

Add to the cart. The count changes on screen, and a hidden role="status", rendered empty from the start, tells screen reader users what happened.

Click here, then press Tab.

Skip to content

Coffee of the month

Fazenda Santa Inês, yellow bourbon, 1,150 m.

Skip link

Click the hint, then press Tab. A SkipLink sits at the top of the page and appears over it on focus: use it to jump to the main content.

Click here, then press Tab.

Skip to results
Roast

12 coffees

Focus lands here, past the filters.

Shown on focus

Click the hint, then press Tab. With focusable, the "Skip to results" link appears in place while it has focus, and Enter jumps past the filters. Without it, the text stays hidden even when focused.

Invoices for March 2026, newest first
ClientAmountStatus
Padaria Estrela$412.00Paid
Café Aurora$186.40Open
Mercado Bom Preço$1,240.00Overdue

Table caption

When the layout makes a table's purpose obvious, a hidden <caption> still names it, and names the scroll region on phones.

A label you can't see

A lone search box next to a search button can hide its label. It must still be a real <label>; a placeholder disappears as soon as someone types.

Gooseneck kettleRated 4.8 out of 5, from 312 reviews$64.00

Symbols in words

"★ 4.8 (312)" means nothing read aloud. Hide the symbols with aria-hidden and give screen readers the whole sentence instead.

In context

Hidden text is everywhere in a good interface. These are whole pieces of UI you can copy, in the skin you picked.

Recent invoices

Invoices for March 2026, newest first
ClientAmountStatusActions
Padaria Estrela#4821$412.00PaidEdit invoice #4821
Café Aurora#4822$186.40OpenEdit invoice #4822
Mercado Bom Preço#4823$1,240.00OverdueEdit invoice #4823
Invoice tableCard · Table · Link · IconButton · VisuallyHidden
Skip to content
Acme Store
Coffee of the monthFazenda Santa Inês, yellow bourbon, 1,150 m.
Store headerSkipLink · Button · Badge · VisuallyHidden
Compare plans
FeatureFreePro
Invoices per month10Unlimited
Payment remindersIncludedIncluded
Bank reconciliationNot includedIncluded
Phone supportNot includedIncluded
Plan comparisonTable · VisuallyHidden

Guidelines

DoFinish the sentence for repeated links.

On screen, "Edit" sits in its row. In a list of links, "Edit invoice #4821" still makes sense.

Don’tThree links called Edit.

Screen reader users who list the links hear "Edit, Edit, Edit" and can't tell which is which.

DoVisible labels when there's room.

Everybody benefits from a label: people with memory or attention difficulties, and anyone checking what they typed.

Don’tHiding labels to save space.

Once the fields are filled, nothing on screen says which is which.

Props

PropTypeDefaultDescription
focusablebooleanfalseShows the content while it, or something inside it, has focus, such as a "Skip to results" link. Needs the Camaleo stylesheet, which hides it the rest of the time.
styleCSSProperties—Merged after the hiding styles, so it can override them. Don't, unless you mean to show the text.
roleReact.AriaRole—Use "status" (polite) or "alert" (urgent) to announce changes. Render it empty first, then set the text.
childrenReactNode—The text to read. Keep a leading space when it continues visible text, as in "Edit" + " invoice #4821".

Every attribute of a <span> passes through, like id (to point aria-describedby at it) or aria-live.

Styling hooks

There is nothing to style: the hiding styles are inline, so no stylesheet can undo them by accident. With focusable they come from the stylesheet instead, because inline styles can't react to focus.

Attributes

data-slot"visually-hidden"
data-focusablewith focusable; hidden while nothing inside has focus

Tokens it reads

Accessibility

TabVisuallyHidden takes no focus itself. A link or button inside it only belongs there with focusable, which shows it while it has focus; without it, it would get focus while staying invisible.
Was this page helpful? · Edit this page on GitHub