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 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 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 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 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" with aria-label="Notifications".

  • Each toast is role="alert" (warning / danger — assertive) or role="status" (info / success — polite), so it’s announced without any extra aria-live wiring.

  • The close button is a real <button> with a visible focus-visible ring and an aria-label="Dismiss" (icon-only).

  • No JS — apogeeToast is 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: 0 or 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 Toaster per page, in a layout shared by every route that fires toasts.
  • Do use danger / warning for anything the user must notice; info / success for 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

  • title is one or two words (“Saved”, “Error”, “Heads up”).
  • message is one short sentence, plain language, states what happened (and, for errors, what to do next).
  • No ending period on title; message gets one if it’s a full sentence.