Toast
A self-dismissing message.
Anatomy
<script>
import { Toast } from '@shardsui/svelte/toast'
</script>
<Toast.Provider>
<Toast.Portal>
<Toast.Viewport>
<!-- Stacked toasts -->
<Toast.Root {toast}>
<Toast.Content>
<Toast.Title />
<Toast.Description />
<Toast.Action />
<Toast.Close />
</Toast.Content>
</Toast.Root>
<!-- Anchored toasts -->
<Toast.Positioner {toast}>
<Toast.Root {toast}>
<Toast.Arrow />
<Toast.Content>
<Toast.Title />
<Toast.Description />
<Toast.Action />
<Toast.Close />
</Toast.Content>
</Toast.Root>
</Toast.Positioner>
</Toast.Viewport>
</Toast.Portal>
</Toast.Provider>Usage guidelines
- Mount one provider near the root: toasts belong to the
<Toast.Provider>that added them, so a second provider keeps a separate queue and viewport. Use more than one only when you want those queues kept apart. - Rename the viewport if "Notifications" doesn't fit: the viewport is a labelled landmark, which is what lets F6 jump focus to it from anywhere. Pass your own
aria-labelto<Toast.Viewport>to change the name screen readers announce. - Exempt your own interactive elements from swiping:
button,a,input,textareaand[role="button"]never start a swipe-to-dismiss. Adddata-shards-ui-swipe-ignoreto anything else that shouldn't.
Global manager
Create a global manager with new Toast.Manager() and pass it to <Toast.Provider>. Any part of the app, including code outside the component tree, can then queue a toast that renders through the same viewport.
The toastManager exposes add, close, update, promise, and subscribe. For a reactive list of the current toasts, read Toast.getToastManager().toasts inside a <Toast.Provider>.
<script>
import { Toast } from '@shardsui/svelte/toast'
const toastManager = new Toast.Manager()
</script>
<Toast.Provider {toastManager}>
{#each Toast.getToastManager().toasts as toast (toast.id)}
<!-- … -->
{/each}
</Toast.Provider>Stacking and animations
Read --toast-index to set each toast's stacking order; index 0 sits at the front.
.toast {
z-index: calc(1000 - var(--toast-index));
transform: scale(calc(1 - 0.1 * var(--toast-index)));
}--toast-offset-y gives each toast its vertical offset when toasts are positioned absolutely and translated apart. Pair it with the data-expanded attribute (present while the viewport is hovered or focused) to spread the stack open.
.toast[data-expanded] {
transform: translateY(var(--toast-offset-y));
}While the stack is collapsed, clamp every toast's height to the frontmost toast with --toast-frontmost-height, and let <Toast.Content> hide the content of the toasts behind it. The data-behind attribute flags content behind the frontmost toast; combine it with data-expanded so it fades back in when the viewport expands:
.toast {
height: var(--toast-frontmost-height, var(--toast-height));
}
.toast-content {
overflow: hidden;
transition: opacity 0.25s;
}
.toast-content[data-behind] {
opacity: 0;
}
.toast-content[data-expanded] {
opacity: 1;
}--toast-swipe-movement-x and --toast-swipe-movement-y track how far the current swipe has moved; translate the toast by them so it follows the pointer.
.toast {
transform: scale(calc(1 - 0.1 * var(--toast-index))) translateX(var(--toast-swipe-movement-x))
translateY(calc(var(--toast-swipe-movement-y) + (var(--toast-index) * -20%)));
}On dismissal, the data-swipe-direction attribute reports which way the toast was swiped; use it to fling the toast off-screen in the same direction.
&[data-ending-style] {
opacity: 0;
&[data-swipe-direction='up'] {
transform: translateY(calc(var(--toast-swipe-movement-y) - 150%));
}
&[data-swipe-direction='down'] {
transform: translateY(calc(var(--toast-swipe-movement-y) + 150%));
}
/* --offset-y derives locally from --toast-offset-y, --toast-index, and swipe movement */
&[data-swipe-direction='left'] {
transform: translateX(calc(var(--toast-swipe-movement-x) - 150%)) translateY(var(--offset-y));
}
&[data-swipe-direction='right'] {
transform: translateX(calc(var(--toast-swipe-movement-x) + 150%)) translateY(var(--offset-y));
}
}The data-limited attribute marks a toast that exceeded the limit option. Limited toasts stay mounted with the HTML inert attribute, so you can hide them outright or animate them differently from the visible stack.
updateKey increments every time a toast is updated or upserted; key an animation off it to replay an attention-grabbing effect. When a remount is acceptable, wrap the toast markup in a {#key} block keyed on it instead.
Examples
Anchored toasts
Anchor a toast to a specific element with <Toast.Positioner> and the positionerProps option passed when you add it. Useful for contextual feedback, like a transient "Copied" toast next to the button the user just clicked.
Render anchored toasts in their own <Toast.Provider>, separate from stacked ones. Give each provider its own global manager, and the two can be driven independently from anywhere in the app:
<script>
import { Toast } from '@shardsui/svelte/toast'
const anchoredToastManager = new Toast.Manager()
const stackedToastManager = new Toast.Manager()
</script>
<Toast.Provider toastManager={anchoredToastManager}>
<AnchoredToasts />
</Toast.Provider>
<Toast.Provider toastManager={stackedToastManager}>
<StackedToasts />
</Toast.Provider><script>
import { Toast } from '@shardsui/svelte/toast'
</script>
<Toast.Viewport>
{#each Toast.getToastManager().toasts as toast (toast.id)}
<Toast.Positioner {toast}>
<Toast.Root {toast}><!-- … --></Toast.Root>
</Toast.Positioner>
{/each}
</Toast.Viewport>Pass positionerProps when adding the toast. Its type is ToastManagerPositionerProps — the anchor-positioning props Toast.Positioner accepts:
<script>
let buttonEl = $state(null)
function onclick() {
anchoredToastManager.add({
description: 'Copied',
timeout: 1500,
positionerProps: {
anchor: buttonEl,
sideOffset: 10
}
})
}
</script>
<button bind:this={buttonEl} {onclick}>Copy</button>Custom position
Your CSS decides where toasts sit: adjust the Viewport and Root styles to move them. A reusable toast component could accept a data-position attribute and let CSS handle each placement variant. The demo places the stack at bottom-center:
Undo action
Pass the actionProps option when adding a toast to configure an action button inside it, such as an Undo button.
Promise
An async toast moves through loading, success, and error states; its type string reflects the current one, so you can style each state differently. Each state accepts a plain string or the same options object as the update method to configure that state's toast in full.
Custom
Attach arbitrary typed data, values or functions alike, to a toast through the data option.
Deduplicated toast
Upserting a toast by the same id bumps its updateKey, letting a custom renderer replay an animation. Below, alternating CSS animation names off updateKey keeps the toast mounted while re-triggering the pulse.
Varying heights
To stack toasts of different heights cleanly, clamp every toast's height to the frontmost one at index 0 with the --toast-frontmost-height CSS variable, while the data-behind attribute hides the content of the toasts behind it. Avoid sizing <Toast.Content> to the root's height (such as height: 100%) — resizing it alongside the root cancels the root's height transition.
API reference
Provider
Provides a context for creating and managing toasts. Doesn't render its own HTML element.
Portal
A portal that moves the viewport out to <body>, clear of ancestor clipping and stacking.
Renders a <div> element.
Viewport
A container viewport for toasts.
Renders a <div> element.
Root
Groups all parts of an individual toast.
Renders a <div> element.
Content
A container for the contents of a toast.
Renders a <div> element.
Title
A title that labels the toast.
Renders an <h2> element.
Description
Secondary text for the toast.
Can be used as the default message for the toast when no title is provided.
Renders a <p> element.
Action
Performs an action when clicked.
Renders a <button> element.
Close
Closes the toast when clicked.
Renders a <button> element.
Positioner
Positions the toast against the anchor.
Renders a <div> element.
Props can also be passed via toast.positionerProps when calling toastManager.add().
Arrow
Displays an element positioned against the anchor.
Renders a <div> element.
Toast.getToastManager
Manages toasts; call it inside a <Toast.Provider>.
<script>
import { Toast } from '@shardsui/svelte/toast'
const toastManager = Toast.getToastManager()
</script>Returns { toasts, add, close, update, promise }.
add method
Adds a toast to the list and returns its toastId, which you can later hand to update or close. Reuse an existing id and the matching toast is updated in place rather than duplicated.
const toastId = toastManager.add({
title: 'Hello',
description: 'Hello, world!'
})For high-priority toasts (priority: 'high'), screen readers announce the title and description strings through a hidden role="alert" live region. Other markup inside <Toast.Root>, including the <Toast.Title> and <Toast.Description> components, stays silent unless the user navigates into the toast viewport.
update method
Updates the toast with new options.
toastManager.update(toastId, {
description: 'New description'
})close method
Closes the toast, removing it from the toast list after any animations complete.
toastManager.close(toastId) // Close one
toastManager.close() // Close allpromise method
Creates an asynchronous toast with three possible states: loading, success, and error.
toastManager.promise(fetch('/api/data'), {
loading: 'Loading…',
success: (data) => `Loaded ${data.length} items`,
error: (err) => `Error: ${err.message}`
})