Primitives
32

Toast

A stack of transient messages, and the first thing in the library with a real exit. The viewport is a live region and renders even when empty — a region added to the page at the same moment as its first message is a region assistive tech was not watching, and the message is simply lost.

plain markup — no library on the page
rendered by aura-react
rendered by aura-vue
rendered by aura-svelte
built from aura-angular's own class helpers
Saved
Your changes are live.
Could not reach the server
Saved
Your changes are live.
Could not reach the server
Saved
Your changes are live.
Could not reach the server
Saved
Your changes are live.
Could not reach the server
Saved
Your changes are live.
Could not reach the server

Code

index.html
<div class="aura-toast__viewport" aria-live="polite" aria-atomic="false">
  <div class="aura-toast" data-state="open" aria-live="polite">
    <div class="aura-toast__body">
      <div class="aura-toast__title">Saved</div>
      <div class="aura-toast__description">Your changes are live.</div>
    </div>
    <button type="button" class="aura-icon-btn aura-icon-btn--sm" aria-label="Dismiss Saved">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" aria-hidden="true">
        <path d="M6 6l12 12M18 6L6 18"></path>
      </svg>
    </button>
  </div>
  <div class="aura-toast aura-toast--danger" data-state="open" aria-live="assertive">
    <div class="aura-toast__body">
      <div class="aura-toast__title">Could not reach the server</div>
    </div>
    <button type="button" class="aura-icon-btn aura-icon-btn--sm" aria-label="Dismiss Could not reach the server">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" aria-hidden="true">
        <path d="M6 6l12 12M18 6L6 18"></path>
      </svg>
    </button>
  </div>
</div>
Demo.tsx
import { ToastViewport } from '@ilomee/aura-react';

/**
 * Rendered with `duration: 0`, so the toasts stay put: a demo whose contents disappear five
 * seconds after the page loads is a demo of an empty corner. Dismissing one still works, and
 * still plays the exit — which is the thing worth seeing here.
 */
const TOASTS = [
  { id: 'a', title: 'Saved', description: 'Your changes are live.', duration: 0 },
  { id: 'b', title: 'Could not reach the server', tone: 'danger' as const, duration: 0 },
];

export default function Demo() {
  return <ToastViewport toasts={TOASTS} onDismiss={() => {}} />;
}
Demo.vue
<script setup lang="ts">
import { ToastViewport, createToaster, provideToaster } from '@ilomee/aura-vue';

// Or: export a createToaster() from a module of your own, and call it from anywhere.
const toaster = provideToaster();
</script>

<template>
  <button type="button" @click="toaster.success('Saved', { description: 'Your changes are live.' })">
    Save
  </button>
  <ToastViewport :toasts="toaster.toasts.value" @dismiss="toaster.dismiss" />
</template>

Vue also ships the queue itself: createToaster() returns { toasts, show, success, info, warning, danger, dismiss, clear }, so the thirty lines under this component do not have to be written again. A factory rather than a singleton, because module state on a server is shared by every request the process handles — where you keep the instance decides what you get. A module of your own is callable from an interceptor or an error handler, which inject() cannot be; provideToaster() + useToast() is one per app and safe under SSR. The methods are named after the tones (danger, not error) so there is one vocabulary rather than two.

Demo.svelte
<script lang="ts">
  import { ToastViewport } from '@ilomee/aura-svelte';
  import { dismissToast } from '@ilomee/aura-core/toast';

  let toasts = $state([{ id: 'a', title: 'Saved', description: 'Your changes are live.' }]);
</script>

<ToastViewport {toasts} ondismiss={(id) => (toasts = dismissToast(toasts, id))} />

Used as aura-toast-viewport.

demo.component.ts
<aura-toast-viewport [toasts]="toasts" (dismiss)="toasts = dismissToast(toasts, $event)" />

Props

Plain markup has no props — the classes below are the whole API. The four packages do nothing more than set these same classes for you.
NameTypeDefaultNotes
renderToast(toast: AuraToastItem) => ReactNodeRender a toast’s body yourself. The shell — live region, timer, pause, exit — stays with the viewport.
action{ label, onAction }On the toast item: a single action rendered as a button under the text. Pair it with duration: 0 — five seconds is not long enough to read, decide and reach the button.
toastsAuraToastItem[] | nullThe list on screen. State is the caller’s — addToast and dismissToast in @ilomee/aura-core/toast are the two pure operations on it.
onDismiss*event(id: string) => voidFires after the exit animation, not at the click — the element has to stay in the DOM long enough to leave it.
closeLabelstring'Dismiss {title}'{title} is replaced with the toast’s own — a template, because the noun sits in a different place in each language.
NameTypeDefaultNotes
toastslot{ toast: AuraToastItem }
toastsAuraToastItem[] | null
dismissevent(id: string) => void
closeLabelstring'Dismiss {title}'
NameTypeDefaultNotes
toastslotSnippet<[AuraToastItem]>
toastsAuraToastItem[] | null
ondismiss*event(id: string) => void
closeLabelstring'Dismiss {title}'
NameTypeDefaultNotes
toastTemplateTemplateRef<{ $implicit: AuraToastItem }>
toastsAuraToastItem[] | null
dismissevent(id: string) => void
closeLabelstring'Dismiss {title}'

Emitted classes

Every framework emits the same classes — that is what makes the four libraries look identical. You can use them directly, without any component library.

ClassRoleNotes
.aura-toast__viewportelement
.aura-toastbaseRequired. A modifier does nothing without it.
.aura-toast__bodyelement
.aura-toast__titleelement
.aura-toast__descriptionelement

Tokens and rules

components.css
layer
var(--z-toast) — minted for this and unused until now
width
min(360px, calc(100vw - 2 * var(--space-4)))
edge
the safe-area insets, so it clears the home indicator