App-root toast provider that hosts the toast queue, viewport portal, and stacked/expanded visual mode — paired with useToast() (injectToast() in Angular) for imperative push/update/dismiss/promise APIs.
Six positions (top/bottom × left/right/center) with portal-rendered viewport.
Sonner-style stacked mode: collapsed cards peek behind the front, hover/focus expands the stack.
Configurable max simultaneous toasts; oldest are dropped when the queue exceeds the cap.
Surface-aware (auto / pixel / linear) — pixel surface adds an HP-bar tone accent to each toast.
Announced through two persistent live regions, polite and assertive; F8 moves focus to the toasts, and duration sets or turns off their auto-dismiss.
Status
stable
Category
feedback
Since
v1.8.0
API
import { PxlKitToastProvider } from '@pxlkit/ui-kit';
Prop
Type
Default
Description
children* (required)
React.ReactNode
—
The part of the app that shows toasts: useToast() works inside it.
Corner, or edge centre, of the screen the toasts appear at.
max
number
5
Maximum simultaneous toasts. Oldest is dropped if exceeded. Defaults to 5.
duration
number
4500
Auto-dismiss delay, in ms, of the toasts that set no duration of their own; 0 keeps them until dismissed. Defaults to 4500. A promise's error toast stays at least 6 s, unless this is 0.
hotkey
string | false
'F8'
Key that moves focus to the toasts, written like F8 or alt+t, or false for none. Defaults to F8; the viewport's accessible name tells it.
surface
'pixel' | 'linear'
—
Surface of the toasts; defaults to the nearest provider.
stacked
boolean
true
Sonner-style stacked-offset visual: toasts collapse into a small stack showing only the front card; hover expands them into a vertical list. Defaults to true.
stackVisible
number
2
How many additional cards peek behind the front when stacked. Defaults to 2.
Accessibility
WCAG target: 2.1 AA
region
status
alert
The viewport is a role="region" landmark named after its hotkey — "Notifications (F8)" by default; hotkey sets another key or, with false, none — and takes focus from it (tabindex="-1"), from where Tab reaches the toasts' buttons. The hotkey works in text fields too, since F8 types nothing. Two visually hidden live regions inside the viewport, there and empty before any toast, announce the toasts: each toast's title and message are written to role="status" — or role="alert" for critical tones (red, gold) and assertive toasts — when it is pushed and when an update changes its title, message or tone (a settled promise), as new content each time, so a repeated message is read again. The cards themselves are not live regions. Toasts dismiss themselves after the provider's duration (4.5 s; 0 keeps them until dismissed, WCAG 2.2.1), a promise's error toast after at least 6 s; a toast's countdown holds while it is hovered or focused, while the page is hidden and while the window is in the background. Hovering or focusing the viewport expands the stacked layout. When the toast holding focus leaves, focus moves to the dismiss button of the next toast, else the previous one, else back to the element it entered the viewport from.
Keyboard
Key
Action
When
F8
Move focus to the toast viewport, expanding stacked toasts.
Toasts on screen; another key, or none, with hotkey.
Tab
Move through the toasts' action and dismiss buttons, from the viewport or the page, expanding stacked toasts.
—
Shift+Tab
Move focus back through the toasts and out of the viewport, collapsing the stack.
—
Enter
Dismiss the toast; focus moves to the next toast's dismiss button, or the previous one's, or back to where it came from.
Dismiss button focused.
Usage
import React from 'react';
import { PxlKitToastProvider, useToast, PixelButton } from '@pxlkit/ui-kit';
function TriggerRow({ children }: { children: React.ReactNode }) {
return <div className="flex flex-wrap gap-2">{children}</div>;
}
function DefaultTriggers() {
const { toast } = useToast();
return (
<TriggerRow>
<PixelButton size="sm" onClick={() => toast({ title: 'Saved', message: 'Your changes were persisted.' })}>
Push toast
</PixelButton>
</TriggerRow>
);
}
export function Default() {
return (
<PxlKitToastProvider>
<DefaultTriggers />
</PxlKitToastProvider>
);
}
Examples
Default
import React from 'react';
import { PxlKitToastProvider, useToast, PixelButton } from '@pxlkit/ui-kit';
function TriggerRow({ children }: { children: React.ReactNode }) {
return <div className="flex flex-wrap gap-2">{children}</div>;
}
function DefaultTriggers() {
const { toast } = useToast();
return (
<TriggerRow>
<PixelButton size="sm" onClick={() => toast({ title: 'Saved', message: 'Your changes were persisted.' })}>
Push toast
</PixelButton>
</TriggerRow>
);
}
export function Default() {
return (
<PxlKitToastProvider>
<DefaultTriggers />
</PxlKitToastProvider>
);
}
Tones
import React from 'react';
import { PxlKitToastProvider, useToast, PixelButton } from '@pxlkit/ui-kit';
function TriggerRow({ children }: { children: React.ReactNode }) {
return <div className="flex flex-wrap gap-2">{children}</div>;
}
function ToneTriggers() {
const { toast } = useToast();
return (
<TriggerRow>
<PixelButton size="sm" tone="green" onClick={() => toast.success('Saved', 'Changes persisted.')}>
Success
</PixelButton>
<PixelButton size="sm" tone="cyan" onClick={() => toast.info('Heads up', 'New release available.')}>
Info
</PixelButton>
<PixelButton size="sm" tone="gold" onClick={() => toast.warning('Careful', 'Storage almost full.')}>
Warning
</PixelButton>
<PixelButton size="sm" tone="red" onClick={() => toast.error('Failed', 'Upload could not finish.')}>
Error
</PixelButton>
</TriggerRow>
);
}
export function Tones() {
return (
<PxlKitToastProvider>
<ToneTriggers />
</PxlKitToastProvider>
);
}
Bottom Right
import React from 'react';
import { PxlKitToastProvider, useToast, PixelButton } from '@pxlkit/ui-kit';
function TriggerRow({ children }: { children: React.ReactNode }) {
return <div className="flex flex-wrap gap-2">{children}</div>;
}
function PositionTriggers() {
const { toast } = useToast();
return (
<TriggerRow>
<PixelButton size="sm" onClick={() => toast({ title: 'Bottom-right toast' })}>
Push to bottom-right
</PixelButton>
</TriggerRow>
);
}
export function BottomRight() {
return (
<PxlKitToastProvider position="bottom-right">
<PositionTriggers />
</PxlKitToastProvider>
);
}
Top Center
import React from 'react';
import { PxlKitToastProvider, useToast, PixelButton } from '@pxlkit/ui-kit';
function TriggerRow({ children }: { children: React.ReactNode }) {
return <div className="flex flex-wrap gap-2">{children}</div>;
}
function PositionTriggers() {
const { toast } = useToast();
return (
<TriggerRow>
<PixelButton size="sm" onClick={() => toast({ title: 'Bottom-right toast' })}>
Push to bottom-right
</PixelButton>
</TriggerRow>
);
}
export function TopCenter() {
return (
<PxlKitToastProvider position="top-center">
<PositionTriggers />
</PxlKitToastProvider>
);
}
Stacked
import React from 'react';
import { PxlKitToastProvider, useToast, PixelButton } from '@pxlkit/ui-kit';
function TriggerRow({ children }: { children: React.ReactNode }) {
return <div className="flex flex-wrap gap-2">{children}</div>;
}
function StackedTriggers() {
const { toast } = useToast();
return (
<TriggerRow>
<PixelButton
size="sm"
onClick={() => {
toast({ title: 'First', message: 'Oldest of the stack.' });
toast({ title: 'Second', message: 'In the middle.' });
toast({ title: 'Third', message: 'Newest in front.' });
}}
>
Push three
</PixelButton>
</TriggerRow>
);
}
export function Stacked() {
return (
<PxlKitToastProvider stacked stackVisible={2}>
<StackedTriggers />
</PxlKitToastProvider>
);
}
Flat
import React from 'react';
import { PxlKitToastProvider, useToast, PixelButton } from '@pxlkit/ui-kit';
function TriggerRow({ children }: { children: React.ReactNode }) {
return <div className="flex flex-wrap gap-2">{children}</div>;
}
function StackedTriggers() {
const { toast } = useToast();
return (
<TriggerRow>
<PixelButton
size="sm"
onClick={() => {
toast({ title: 'First', message: 'Oldest of the stack.' });
toast({ title: 'Second', message: 'In the middle.' });
toast({ title: 'Third', message: 'Newest in front.' });
}}
>
Push three
</PixelButton>
</TriggerRow>
);
}
export function Flat() {
return (
<PxlKitToastProvider stacked={false}>
<StackedTriggers />
</PxlKitToastProvider>
);
}
Pixel Surface
import React from 'react';
import { PxlKitToastProvider, useToast, PixelButton } from '@pxlkit/ui-kit';
function TriggerRow({ children }: { children: React.ReactNode }) {
return <div className="flex flex-wrap gap-2">{children}</div>;
}
function SurfaceTriggers() {
const { toast } = useToast();
return (
<TriggerRow>
<PixelButton
size="sm"
onClick={() =>
toast({ title: 'Pixel surface', message: 'HP-bar accent on the left edge.', tone: 'cyan' })
}
>
Push pixel toast
</PixelButton>
</TriggerRow>
);
}
export function PixelSurface() {
return (
<PxlKitToastProvider surface="pixel">
<SurfaceTriggers />
</PxlKitToastProvider>
);
}
Linear Surface
import React from 'react';
import { PxlKitToastProvider, useToast, PixelButton } from '@pxlkit/ui-kit';
function TriggerRow({ children }: { children: React.ReactNode }) {
return <div className="flex flex-wrap gap-2">{children}</div>;
}
function SurfaceTriggers() {
const { toast } = useToast();
return (
<TriggerRow>
<PixelButton
size="sm"
onClick={() =>
toast({ title: 'Pixel surface', message: 'HP-bar accent on the left edge.', tone: 'cyan' })
}
>
Push pixel toast
</PixelButton>
</TriggerRow>
);
}
export function LinearSurface() {
return (
<PxlKitToastProvider surface="linear">
<SurfaceTriggers />
</PxlKitToastProvider>
);
}