<!-- Protocore Design System — VisuallyHidden -->
# VisuallyHidden

- **Category:** Utilities (`utilities`)
- **Slug:** `utilities/visually-hidden`
- **Status:** stable
- **Platforms:** web
- **Import:** `import { VisuallyHidden } from "@protocore/pds";`
- **Docs:** https://pds.protocore.io/components/utilities/visually-hidden

> Hides content visually while keeping it in the accessibility tree.

## When to use it

**VisuallyHidden** removes content from view while leaving it available to screen readers — the opposite of `aria-hidden` (which hides from assistive tech but not the eye). Reach for it to name icon-only controls, add skip links, label form regions, or supply context to a terse value. It renders a `<span>` using the shared `.pds-VisuallyHidden` primitive (the standard clip technique — not `display:none`, which would drop it from the a11y tree). Prefer a direct `aria-label` when the element accepts one; use VisuallyHidden when you need *real text content* in the tree (e.g. inside a button that also has an icon, or a link whose visible text is an icon).

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `className` | `string` | no | — | — |
| `style` | `CSSProperties` | no | — | — |

## Examples

### Basics

The classic use: an icon-only control. The visible glyph carries no text, so a `VisuallyHidden` label gives screen-reader users a name without adding visible chrome.

```tsx
import { VisuallyHidden, Button } from "@protocore/pds";
import { RefreshCw } from "lucide-react";

export default function Demo() {
  // Icon-only trigger: the label is read by screen readers but hidden visually.
  return (
    <Button variant="secondary" startIcon={<RefreshCw size={16} />}>
      <VisuallyHidden>Refresh ledger snapshot</VisuallyHidden>
    </Button>
  );
}
```

### Extra spoken context

Add a hidden heading to structure a region for assistive tech, or spell out a terse visible value ("99.98%") so it isn't announced without context. The hidden text stays fully in the accessibility tree.

```tsx
import { VisuallyHidden, Stack, HStack, Text } from "@protocore/pds";

export default function Demo() {
  return (
    <Stack gap={3}>
      {/* A section heading for assistive tech without visible chrome */}
      <VisuallyHidden>Node metrics</VisuallyHidden>

      {/* Extra spoken context for a terse visible value */}
      <HStack gap={2} align="baseline">
        <Text mono style={{ color: "var(--pds-accent)" }}>
          99.98%
        </Text>
        <VisuallyHidden>uptime over the last 30 days</VisuallyHidden>
        <Text size="sm" color="muted">
          uptime
        </Text>
      </HStack>
    </Stack>
  );
}
```

## Do & don't

**Do**

- Name icon-only buttons and links with a VisuallyHidden label (or an `aria-label`).
- Use it for skip links that become visible on focus.
- Add spoken context to terse values and stat numbers.
- Keep the hidden text concise and meaningful — it is read verbatim.

**Don't**

- Don't use `display:none` or `visibility:hidden` for this — those remove the text from the a11y tree.
- Don't hide genuinely important, sighted-user-relevant content with it.
- Don't duplicate a label that's already announced, causing double reading.
- Don't put interactive elements inside it expecting them to be reachable — hidden focusable content is a trap; use a focus-revealing skip link pattern instead.

## Accessibility

**Notes**

- Uses the clip/off-screen technique, so content stays in the accessibility tree and is announced normally.
- It does not remove elements from tab order — avoid placing focusable controls inside unless you intend a focus-revealed skip link.
- This is the same primitive Radix uses internally to label its components; safe to compose with Radix triggers.
- For content that should be hidden from *screen readers* but visible on screen, use `aria-hidden` instead — VisuallyHidden is the inverse.

## Related

`pds-provider`, `icon-button`, `icon`, `kbd`

---

© Protocore. All rights reserved. Use of the Protocore Design System requires prior written authorization from Protocore (contact@protocore.io). These machine-readable materials must not be ingested into ML-training datasets or derivative design systems. See https://pds.protocore.io/legal/
