Skip to content
ShineUI
Install

Toast

News that fades.

<sui-toast-host>FeedbackStable

About

Toast stack for the results of actions: a payment recorded, an export that failed. Render one host per page, or let the toast() util create it, and call push() with a severity, a title and a detail; each toast leaves on its own after six seconds. Reach for sui-message when the news must stay on the page.

Usage

<sui-toast-host position="top-right"></sui-toast-host> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); const el = document.querySelector("sui-toast-host"); el.addEventListener("suiAction", (e) => console.log(e.detail)); </script>
import { SuiToastHost } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { return <SuiToastHost position="top-right" onSuiAction={(e) => console.log(e.detail)} />; }
<script setup> import { SuiToastHost } from "@shineui/vue"; import "@shineui/core/shineui.css"; const onChange = (e) => console.log(e.detail); </script> <template> <SuiToastHost position="top-right" @sui-action="onChange" /> </template>
import { Component } from "@angular/core"; import { SuiToastHost } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiToastHost], template: `<sui-toast-host position="top-right" (suiAction)="onChange($event)"></sui-toast-host>`, }) export class Example { onChange(e: CustomEvent) { console.log(e.detail); } }

Basic

One host per page; three sticky toasts arrive on load and the buttons push one per severity. A toast is announced on arrival and never focused, so put every action it offers on the page too. Critical interrupts, the rest wait their turn. The X is the "Dismiss" icon button sui-dialog and sui-drawer also use.

<div style="display:flex;gap:8px;flex-wrap:wrap;"> <sui-button label="Record payment" data-sev="positive"></sui-button> <sui-button label="Review feed" data-sev="attention"></sui-button> <sui-button label="Export PDF" data-sev="critical"></sui-button> <sui-button label="Rates updated" data-sev="info"></sui-button> <sui-button label="New device" data-sev="neutral"></sui-button> <sui-button variant="ghost" label="Clear" data-clear></sui-button> </div> <sui-toast-host position="top-right"></sui-toast-host>
// setup(root): properties and events setup(root) { const host = root.querySelector('sui-toast-host'); const seed = [ { severity: 'positive', title: 'Payment recorded', detail: 'PMT-0921. $6,420.00 applied to INV-1043.', time: 'Just now', duration: 0 }, { severity: 'attention', title: '2 feed items to review', detail: 'Operating Checking has unmatched transactions.', action: 'Review', duration: 0 }, { severity: 'critical', title: 'Export failed', detail: 'The PDF service timed out. Try again.', time: '2m', duration: 0 }, ]; customElements.whenDefined('sui-toast-host').then(() => seed.forEach((t) => host.push(t))); root.querySelectorAll('sui-button[data-sev]').forEach((btn) => { btn.addEventListener('click', () => host.push(more[btn.dataset.sev])); }); root.querySelector('sui-button[data-clear]').addEventListener('click', () => host.clear()); }

Positions

position takes the four corners plus top-center, bottom-center and center. A top stack grows downward and a bottom stack upward, so the newest toast is always the one nearest the middle of the screen; center grows both ways and stays centered.

<div style="display:flex;gap:8px;flex-wrap:wrap;"> <sui-button id="tst-top-center" variant="outline" label="Push to top center"></sui-button> <sui-button id="tst-bottom-center" variant="outline" label="Push to bottom center"></sui-button> <sui-button id="tst-center" variant="outline" label="Push to center"></sui-button> </div> <sui-toast-host name="top" position="top-center"></sui-toast-host> <sui-toast-host name="bottom" position="bottom-center"></sui-toast-host> <sui-toast-host name="middle" position="center"></sui-toast-host>
// setup(root): properties and events setup(root) { const push = (name, opts) => root.querySelector(`sui-toast-host[name="${name}"]`).push(opts); root.querySelector('#tst-top-center').addEventListener('click', () => { push('top', { severity: 'positive', title: 'Draft saved', detail: 'Top center, growing downward.', duration: 4000 }); }); root.querySelector('#tst-bottom-center').addEventListener('click', () => { push('bottom', { severity: 'info', title: 'Sync finished', detail: 'Bottom center, growing upward.', duration: 4000 }); }); root.querySelector('#tst-center').addEventListener('click', () => { push('middle', { severity: 'attention', title: 'Period closes tonight', detail: 'Center of the viewport, growing both ways.', action: 'Review', duration: 4000 }); }); }

Timers and dismiss

duration is milliseconds and defaults to 6000; 0 keeps a toast until the X or dismiss(id). Hovering a timed toast or tabbing into it holds the countdown until both the pointer and focus have left. The export button pushes a sticky toast, dismisses it by id and pushes the result, the way a long request reports back.

<div style="display:flex;gap:8px;flex-wrap:wrap;"> <sui-button id="tst-short" variant="outline" label="Push a 4 second toast"></sui-button> <sui-button id="tst-sticky" variant="outline" label="Push a sticky toast"></sui-button> <sui-button id="tst-promise" variant="outline" label="Export, then report"></sui-button> </div> <sui-toast-host position="bottom-right"></sui-toast-host>
// setup(root): properties and events setup(root) { const host = root.querySelector('sui-toast-host'); root.querySelector('#tst-short').addEventListener('click', () => { host.push({ severity: 'positive', title: 'Draft saved', detail: 'Hover or Tab into this toast to hold the timer.', action: 'Undo', duration: 4000 }); }); root.querySelector('#tst-sticky').addEventListener('click', () => { host.push({ severity: 'attention', title: 'Reconciliation is due Friday', detail: 'duration 0, so it waits for the X.', duration: 0 }); }); root.querySelector('#tst-promise').addEventListener('click', async () => { const id = await host.push({ severity: 'info', title: 'Exporting PDF', detail: 'This toast stays until the export settles.', duration: 0 }); setTimeout(() => { host.dismiss(id); host.push({ severity: 'positive', title: 'Export ready', detail: 'INV-1043.pdf is in your downloads.', time: 'Just now' }); }, 1800); }); }

A cap on the stack

max is how many toasts show at once, five unless set. A push past it waits in a queue and enters as an earlier toast leaves, in the order pushed, with its timer starting then; a keyboard dismiss moves focus to the next toast's X, then the previous one's, then back to where it came from.

<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;"> <sui-button id="tst-burst" variant="outline" label="Push six saves"></sui-button> <sui-button id="tst-burst-clear" variant="ghost" label="Clear"></sui-button> </div> <sui-toast-host name="capped" max="3" position="bottom-right"></sui-toast-host>
// setup(root): properties and events setup(root) { const host = root.querySelector('sui-toast-host[name="capped"]'); root.querySelector('#tst-burst').addEventListener('click', () => { for (let i = 1; i <= 6; i++) host.push({ severity: 'positive', title: `Saved ${i} of 6`, detail: i <= 3 ? 'Showing now.' : 'Queued until a slot opens.', duration: 0 }); }); root.querySelector('#tst-burst-clear').addEventListener('click', () => host.clear()); }

Named hosts and events

name separates two stacks on one page. It reflects, so a name set as a property still answers document.querySelector('sui-toast-host[name="ledger"]') and toast()'s second argument. suiRemove fires whenever a toast leaves: timer, X, dismiss(id) or clear(). suiAction fires on the action link and the toast stands.

Idle.
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;"> <sui-button id="tst-to-shell" variant="outline" label="Push to shell"></sui-button> <sui-button id="tst-to-ledger" variant="outline" label="Push to ledger"></sui-button> <span id="tst-log" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted);">Idle.</span> </div> <sui-toast-host name="shell" position="top-left"></sui-toast-host> <sui-toast-host name="ledger" position="bottom-left"></sui-toast-host>
// setup(root): properties and events setup(root) { const log = root.querySelector('#tst-log'); const shell = root.querySelector('sui-toast-host[name="shell"]'); const ledger = root.querySelector('sui-toast-host[name="ledger"]'); [shell, ledger].forEach((host) => { host.addEventListener('suiRemove', (e) => { log.textContent = `suiRemove from ${host.name}: ${e.detail.title}.`; }); host.addEventListener('suiAction', (e) => { log.textContent = `suiAction from ${host.name}: ${e.detail.action}. The toast stays.`; }); }); root.querySelector('#tst-to-shell').addEventListener('click', () => { shell.push({ severity: 'info', title: 'Session extended', detail: 'Routed to the shell stack.', duration: 0 }); }); root.querySelector('#tst-to-ledger').addEventListener('click', () => { ledger.push({ severity: 'positive', title: 'Entry posted', detail: 'JE-0331 routed to the ledger stack.', action: 'View entry', duration: 0 }); }); }

API

<sui-toast-host>

Properties

PropertyAttributeTypeDefaultDescription
maxmaxnumber5How many toasts show at once. A push past the cap waits in a queue and enters as an earlier toast leaves, in the order pushed, so twelve saves in a row never stack past the fold; its timer starts when it shows. Zero shows everything at once.
namenamestringstring | undefinedNames this stack so a page with more than one host can target the right one, e.g. document.querySelector('sui-toast-host[name="shell"]').push(...). Reflected, so the attribute selector still finds the host when a framework binding sets the name as a property (el.name = 'shell') rather than writing it in markup.
positionpositionSuiToastPosition"bottom-center" | "bottom-left" | "bottom-right" | "center" | "top-center" | "top-left" | "top-right"'top-right'Where the stack sits: a corner, the middle of the top or bottom edge, or the center of the viewport. A top stack grows downward and a bottom stack grows upward, so the newest toast is always the one nearest the middle of the screen; center grows both ways and stays centered. The corner names are sides of the screen in either reading direction; inside a toast the severity edge and the icon lead and the X trails. An unrecognised value falls back to top-right. While toasts show, the stack paints in the browser's top layer, above dialogs and every other surface but tooltips, so it answers to the viewport even inside a transformed or clipping box. The live regions that announce the toasts stay where the element is, and stay live while a modal is open; Tab reaches the stack from the modal's last control and goes back.

Events

EventDetailReactVueDescription
suiActionSuiToastItemonSuiAction@sui-actionFires when a toast's action link is clicked. Detail is the toast; the toast stays until dismissed.
suiRemoveSuiToastItemonSuiRemove@sui-removeFires when a toast leaves the stack (timer, the X, dismiss() or clear()); detail is the toast. It fires as the toast starts its short fade out, so a listener that reads the stack sees the toast gone from items one fade later. It is suiRemove, as on sui-chip and sui-file-upload, because one item leaving a collection is what it reports; the stack itself never closes, and every suiClose in the library carries a SuiCloseReason.

Methods

MethodSignatureDescription
clear()clear() => Promise<void>Removes every toast, queued ones included, emitting suiRemove for each.
dismiss()dismiss(id: number) => Promise<void>Removes one toast by id and emits suiRemove for it, whether it is showing or still queued.
push()push(opts: SuiToastOptions) => Promise<number>Adds a toast and resolves with its id. Options: severity on the shared ladder (positive, attention, critical, info, neutral; default info), title, detail, action (a button label that emits suiAction), time (a short stamp) and duration in ms (default 6000; 0 keeps it until dismissed). The timer holds while the toast is hovered or holds focus. A toast is announced once, when it shows: its title and detail go to a polite live region, or, for a critical toast, to an assertive one that interrupts. The two regions sit side by side beside the stack, never one inside the other, so no screen reader reads a toast twice, and each line leaves its region a second later, so a reader browsing the page meets the toast once. A toast is never focused, so put every action it offers on the page as well. The action is a native button; the X is the small sui-icon-button labeled "Dismiss" that sui-dialog, sui-drawer and sui-message also use.

Types

SuiSeverity type
No members.
SuiToastItem interfaceA queued toast: the pushed options plus the id push() returned.
idnumber
titlestring
detail?string
severity?SuiToastSeverity
time?stringSmall time stamp shown beside the title, e.g. "Just now".
action?stringOptional action label rendered as a link-style button; the host emits suiAction with the toast.
duration?numberAuto-dismiss in ms. 0 keeps it until dismissed. Default 6000.
SuiToastOptions interface
titlestring
detail?string
severity?SuiToastSeverity
time?stringSmall time stamp shown beside the title, e.g. "Just now".
action?stringOptional action label rendered as a link-style button; the host emits suiAction with the toast.
duration?numberAuto-dismiss in ms. 0 keeps it until dismissed. Default 6000.
SuiToastPosition typeWhere a stack sits: the four corners, the middle of the top or bottom edge, or dead center.
No members.
SuiToastSeverity typeThe severity a toast can carry: the shared status ladder.
No members.

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

See also