A brief, auto-dismissing confirmation. Never a blocker.
.iz-toast is for the "Invoice saved" / "Couldn't save proposal"
moment — a short-lived message that confirms an action just happened, then gets
out of the way on its own. It never traps focus and never requires a dismiss
click; a consumer's own timer controls how long it stays. First extracted from
Irozon Tools, which had reinvented this exact pattern three times — once per
feature area — before it moved here.
.iz-toast-region — one per page/app shell, fixed to the viewport corner, aria-live="polite" + aria-atomic="true" so assistive tech announces each toast as it's inserted..iz-toast inside the region — an icon slot and a message. data-visible="true" (set a frame after mount) drives the entrance; removing it drives the exit.<div class="iz-toast-region" aria-live="polite" aria-atomic="true">
<div class="iz-toast" data-variant="success" data-visible="true" role="status">
<span class="iz-toast__icon" aria-hidden="true">✓</span>
<span class="iz-toast__message">Invoice saved</span>
</div>
</div>
The same four roles Badge and Status already use.
data-variant reuses Badge/Status's exact semantic set — no new
colors, and dark theme resolves for free since every color is a token.
No queue, no timer, no dismiss button — on purpose.
Auto-dismiss timing, whether multiple toasts stack or replace each other, and any
manual dismiss control are all application concerns, not this component's. A
consumer that wants a close button composes one from
.iz-button[data-icon-only]
inside .iz-toast, the same way a field composes in its own inline
action. Keeping the state machine out of the design system is deliberate — three
different apps will reasonably want different auto-dismiss durations and stacking
behavior, and none of that changes what a toast looks like.
Announced, not just shown.
aria-live="polite" on .iz-toast-region — a screen reader announces new toast text without interrupting whatever the user is doing, and without needing focus to move..iz-toast__message — the icon is aria-hidden="true" and never the only way the outcome is communicated.data-visible absent, mid-exit) the toast is pointer-events: none — it can only ever be interacted with while actually visible, and even then it never traps focus or keyboard navigation the way a dialog does.Toast and ToastRegion from @irozon/design-system-react.
Toast owns the entrance/auto-dismiss animation for one item;
ToastRegion is the aria-live container. The toast
list — what's currently showing — stays your app's own state; this
package doesn't manage a queue.
import { ToastRegion } from "@irozon/design-system-react";
import { useState, useCallback } from "react";
const [toasts, setToasts] = useState([]);
const showToast = useCallback((message, variant = "success") => {
setToasts((t) => [...t, { id: Date.now(), message, variant }]);
}, []);
<ToastRegion
toasts={toasts}
onDismiss={(id) => setToasts((t) => t.filter((x) => x.id !== id))}
/>