Toast
A transient notification, fired imperatively from anywhere on the page.
Last updated
Uses JavaScript
Mounts the toast host (Toaster) and defines window.apogeeToast(message, options).
Calling it clones a <template>, fills in the icon/title/message, and appends it
to the host — stacked, auto-dismissing, dismissible by a close button.
Without JS: apogeeToast is never defined — nothing fires, and no notification appears. Don't make Toast the only feedback for an action; pair it with a non-JS path (redirect, inline message) where that matters.
Place one <Toaster /> on the page (commonly in the base layout), then fire
toasts from anywhere:
Show codeHide code
<Toaster />
<button id="toast-info">Show info</button>
<button id="toast-success">Show success</button>
<button id="toast-warning">Show warning</button>
<button id="toast-danger">Show danger</button>
<script>
document.getElementById('toast-info')?.addEventListener('click', () =>
apogeeToast('Changes are saved automatically.', { color: 'primary', title: 'Heads up' }))
document.getElementById('toast-success')?.addEventListener('click', () =>
apogeeToast('Your changes have been saved.', { color: 'positive', title: 'Saved' }))
document.getElementById('toast-warning')?.addEventListener('click', () =>
apogeeToast('Your session expires in 5 minutes.', { color: 'warning', title: 'Heads up' }))
document.getElementById('toast-danger')?.addEventListener('click', () =>
apogeeToast('Could not save your changes.', { color: 'critical', title: 'Error' }))
</script>The <Toaster /> mounted above stays for the rest of this page — every button
below fires into that same instance.
Add
npx @ava-ui/apogee add toast
Bring @lucide/astro for the status icons (Info, CircleCheck,
TriangleAlert, CircleAlert). No other Apogee dependencies. Requires
JavaScript — a toast is inherently imperative, so there’s no no-JS fallback
markup to fall back to.
Usage
---
import Toaster from '@/components/apogee/Toast/Toaster.astro'
---
<!-- once, e.g. in your base layout -->
<Toaster />
// anywhere, client-side
apogeeToast('Saved', { color: 'positive', title: 'Done' })
Mount exactly one <Toaster /> per page — its region, template, and icon
set use fixed element ids, so a second instance won’t get its own toasts.
Anatomy
Toast has two pieces. Individual toasts aren’t slots — the script clones a
<template> and fills it in per call:
- Toaster — the fixed, positioned host region (
role="region") that toasts are appended to. - Toast — icon (by variant) + optional title + message + close button.
API
@composable — exposed to Content Editors in AVA. Any other attribute passes
through to the Toaster root.
<Toaster> props
| Prop | Type | Default | Description |
|---|---|---|---|
id |
string |
'apogee-toaster' |
Host id. Mount one Toaster per page; the script binds to the first. |
position |
'top-start' | 'top-center' | 'top-end' | 'bottom-start' | 'bottom-center' | 'bottom-end' |
'bottom-end' |
Screen corner, in logical terms: end is the right in LTR and the left in RTL. |
class |
string |
— | Extra classes on the host, merged last. |
apogeeToast(message, options)
The imperative function Toaster defines on window, not an Astro prop —
call it after the Toaster has mounted.
| Argument | Type | Default | Description |
|---|---|---|---|
message |
string |
— | Toast body text. |
options.color |
'primary' | 'neutral' | 'positive' | 'warning' | 'critical' |
'primary' |
Icon + border tint. warning and critical use role="alert"; the others use role="status". |
options.title |
string |
— | Bold title line above the message. Omitted entirely if not passed. |
options.duration |
number (ms) |
4000 |
Auto-dismiss delay. 0 makes it sticky (stays until closed). |
options.position |
'top-start' | 'top-center' | 'top-end' | 'bottom-start' | 'bottom-center' | 'bottom-end' |
the host’s | Corner for this toast; a region per corner is created on first use. |
options.actions |
{ label: string; onClick?: () => void; href?: string }[] |
— | Small ghost buttons under the message. A click runs onClick (or follows href), then dismisses the toast. |
options.width |
'short' | 'long' | string |
'short' (20rem) |
Toast width: long is 25rem; any CSS length works. |
options.featured |
boolean |
false |
The colour icon in a FeaturedIcon tile. |
options.avatar |
{ src?: string; name: string; status?: 'online' | 'offline' } |
— | An avatar in place of the icon; the title line becomes the name. |
options.time |
string |
— | Shown after the title (“Ana Lima · 2 min ago”). |
options.progress |
number (0–100) |
— | A progress bar under the message. The toast stays until progress reaches 100, then its timer starts. |
The timer pauses while the pointer or keyboard focus is on the toast and resumes when it leaves. apogeeToast() returns an id; apogeeToast.dismiss(id) closes that toast early and apogeeToast.update(id, { message?, title?, progress? }) changes an open one (a no-op once it is gone).
Show codeHide code
<button onclick="apogeeToast('Top start', { position: 'top-start' })">Top start</button>
<button onclick="const id = apogeeToast('Sticky until dismissed', { duration: 0 }); setTimeout(() => apogeeToast.dismiss(id), 3000)">Dismiss after 3s</button>Actions and width
Show codeHide code
<button onclick="apogeeToast('Draft deleted', { title: 'Deleted', width: 'long', duration: 0, actions: [{ label: 'Undo', onClick: () => apogeeToast('Draft restored', { color: 'positive' }) }, { label: 'View trash', href: '#' }] })">Delete with undo</button>
<button onclick="apogeeToast('Short width', { width: 'short' })">Short</button>
<button onclick="apogeeToast('A custom 30rem width for a longer sentence that would otherwise wrap twice.', { width: '30rem' })">30rem</button>Featured icon, an avatar message, and an upload with progress:
Show codeHide code
<button onclick="apogeeToast('Your invoice is paid.', { color: 'positive', title: 'Payment received', featured: true })">Featured</button>
<button onclick="apogeeToast('Can you review the brief?', { avatar: { name: 'Ana Lima', status: 'online' }, time: '2 min ago' })">Avatar</button>
<button onclick="let p = 0; const id = apogeeToast('report.pdf', { title: 'Uploading', progress: 0 }); const t = setInterval(() => { p += 20; apogeeToast.update(id, { progress: p, ...(p >= 100 && { title: 'Uploaded' }) }); if (p >= 100) clearInterval(t) }, 400)">Upload</button>Variants
Every color, shown in the hero preview above — click each button to fire
primary, positive, warning, and critical toasts.
Positions
All six position values (shown as code, not live — only one Toaster can be
mounted on this page at a time):
<Toaster position="top-start" />
<Toaster position="top-center" />
<Toaster position="top-end" />
<Toaster position="bottom-start" />
<Toaster position="bottom-center" />
<Toaster position="bottom-end" />
States
- Enter — fades and scales in (
apogee-toast-in,--duration-medium/--ease-standard). - Exit — fades and scales out (
apogee-toast--out,--duration-fast/--ease-standard) on close-button click or auto-dismiss. - Sticky (
duration: 0) — never auto-dismisses; only the close button removes it. - Reduced motion — enter/exit animations are disabled under
prefers-reduced-motion: reduce.
Examples
A sticky error that needs manual dismissal:
apogeeToast('Payment failed — update your card.', { color: 'critical', title: 'Action needed', duration: 0 })
A quick, title-less confirmation:
apogeeToast('Link copied to clipboard.')
Accessibility
| Key | Action |
|---|---|
Tab |
Reaches the action buttons and the close button; focus pauses the timer. |
Enter Space |
Press the focused button. |
-
The auto-dismiss timer pauses while the pointer or keyboard focus is on a toast and resumes when it leaves.
-
apogeeToast()returns an id;apogeeToast.dismiss(id)closes that toast early. -
The host is
role="region"witharia-label="Notifications". -
Each toast is
role="alert"(warning/danger— assertive) orrole="status"(info/success— polite), so it’s announced without any extraaria-livewiring. -
The close button is a real
<button>with a visiblefocus-visiblering and anaria-label="Dismiss"(icon-only). -
No JS —
apogeeToastis never defined, so nothing renders. Never make a toast the only feedback for an action a no-JS user can take. -
Actions are ordinary buttons at the end of the tab order; give a toast with actions
duration: 0or a long timeout so a keyboard user can reach them.
Tokens
| Token | Used for |
|---|---|
--border-neutral / --background-elevation-raised / --foreground-neutral |
Toast border, fill, text |
--foreground-neutral-faded |
Message text + close-button icon |
--foreground-{hue} |
The icon |
--border-{hue}-faded |
The border tint per color |
--outline-color-focus |
Close-button focus ring |
--duration-medium / --duration-fast / --ease-standard |
Enter / exit animation |
Do / Don’t
- Do mount exactly one
Toasterper page, in a layout shared by every route that fires toasts. - Do use
danger/warningfor anything the user must notice;info/successfor routine confirmation. - Don’t put content the user must act on (a form, a required decision) only in a toast — it auto-dismisses and isn’t reachable after.
- Don’t fire toasts for every minor state change — reserve them for things worth interrupting the user for.
Content
titleis one or two words (“Saved”, “Error”, “Heads up”).messageis one short sentence, plain language, states what happened (and, for errors, what to do next).- No ending period on
title;messagegets one if it’s a full sentence.