Components

Toast & banner

Your messenger for what just happened, in two flavors. A toast is quick — it pops in, says its piece, and gets out of the way. A banner is steady — it stays right in the page until the situation clears. Both speak the same five-tone severity scale as Badge.

Toast

Fire it from anywhere — a mutation handler, a catch block — through the toast API; a store lines them up and the one mounted <ToastRegion> shows the stack. Each toast is its own live region (role=status, or alert for critical), so a screen reader announces it without stealing focus. Hover or focus the stack and the timer waits for you; criticals stick around until you dismiss them.

Fire a toast — it lands bottom-right, pauses on hover, and auto-dismisses (criticals stay).

The steady half — for something the page needs to keep saying, not a passing event. It sits right in the flow, solid and opaque, with the same tones, an optional action, and an optional dismiss.

Tones

The shared scale: neutral (a plain note), info, affirm (done — it worked), caution (worth a look), critical (failed or blocked). Each tone is a solid, friendly color — the metal names (silver, patina, copper, rust) belong to the proposed system. Per ADR-0017 every tone carries an icon, so severity survives grayscale and color-blindness; the color rides the accent and icon while the text stays plain and readable.

Materiality

Same tones, two surfaces — and both are solid here. The floating glass toast is a proposed-system idea; in the official brand a toast is a flat, opaque card with a soft warm shadow, and a banner sits flat in the page.

toast surface
Flat and opaque — the frosted glass (card--sheet) belongs to the proposed system. Here the toast is a solid surface with a soft warm shadow at the highest lift, plus a tone accent bar and a tone icon.
banner surface
Opaque — a 8% tone tint toward the surface, a tone accent bar, and a tone icon at -700 (light) / -300 (dark) for contrast on the tint.
region
The stack portals to a body-level #halo-toast-root whose id selector beats the page-pattern z-index pin, so the fixed region holds its corner.
motion
Toasts pop in and slide out over --dur-base; reduced-motion turns the animation off — and with no glass blur here, that's all there is to drop.

Accessibility

  • Announced, not focused. Each toast is a live region (status / alert); focus never jumps to it, so a keyboard user is not interrupted mid-task.
  • Pauses for the reader. Hover or focus the stack and the auto-dismiss timer stops, so a toast can't vanish while being read or acted on.
  • Dismissable by keyboard. Every toast and dismissable banner has a real × button in the tab order.
  • Severity is never colour alone. The icon (and the word) carry the meaning; the tone reinforces it (ADR-0017).

API

toast(message, opts) / .affirm / .info / .caution / .critical
Fire a toast and get its id. Tone helpers are shorthands; toast.dismiss(id) and toast.clear() remove them.
ToastOptions
title, duration (ms or null to stay), action ({ label, onPress }), icon, dismissLabel.
<ToastRegion>
Mount once at the app root. placement, label, dismissLabel, max — the stack cap.
<Banner>
tone, title, children, icon, action, onDismiss, dismissLabel. Inline and controlled by its parent.

Usage

Mount the region oncetsx
import { ToastRegion } from '@halo-compliance/ui'

// once, at the app root (e.g. in your shell):
<ToastRegion placement="bottom-end" />
Fire a toast from a handlertsx
import { toast } from '@halo-compliance/ui'

async function onSave() {
  try {
    await saveRiskReport()
    toast.affirm('Risk report saved.')
  } catch {
    toast.critical('Could not save the report.', {
      action: { label: 'Retry', onPress: onSave },
    })
  }
}
An inline bannertsx
import { Banner, Button } from '@halo-compliance/ui'

<Banner
  tone="caution"
  title="Review needed"
  action={<Button variant="ghost" tone="secondary" size="sm">Review</Button>}
  onDismiss={() => setDismissed(true)}
>
  Three findings need a responsible party before this report can close.
</Banner>

When to use which

  • Toast for a passing event. Saved, copied, export started — feedback that doesn't need to persist or block.
  • Banner for a standing state. Draft, archived, review-needed — a fact about the page that stays until it changes.
  • Modal for a decision. If the user must stop and choose, that blocks the page — reach for a Modal, not a message.

Anti-patterns

  • A toast for an error that needs action. If the user must fix something, keep it on the page (a banner or inline error), not in a vanishing toast.
  • Toasts as a log. They are momentary; the stack caps and the rest are dropped. A history belongs in the page.
  • A banner with no way to act or dismiss. If it's actionable, give it an action; if it's transient, it was a toast.
  • Tone for decoration. Pick the tone for the severity, not the look.
  • /badge — the same five-tone severity register, as a status label.
  • /modal — the decision overlay, for when a message isn't enough.
  • /helper-text — the inline field-level severity text; banner is its section-level sibling.
  • /button — the solid-color actions inside a toast or banner.