Toast
Imperative, globally-scoped toast notifications. Drop <Toaster /> once in your root layout and call toast.* from anywhere — event handlers, async functions, or outside React.
pnpm add @structyl/styledSetup
Add <Toaster /> once anywhere in your component tree — typically the root layout. Toasts fired anywhere in your app will render there.
// app/layout.tsx
import { Toaster } from '@structyl/styled';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<Toaster />
</body>
</html>
);
}Variants
Six built-in variants — try them all live below.
import { toast } from '@structyl/styled';
toast.success('Changes saved', { description: 'Your profile has been updated.' });
toast.error('Upload failed', { retry: () => upload() });
toast.warning('Storage almost full');
toast.info('Update available');
toast.loading('Processing…'); // duration: Infinity by default
toast.show({ title: 'Custom', variant: 'default' }); // full controlPromise toast
toast.promise shows a loading state while the promise is pending, then automatically transitions to success or error.
toast.promise(
fetch('/api/save').then(r => r.json()),
{
loading: 'Saving…',
success: (data) => `Saved ${data.name}!`,
error: (err) => `Error: ${err.message}`,
},
);Placement
Set the default position on <Toaster />, or override per-toast via horizontal / vertical options.
// Default for all toasts — bottom right
<Toaster horizontal="right" vertical="bottom" />
// Override for a specific toast
toast.error('Auth expired', { horizontal: 'center', vertical: 'top' });Update & dedup
Passing the same id to any toast.* call replaces the existing toast rather than stacking a new one.
const id = toast.loading('Uploading file…');
// Later — replaces the loading toast in-place
toast.success('Upload complete!', { id, description: 'Your file is ready.' });
toast.error('Upload failed', { id, retry: () => upload() });Dismiss & remove
const id = toast.info('You have 3 unread messages');
toast.dismiss(id); // plays the exit animation
toast.remove(id); // instant removal, no animation
toast.dismiss(); // dismiss ALL toastsToaster props
| Prop | Type | Default | Description |
|---|---|---|---|
| horizontal | 'left' | 'center' | 'right' | 'right' | Default horizontal alignment for toasts. |
| vertical | 'top' | 'bottom' | 'bottom' | Default vertical alignment for toasts. |
| maxToasts | number | 5 | Maximum number of toasts visible at once per position group. |
| className | string | — | Extra classes forwarded to the viewport wrapper. |
Toast options
Passed as the second argument to any toast.* method.
| Option | Type | Default | Description |
|---|---|---|---|
| id | string | auto | Reuse to update an existing toast instead of stacking a new one. |
| description | string | — | Secondary text shown below the title. |
| variant | ToastVariant | 'default' | One of: default | success | error | warning | info | loading. |
| duration | number | 4000 | Auto-dismiss delay in ms. Pass Infinity to keep until manually dismissed. |
| horizontal | 'left' | 'center' | 'right' | Toaster default | Override horizontal placement for this toast. |
| vertical | 'top' | 'bottom' | Toaster default | Override vertical placement for this toast. |
| retry | () => void | — | Adds a Retry button that calls this function when clicked. |
| action | { label: string; onClick: () => void } | — | Custom action button. Takes priority over retry for the button label. |
| onDismiss | (id: string) => void | — | Called right before the toast is dismissed. |
useToast
Subscribe to the toast store from inside a React component. Useful for building a custom Toaster or reading the current toast list.
import { useToast } from '@structyl/styled';
function CustomToaster() {
const { toasts, dismiss, remove } = useToast();
return (
<div className="fixed bottom-4 right-4 flex flex-col gap-2">
{toasts
.filter(t => t.open)
.map(t => (
<div key={t.id} className="rounded-xl bg-card border border-border px-4 py-3 shadow-lg">
<p className="font-semibold">{t.title}</p>
{t.description && <p className="text-sm text-muted-foreground">{t.description}</p>}
<button onClick={() => dismiss(t.id)}>Close</button>
</div>
))
}
</div>
);
}Accessibility
Toast is built on the Radix Toast primitive which handles ARIA live regions automatically:
- • The viewport has
role="region"andaria-label="Notifications" - • Each toast is announced to screen readers as a live region update
- • Focus is not moved to the toast — users can continue their current task
- • The hotkey F8 moves focus to the toast viewport for keyboard users
- • Toasts can be closed with Escape or swiped on touch devices