LiveRegion
SM, under 950B gzippedA declarative live region for components that track their own message as state.
- Category
- Feedback & status
- Budget
- sm, single-purpose control
- Runtime deps
- None
Preview
This is the exact fixture the conformance suite renders in CI. Switch the language to see the component mirror and reformat.
Install
npx shadcn@latest add https://kata-ui-rho.vercel.app/r/live-region.jsonCopies the source into your project. Pulls in 1 primitive: visually-hidden.
Source
"use client";
import { useEffect, useState } from "react";
import { VisuallyHidden } from "./visually-hidden";
export interface LiveRegionProps {
message: string;
politeness?: "polite" | "assertive";
}
/**
* A declarative live region for a component that already tracks its own
* message as state, as an alternative to calling the imperative `announce()`
* function.
*
* Renders empty on mount and only starts reflecting `message` one commit
* later — the same mount-before-fill requirement `announce()` documents:
* a screen reader must see this node exist before its text first changes, or
* most implementations never announce it at all.
*/
export function LiveRegion({ message, politeness = "polite" }: LiveRegionProps) {
const [content, setContent] = useState("");
useEffect(() => {
// Deliberately not "just render `message` directly" — the whole point of
// this component is the one-commit delay between mount and first fill
// that makes screen readers actually announce the change.
// eslint-disable-next-line react-hooks/set-state-in-effect
setContent(message);
}, [message]);
return (
<VisuallyHidden as="div">
<div role={politeness === "assertive" ? "alert" : "status"} aria-live={politeness} aria-atomic="true">
{content}
</div>
</VisuallyHidden>
);
}
What CI checks
- Bundled, minified and gzipped against its tier budget
- Audited by axe in the state previewed above
- Rendered through react-dom/server with no browser globals
- Scanned for network calls, dangerous sinks, and unguarded animation