Toast
A self-dismissing message.
Anatomy
<script setup>
import { Toast } from '@shardsui/vue/toast'
</script>
<template>
<Toast.Provider>
<Toast.Portal>
<Toast.Viewport>
<!-- Stacked toasts -->
<Toast.Root :toast="toast">
<Toast.Content>
<Toast.Title />
<Toast.Description />
<Toast.Action />
<Toast.Close />
</Toast.Content>
</Toast.Root>
<!-- Anchored toasts -->
<Toast.Positioner :toast="toast">
<Toast.Root :toast="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>
</template>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 Toast.createManager() 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 the toasts slot prop of <Toast.Provider>, or call Toast.getToastManager().toasts from a component rendered inside it.
<script setup>
import { Toast } from '@shardsui/vue/toast'
const toastManager = Toast.createManager()
</script>
<template>
<Toast.Provider v-slot="{ toasts }" :toast-manager="toastManager">
<template v-for="toast in toasts" :key="toast.id">
<!-- … -->
</template>
</Toast.Provider>
</template>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 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. Combine data-behind 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, use data-swipe-direction to fling the toast off-screen in the direction it was swiped.
&[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));
}
}A toast that exceeds the limit option gets data-limited and stays mounted with the HTML inert attribute, so you can hide it outright or animate it differently from the visible stack.
updateKey increments every time a toast is updated or upserted; key an animation off it to replay an effect. When a remount is acceptable, put it in the :key of the toast markup 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 setup>
import { Toast } from '@shardsui/vue/toast'
const anchoredToastManager = Toast.createManager()
const stackedToastManager = Toast.createManager()
</script>
<template>
<Toast.Provider :toast-manager="anchoredToastManager">
<AnchoredToasts />
</Toast.Provider>
<Toast.Provider :toast-manager="stackedToastManager">
<StackedToasts />
</Toast.Provider>
</template><script setup>
import { Toast } from '@shardsui/vue/toast'
const toastManager = Toast.getToastManager()
</script>
<template>
<Toast.Viewport>
<Toast.Positioner v-for="toast in toastManager.toasts" :key="toast.id" :toast="toast">
<Toast.Root :toast="toast"><!-- … --></Toast.Root>
</Toast.Positioner>
</Toast.Viewport>
</template>Pass positionerProps when adding the toast. Its type is ToastManagerPositionerProps — the anchor-positioning props Toast.Positioner accepts:
<script setup>
import { useTemplateRef } from 'vue'
const button = useTemplateRef('button')
function copy() {
anchoredToastManager.add({
description: 'Copied',
timeout: 1500,
positionerProps: {
anchor: button.value,
sideOffset: 10
}
})
}
</script>
<template>
<button ref="button" @click="copy">Copy</button>
</template>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.
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
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 the toast queue to the parts beneath it. 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 setup>
import { Toast } from '@shardsui/vue/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}`
})