motif svelteArc

Toast Stack ported

Every result in one calm, stacked corner.

Stack short results at the edge until you reach for them.

Live specimen

Loading demo…

Guidance

When to use

  • For short, non-blocking results of what someone just did.
  • When several results can arrive at once and must queue.
  • When a result needs an action (Undo) or a type morph (loading into success).

When not to use

  • For a single one-off confirmation — use toast.
  • For persistent, in-flow messages — use alert.
  • When people must respond before continuing — use dialog.

Installation

pnpm add sveltearc

Usage

<script lang="ts">
	import ToastStackProvider from '$lib/components/toast-stack/toast-stack-provider.svelte';
	import ToastStack from '$lib/components/toast-stack/toast-stack.svelte';
	import { useToastStack } from '$lib/components/toast-stack/toast-stack-store.svelte';

	function save() {
		const { toast, update } = useToastStack();
		const id = toast({ type: 'loading', title: 'Saving…' });
		persist().then(() => update(id, { type: 'success', title: 'Saved' }));
	}
</script>

<ToastStackProvider>
	<button onclick={save}>Save</button>
	<ToastStack />
</ToastStackProvider>

Variants & Examples

Types

Success, info, warning, error and loading toasts.

<button onclick={() => store.toast({ type: 'success', title: 'Deployed' })}>Ship</button>
<button onclick={() => store.toast({ type: 'error', title: 'Build failed', description: 'See the log.' })}>Fail</button>

With action

An Undo action that morphs the toast on click.

<button onclick={() => store.toast({ title: 'Deleted', action: { label: 'Undo', onClick: (id) => restore(id) } })}>Delete</button>

Update in place

A loading toast that morphs into its result under a stable id.

<button onclick={() => { const id = store.toast({ id: 'save', type: 'loading', title: 'Saving…' }); finish().then(() => store.update(id, { type: 'success', title: 'Saved' })); }}>Save</button>

API Reference

PropTypeDefaultDescription
childrenSnippet—Provider: the subtree the toast queue is scoped to.
durationnumber5000Provider: base lifetime in milliseconds. Warnings and errors stay 1.6 times longer.
limitnumber12Provider: oldest toasts beyond this count are dropped from the queue.
labelstring'Notifications'Viewport: accessible name of the notification region.
position'bottom-right' | 'bottom-center' | 'bottom-left''bottom-right'Viewport: which bottom corner (or center) the stack anchors to.
containedbooleanfalseViewport: pin the stack inside the nearest positioned ancestor instead of the window.
visibleToastsnumber3Viewport: how many toasts show at once. Older ones wait behind.
hotkeybooleantrueViewport: Alt+T moves focus into the stack.
classstring—Viewport: additional classes merged with the viewport class.
toast(options: ToastOptions) => string—API: shows a toast and returns its id. Reuse an id to update in place.
update(id, patch) => void—API: morphs a toast in place and restarts its timer.
dismiss(id?) => void—API: dismisses one toast, or every toast when called without an id.

Keyboard Interactions

KeyAction
Alt+TMove focus into the stack.
EscapeDismiss the focused toast.
Enter / SpaceRun the toast action or dismiss from its buttons.

Accessibility

  • The region is aria-live="polite" with aria-relevant="additions text"; each card is role="status".
  • The type is announced as a screen-reader prefix ("Success: …"), not colour alone.
  • Toasts beyond the visible count are inert; keyboard dismissal hands focus to the next toast.

Motion

Phase 1 still-state: no animation. layoutStack() geometry is applied statically; toasts appear, fan out on hover/focus/tap, and dismiss instantly. Phase 2 wires the enter rise, the stack springs, the icon/text/action swaps and the swipe-to-dismiss.

Notes for AI

  • Render ToastStackProvider once and ToastStack once inside it; call useToastStack() below the provider.
  • Reuse a toast id to morph it in place instead of stacking a new one.

Source of truth: registry/components/toast-stack/toast-stack.tsx