You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/soma/components/toast
dev 2956895e04
Add toast provider coverage
5 months ago
..
components uix: drawer/popover/toast/accordion eidos wrappers + sema packs + /uix docs scaffold 5 months ago
README.md Refactor sema and add shared dom runtime 6 months ago
exports.ts Tighten uix audit contracts 5 months ago
index.ts soma: 10 new components, README docs for all 22, PrimitiveAnchorAttributes 6 months ago
langs.ts soma: gesture layer, drawer component, full audit fixes across 26 components 6 months ago
toast-provider.svelte.test.ts Add toast provider coverage 5 months ago
toast-provider.svelte.ts Remove global browser guards from Soma runtime paths 5 months ago
toaster.svelte.test.ts Expand Soma component coverage 5 months ago
toaster.svelte.ts Tighten uix audit contracts 5 months ago
types.ts uix: drawer/popover/toast/accordion eidos wrappers + sema packs + /uix docs scaffold 5 months ago

README.md

Toast

Notification messages that appear temporarily. Managed imperatively via createToaster(). Supports auto-dismiss, pause on hover/focus, swipe-to-dismiss, keyboard hotkey, promise tracking, and ARIA live regions.

Anatomy

<script>
	import { Toast, createToaster } from '$soma/components/toast';

	const toaster = createToaster({ duration: 5000 });
</script>

<Toast.Provider {toaster}>
	<Toast.Viewport>
		{#each toaster.toasts as t (t.id)}
			<Toast.Item toast={t}>
				<Toast.Title>{t.title}</Toast.Title>
				<Toast.Description>{t.description}</Toast.Description>
				<Toast.Action altText="Undo action" onclick={t.action?.onClick}>
					{t.action?.label}
				</Toast.Action>
				<Toast.Close>x</Toast.Close>
			</Toast.Item>
		{/each}
	</Toast.Viewport>
</Toast.Provider>

Setup the Provider once in a layout. Create toasts from anywhere via the toaster instance.

Parts

Part Element Description
Provider none Root context. Receives the toaster instance. No DOM.
Viewport <div> Positioned container for all toasts. ARIA region. Hotkey target.
Item <div> Individual toast. Auto-dismiss timer, pause on hover/focus, swipe.
Title <div> Toast heading. Linked via aria-labelledby.
Description <div> Toast body text. Linked via aria-describedby.
Action <button> Interactive action button with accessible description.
Close <button> Dismiss button.

Imperative API

const toaster = createToaster({
	duration: 5000, // default auto-dismiss (ms), 0 for none
	max: 5, // max visible toasts
	swipeDirection: 'right', // 'left' | 'right' | 'up' | 'down'
	swipeThreshold: 50, // px distance to trigger dismiss
	hotkey: ['F8'] // keyboard shortcut to focus viewport
});

// Create toasts by semantic intent
const id = toaster.create({ title: 'Hello' });
toaster.create({ title: 'Saved', description: 'Changes saved.', intent: 'fulfill' });
toaster.create({ title: 'Something needs attention', intent: 'risk' });
toaster.create({ title: 'Something went wrong', intent: 'threat' });
toaster.create({ title: 'Uploading...', loading: true }); // persistent by default (duration: 0)

// Manage toasts
toaster.dismiss(id); // dismiss one
toaster.dismiss(); // dismiss all
toaster.update(id, { title: 'Updated' });

// Toast with action
toaster.create({
	title: 'File deleted',
	action: { label: 'Undo', onClick: () => restore() }
});

// Toast with custom duration
toaster.create({ title: 'Quick', duration: 2000 });
toaster.create({ title: 'Persistent', duration: 0 }); // no auto-dismiss

// Promise tracking — loading → fulfill/threat
toaster.promise(fetchData(), {
	loading: { title: 'Loading...', description: 'Fetching data.' },
	fulfill: (data) => ({ title: 'Done', description: `Loaded ${data.count} items.` }),
	threat: (err) => ({ title: 'Failed', description: String(err) })
});

ARIA

Part Attribute Value
Viewport role region
Viewport aria-label Configurable (default "Notifications")
Item role status (neutral/affirm/fulfill) | alert (risk/threat)
Item aria-live polite (neutral/affirm/fulfill) | assertive (risk/threat)
Item aria-atomic true
Item aria-labelledby ID of Title
Item aria-describedby ID of Description
Action aria-label altText prop value
Close aria-label Translated "Close"

Risk and threat toasts use role="alert" with aria-live="assertive" for immediate screen reader announcement. Other intents use role="status" with aria-live="polite".

Data Attributes

Provider is pure context — it emits no DOM, so it has no data-* attribute.

Part Attribute Values
Viewport data-toast-viewport Always present
Item data-toast-item Always present
Item data-state open | closed
Item data-intent neutral | affirm | fulfill | risk | threat
Item data-loading Present while the toast is in loading/pending phase
Item data-swipe start | move | cancel | end (during swipe gesture)
Item data-swipe-direction left | right | up | down
Item data-starting-style Present during open animation
Item data-ending-style Present during close animation
Title data-toast-title Always present
Description data-toast-description Always present
Action data-toast-action Always present
Close data-toast-close Always present

CSS Variables

Variable Part Description
--soma-toast-swipe-move-x Item Horizontal swipe displacement in px
--soma-toast-swipe-move-y Item Vertical swipe displacement in px
--soma-toast-swipe-end-x Item Final horizontal position on dismiss (e.g. 100%)
--soma-toast-swipe-end-y Item Final vertical position on dismiss (e.g. 100%)

Keyboard

Key Action
F8 (default hotkey) Focus the toast viewport
Tab Navigate between action/close buttons within a toast

The hotkey is configurable via createToaster({ hotkey: ['F8'] }).

Behavior

Auto-dismiss

  • Default duration: 5000ms (configurable per toaster and per toast)
  • Set duration: 0 on a toast to disable auto-dismiss
  • loading: true defaults to duration: 0 (persistent until dismissed)
  • Timer pauses on hover (pointerenter) and focus
  • Timer resumes on pointerleave and blur

Swipe to dismiss

  • Swipe direction configurable: left, right, up, down (default right)
  • Threshold configurable (default 50px)
  • During swipe: data-swipe="move" + CSS variables for position
  • On cancel: data-swipe="cancel", position resets
  • On dismiss: data-swipe="end", toast removed

Max toasts

  • When max is exceeded, the oldest toast is dismissed automatically
  • Dismissed toast's onDismiss callback is called

Promise tracking

  • toaster.promise(promise, { loading, fulfill, threat }) creates a loading toast
  • When the promise resolves, the toast updates to intent='fulfill'
  • When the promise rejects, the toast updates to intent='threat'
  • fulfill/threat options can be functions receiving the resolved value or error

Usage

Setup in layout

<!-- +layout.svelte -->
<script>
	import { Toast, createToaster } from '$soma/components/toast';
	import { setContext } from 'svelte';

	const toaster = createToaster({ duration: 5000, max: 5 });
	setContext('toaster', toaster);
</script>

<Toast.Provider {toaster}>
	<slot />

	<Toast.Viewport>
		{#each toaster.toasts as t (t.id)}
			<Toast.Item toast={t}>
				<Toast.Title>{t.title}</Toast.Title>
				{#if t.description}
					<Toast.Description>{t.description}</Toast.Description>
				{/if}
				<Toast.Close>x</Toast.Close>
			</Toast.Item>
		{/each}
	</Toast.Viewport>
</Toast.Provider>

Create toast from any page

<script>
	import { getContext } from 'svelte';

	const toaster = getContext('toaster');
</script>

<button onclick={() => toaster.create({ title: 'Saved!', intent: 'fulfill' })}> Save </button>

Promise tracking

<button
	onclick={() => {
		toaster.promise(saveData(), {
			loading: { title: 'Saving...' },
			fulfill: () => ({ title: 'Saved!' }),
			threat: (e) => ({ title: 'Failed', description: e.message })
		});
	}}
>
	Save
</button>

Swipe animation CSS

[data-toast-item] {
	transition: transform 200ms ease;
}
[data-toast-item][data-swipe='move'] {
	transform: translateX(var(--soma-toast-swipe-move-x));
	transition: none;
}
[data-toast-item][data-swipe='cancel'] {
	transform: translateX(0);
}

Comparison with reference libraries

Feature Radix Ark Sonner Soma
API style Declarative Imperative Imperative Imperative
Semantic variants foreground/background success/error/warning/info success/error/etc intent + loading state
Auto-dismiss Yes Yes Yes Yes
Pause on hover Yes Yes Yes Yes
Swipe to dismiss Yes Yes Yes Yes
Swipe CSS vars Yes No No Yes
Max toasts Manual Built-in Built-in Built-in
Hotkey F8 No Alt+T F8 (configurable)
Promise tracking No Yes Yes Yes
Loading state No No Yes Yes
ARIA urgency Manual Auto by type Auto Auto by intent
Queue management Manual Built-in Built-in Built-in

Powered by TurnKey Linux.