- Accordion
- Action Bar
- Action Toast
- Alert Banner
- Alert Dialog
- Alert
- Alphabet Index
- Amount Field
- Aspect Ratio
- Attachment
- Availability List
- Avatar
- Badge
- Bank Card
- Breadcrumb
- Bubble
- Button Group
- Button
- Calendar
- Camera Frame
- Card Design Picker
- Card Details
- Card Peek
- Card Stack
- Card
- Carousel
- Chart
- Chat Thread
- Checkbox
- Chip Group
- Choice Tile
- Code Block
- Code Dots
- Collapsible
- Combobox
- Command
- Context Menu
- Copy Field
- Countdown
- Country Select
- Data Table
- Date Field
- Date Picker
- Date Range Picker
- Dialog
- Direction
- Document Paper
- Drawer
- Dropdown Menu
- Empty
- Entity Avatar
- Field
- File Trigger
- File Upload Item
- Hover Card
- Input Group
- Input OTP
- Input
- Item
- Kbd
- Key Value
- Keypad
- Label
- Live Region
- Marker
- Message Scroller
- Message
- Money Amount
- Native Select
- Number Field
- Pagination
- Percent Split Editor
- Phone Field
- Popover
- Progress
- QR Code
- Questionnaire
- Radio Group
- Range Histogram
- Rating
- Resizable
- Scroll Area
- Search Field
- Select
- Separator
- Share Bar
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Sortable List
- Spinner
- Status Timeline
- Step Header
- Steps List
- Swipe Row
- Switch
- Table
- Tabs
- Text Field
- Textarea
- Tier Ladder
- Toggle Group
- Toggle
- Token Search Field
- Tooltip
- Trace
- Transaction Row
- Usage Meter
"use client"
import * as React from "react"Live Region gives you an announce() helper and a LiveRegion component that send messages to one shared, visually hidden announcer at the end of the page. Use it for results that sighted users notice without moving focus, such as "Statement ready", "Card frozen" or "12 transactions shown". Pick announce() for one-off events from a handler, and LiveRegion when the message follows a piece of state. For an error tied to a form field, use the field's own error part instead, since it is read with the field.
Installation#
pnpm dlx shadcn@latest add @dots/live-region
Usage#
import { announce, LiveRegion } from "@/components/ui/live-region"<Button onPress={() => announce("Card frozen")}>Freeze card</Button>
<LiveRegion>{`${count} transactions shown`}</LiveRegion>Features#
- One body-level announcer with a polite and an assertive log, created once and reused
- Stays audible while a React Aria modal hides the rest of the page, through
data-react-aria-top-layer - Waits briefly after the announcer is created so the first message is not lost
- Every
announce()call adds a new line, so the same message twice is read twice - Lines are removed after 7 seconds, so the log never grows
LiveRegionrenders nothing and announces only when its text changes, not on mount
Announce from a handler#
Call announce() after an action finishes, as in the demo at the top of this page. It works from inside a sheet or dialog, where a live region rendered in the page would be hidden from screen readers.
<Button
isPending={isPending}
onPress={async () => {
setIsPending(true)
announce("Preparing your statement")
await exportStatement()
setIsPending(false)
announce("Statement ready")
}}
>
Prepare PDF
</Button>Component#
Render LiveRegion with a single string. It announces the new text each time it changes. The text it mounts with is not read, because it is already on the page.
24 transactions
"use client"
import * as React from "react"<LiveRegion>{`${count} transactions shown`}</LiveRegion>Assertive#
Pass "assertive" only for errors that block the user, such as a failed payment. It interrupts whatever the screen reader is saying. Show the same message on screen too.
"use client"
import * as React from "react"announce("Payment failed. Your balance is too low.", "assertive")Accessibility#
The announcer is a visually hidden container with two role="log" regions, one aria-live="polite" and one aria-live="assertive", both with aria-relevant="additions". It is marked as a React Aria top layer, so ariaHideOutside leaves it readable while a sheet, dialog or alert dialog is open.
- Keep messages short and complete: "Statement ready", not "Done".
- Use
politefor confirmations and progress. Useassertiveonly for blocking errors. - Do not announce what already moves focus. A dialog that opens reads its own title.
- Do not announce every keystroke. Wait until a value settles, as
PercentSplitRemainderdoes. - Pass a single string to
LiveRegion, for example a template literal. JSX with several text nodes is not a string. - To repeat the same message, call
announce()again.LiveRegionskips unchanged text.
API Reference#
announce#
Reads a message to screen reader users.
announce(message: string, politeness?: LiveRegionPoliteness): void| Parameter | Type | Default | Description |
|---|---|---|---|
message | string | - | The text to read. An empty string does nothing. |
politeness | "polite" | "assertive" | "polite" | "assertive" interrupts. Use it for errors only. |
LiveRegion#
Announces its text each time it changes. Renders nothing.
| Prop | Type | Default | Description |
|---|---|---|---|
children | string | - | The message as one string. An empty string announces nothing. |
politeness | "polite" | "assertive" | "polite" | How the change is announced. |
LiveRegionPoliteness#
type LiveRegionPoliteness = "polite" | "assertive"