Text a screen reader announces but the eye does not see (also called screen-reader-only or sr-only). Reach for it when a visible label is clear from its surroundings but not on its own, and for a skip link.
import { VisuallyHidden, announce } from "@umriss-ui/core";
The eye reads "Acknowledge" beside the incident; a screen reader hears which one.
import { Button, VisuallyHidden } from "@umriss-ui/core";
export default function CompleteALabel() {
return (
<Button size="sm">
Acknowledge
<VisuallyHidden> incident INC-1048</VisuallyHidden>
</Button>
);
}
Render it as a link with focusable: it is the first Tab stop on the page and shows itself only while it has focus.
import { Stack, Text, VisuallyHidden } from "@umriss-ui/core";
export default function SkipLink() {
return (
<Stack gap={2} align="flex-start">
<VisuallyHidden as="a" href="#shipments" focusable>
Skip to the shipment list
</VisuallyHidden>
<Text size="sm" tone="secondary">
Tab into this example: the link appears above this line.
</Text>
</Stack>
);
}
Twenty "Edit budget" buttons sound alike to a screen reader; append each row's name hidden and each says what it edits.
import { Button, Stack, Text, VisuallyHidden } from "@umriss-ui/core";
/* Data from the controlling world, written out here so the example runs on its own. */
interface CostCentre {
id: string;
name: string;
owner: string;
/** The budget of one month. */
monthlyBudget: number;
}
const COST_CENTRES: readonly CostCentre[] = [
{ id: "CC-1100", name: "Sales", owner: "Helen Marsh", monthlyBudget: 142_000 },
{ id: "CC-1200", name: "Marketing", owner: "Rafael Ortiz", monthlyBudget: 68_000 },
{ id: "CC-2100", name: "Engineering", owner: "Anika Sørensen", monthlyBudget: 188_000 },
{ id: "CC-2200", name: "Design", owner: "Paul Whitaker", monthlyBudget: 54_000 },
{ id: "CC-3100", name: "Customer service", owner: "Grace Obi", monthlyBudget: 61_000 },
{ id: "CC-4100", name: "Finance", owner: "Martina Vogel", monthlyBudget: 47_000 },
{ id: "CC-4200", name: "People", owner: "Daniel Frost", monthlyBudget: 39_000 },
{ id: "CC-4300", name: "IT", owner: "Kenji Arai", monthlyBudget: 83_000 },
{ id: "CC-4400", name: "Facilities", owner: "Olga Ivanova", monthlyBudget: 72_000 },
];
export default function ActionsInRows() {
return (
<Stack gap={2} style={{ maxWidth: 420 }}>
{COST_CENTRES.slice(0, 4).map((centre) => (
<Stack key={centre.id} direction="row" gap={3} align="center" justify="space-between">
<Text size="sm">
{centre.name} <Text as="span" size="xs" tone="muted">{centre.owner}</Text>
</Text>
<Button size="sm" variant="ghost">
Edit budget
<VisuallyHidden> of {centre.name}</VisuallyHidden>
</Button>
</Stack>
))}
</Stack>
);
}
| Key | Action |
|---|---|
Tab |
Reaches a focusable one and shows it while it has focus. |
VisuallyHiddenProps<E extends ElementType = "span">| Name | Type | Default | Description |
|---|---|---|---|
as | E | "span" | Element to render as. Shown in: Add a skip link |
focusable | boolean | false | Becomes visible as soon as the content receives the focus (skip link). Shown in: Add a skip link |
ref | Ref<HTMLElement> | — | Receives the component's root element. from RefPropShown in: A row that holds still (Sizes), An anchored surface (Popover), Take the width of the trigger (Popover) and 2 more |
Also takes every attribute of the chosen element – without as, ref.
What umriss deliberately does not build, and why: ADR-0032.