Visually Hidden

The Visually Hidden component renders content that is invisible to sighted users but fully present in the accessibility tree. Use it to provide additional context for assistive technology users without affecting the visual design.

Common use cases include labeling icon-only buttons, supplementing terse visible text with fuller descriptions, and building focusable skip-link patterns.


Installation

npm install @libretexts/davis-react

Basic Usage

Wrap any text or element you want screen readers to hear but sighted users not to see.

import { VisuallyHidden } from '@libretexts/davis-react';

// Add an accessible label to an icon-only button
export default function CloseButton() {
  return (
    <button>
      <CloseIcon aria-hidden="true" />
      <VisuallyHidden>Close dialog</VisuallyHidden>
    </button>
  );
}

Custom Element

By default VisuallyHidden renders a <span>. Use the as prop to render any HTML element or React component.

{/* Render as a heading for screen readers without affecting visual layout */}
<VisuallyHidden as="h2">Section heading for screen readers only</VisuallyHidden>

{/* Annotate a table column that is visually obvious but needs a label for AT */}
<th>
  <VisuallyHidden>Actions</VisuallyHidden>
</th>

Focusable Variant

Set focusable to true to make the element visible when it receives keyboard focus. This enables skip-link-style patterns directly with VisuallyHidden.

<VisuallyHidden as="a" href="#main-content" focusable>
  Skip to main content
</VisuallyHidden>

When focusable is true, the clip styles are removed on :focus and :focus-within, making the element fully visible with high-contrast styling for keyboard users.

Use SkipLink for skip navigation

For the standard skip-to-main-content pattern, prefer the dedicated SkipLink component, which handles positioning and styling automatically.


Adding Context to Terse Labels

Use VisuallyHidden to give screen reader users extra context that would be redundant visually but helpful when navigating by element.

// A table with "Edit" links for each row — sighted users see the row context,
// screen reader users hear "Edit Jane Smith" for each link
<table>
  <tbody>
    {users.map((user) => (
      <tr key={user.id}>
        <td>{user.name}</td>
        <td>
          <a href={`/users/${user.id}/edit`}>
            Edit<VisuallyHidden> {user.name}</VisuallyHidden>
          </a>
        </td>
      </tr>
    ))}
  </tbody>
</table>

Props

PropTypeDefaultDescription
childrenReactNodeContent to hide visually (required)
asElementType'span'HTML element or component to render as
focusablebooleanfalseWhen true, the element becomes visible on keyboard focus
classNamestringAdditional CSS classes

Accessibility

  • Uses the standard SR-only clip pattern: position: absolute, width: 1px, height: 1px, overflow: hidden, clip: rect(0, 0, 0, 0), white-space: nowrap
  • The content is never removed from the accessibility treearia-hidden is not applied
  • When focusable={true}, the clip styles are reversed on :focus so the element is fully visible and usable by keyboard users
  • Prefer VisuallyHidden over aria-label when the text is more than a single phrase, or when it needs to be localizable via React's rendering pipeline

When to use VisuallyHidden vs aria-label:

  • Use VisuallyHidden inside an element when the label needs to be rendered as real DOM text (e.g., for translation, testing, or rich content)
  • Use aria-label directly on an element for simple, short string labels on interactive elements like buttons and inputs
  • Never use both on the same element — they will conflict