Components
Toast
Non-interruptive and stackable notifications. They pop up like... toast.
How it works
Toasts are managed by a global container. Trigger them either completely with HTML (using invoker commands) or with JavaScript.
Structure lives in HTML via a default <template id="toast-template">. CSS owns the lifetime through attr(data-duration type(<time>)). JS only clones the template, fills the structural slots ([data-toast-title], [data-toast-description], [data-toast-icon],
[data-toast-close]) using textContent, and
removes the toast on animationend. Provide your own <template> with the same slot markers and reference it via data-template
on the trigger to override the default look.
HTML
Use commandfor="toast-manager" and command="--show-toast"
on a button. The data-title attribute will be used as the message.
<button class="button" commandfor="toast-manager" command="--show-toast" data-title="Default Notification"> Show Default Toast</button>JavaScript
This is arguably the most common way. After some kind of fetch you might want to display some kind of message.
Use the showToast helper or by dispatching a CommandEvent.
// Using the helperwindow.showToast({ title: 'Triggered from JS!', description: 'With an optional description', severity: 'success', duration: '3000ms'});
// Or using native CommandEventconst btn = document.createElement('button');btn.setAttribute('data-title', 'Triggered from JS!');btn.setAttribute('data-description', 'With an optional description');btn.setAttribute('data-severity', 'success');btn.setAttribute('data-duration', '3000ms');document.getElementById('toast-manager').dispatchEvent( new CommandEvent('command', { command: '--show-toast', source: btn }));<button class="button" id="js-trigger-html">Trigger from JS</button>
<script> function setupToastDemoHtml() { const btn = document.getElementById("js-trigger-html"); if (btn) { btn.addEventListener("click", () => { if ((window as any).showToast) { ; (window as any).showToast({ title: "Triggered from JS!", description: "With an optional description", severity: "success", duration: "3000ms", }); } }); } }
setupToastDemoHtml(); document.addEventListener("astro:after-swap", setupToastDemoHtml);</script>Severities
Use the data-severity attribute to change the appearance of the
toast.
<button class="button filled green" commandfor="toast-manager" command="--show-toast" data-title="Success!" data-severity="success"> Success</button>
<button class="button filled red" commandfor="toast-manager" command="--show-toast" data-title="Something went wrong" data-severity="critical"> Error</button>
<button class="button filled blue" commandfor="toast-manager" command="--show-toast" data-title="Did you know?" data-severity="info"> Info</button>Title + description
Use data-title for a single-line toast, or add data-description for a two-line toast with additional context.
<button class="button" commandfor="toast-manager" command="--show-toast" data-title="Title only"> Title only</button>
<button class="button" commandfor="toast-manager" command="--show-toast" data-title="Title with description" data-description="This is additional context information"> Title + description</button>Duration
Control how long the toast stays visible using data-duration.
Supports CSS time units such as ms or s.
<button class="button" commandfor="toast-manager" command="--show-toast" data-title="I disappear quickly" data-duration="1500ms"> 1.5s Toast</button>
<button class="button" commandfor="toast-manager" command="--show-toast" data-title="I stay for a while" data-duration="10s"> 10s Toast</button>Anatomy
1. Container (Manager)
2. Individual Toast Body
3. Content Area
4. Close Button (Optional)
API
| Type | Modifiers | Default | Description |
|---|---|---|---|
| Command | --show-toast | - | Custom invoker command to trigger a toast notification. |
| Message | value / textContent | - | The message to display in the toast. |
| Severity | data-severity | info | Modifies the toast appearance. Options: success, critical, info. |
| Duration | data-duration | 5000ms | Determines how long the toast remains visible. Supports CSS time units (ms, s). |
| Container | #toast-manager | - | The global fixed container that manages the stack of toasts. |
| Component | .toast | - | Individual notification element within the container. |
Browser support
See also the full browser support guide.
Installation
@layer components.extended { :where(#toast-manager) { background: none; block-size: auto; border: none; display: none; flex-direction: column-reverse; gap: var(--size-3); inline-size: auto; inset: auto var(--size-4) var(--size-4) auto; margin: 0; max-inline-size: calc(100vi - var(--size-8)); overflow: visible; padding: 0; pointer-events: none; position: fixed; z-index: 1000;
&:popover-open { display: flex; }
&::backdrop { display: none; }
/* Optional cap on visible toasts. Older toasts beyond this depth are hidden but still in the DOM so they animate cleanly when newer ones are removed. */ /* > .toast:nth-last-child(n + 6) { display: none; } */ }
:where(.toast) { --_anim-enter: 0.3s; --_anim-exit: 0.3s;
align-items: center; animation: toast-enter var(--_anim-enter) var(--ease-out-3) both, toast-hold attr(data-duration type(<time>), 5s) linear both, toast-exit var(--_anim-exit) var(--ease-in-3) both; animation-delay: 0s, var(--_anim-enter), calc(var(--_anim-enter) + attr(data-duration type(<time>), 5s)); background-color: light-dark(var(--gray-15), var(--gray-2)); border-radius: var(--border-radius); box-shadow: var(--shadow-3); color: var(--text-primary-contrast); display: flex; gap: var(--size-3); justify-content: space-between; min-inline-size: 30ch; padding: var(--size-3) var(--size-4); pointer-events: auto;
&:hover { animation-play-state: paused; }
&.exiting { animation: toast-exit var(--_anim-exit) var(--ease-in-3) forwards; }
.icon { background: no-repeat center / contain; block-size: var(--size-4); flex-shrink: 0; inline-size: var(--size-4); }
/* Hide the icon slot when there's no severity to render against. */ &:not([data-severity]) .icon { display: none; }
.content { font-size: var(--font-size-05); word-break: break-word; }
.title { font-weight: 600; }
.description { color: oklch(from currentColor l c h / 75%); font-size: var(--font-size-0); }
.close-button { background: none; border: none; color: inherit; cursor: pointer; display: grid; opacity: 0.7; padding: var(--size-1); place-items: center; transition: opacity 0.2s;
&:hover { opacity: 1; } }
/* Severity icons via CSS data URIs — keeps icon assets out of JS strings. */ &[data-severity="success"] .icon { background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%2322c55e' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M22 11.08V12a10 10 0 1 1-5.93-9.14'/><path d='m9 11 3 3L22 4'/></svg>"); }
&[data-severity="info"] .icon { background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%233b82f6' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='12' cy='12' r='10'/><path d='M12 16v-4'/><path d='M12 8h.01'/></svg>"); }
&[data-severity="warning"] .icon { background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%23f59e0b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3'/><path d='M12 9v4'/><path d='M12 17h.01'/></svg>"); }
&[data-severity="critical"] .icon { background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%23ef4444' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='12' cy='12' r='10'/><path d='m15 9-6 6'/><path d='m9 9 6 6'/></svg>"); } }
@keyframes toast-enter { from { opacity: 0; translate: var(--size-4) 0; }
to { opacity: 1; translate: 0 0; } }
/* Identity keyframe — only purpose is to delay the exit animation by the author-defined duration. */ @keyframes toast-hold { from { opacity: 1; }
to { opacity: 1; } }
@keyframes toast-exit { from { opacity: 1; scale: 1; translate: 0 0; }
to { opacity: 0; scale: 0.9; translate: 0 var(--size-4); } }}