Skip to content

HabToastHost ​

The bottom-right stack, above everything. It confirms what already happened.

Each dismisses itself after five seconds — hover one to hold it
vue
<script setup lang="ts">
const toasts = ref<HabToast[]>([]);
const dismiss = (id: string | number) => {
	toasts.value = toasts.value.filter((t) => t.id !== id);
};
</script>

<template>
	<HabToastHost
		:toasts="toasts"
		@dismiss="dismiss"
	/>
</template>

It never asks a question ​

A toast disappears. That makes it the wrong place for anything the user has to act on, and the wrong place for anything they might need to read twice — a question is a HabModal, and something that must stay legible is a HabBanner.

Write the message in the past tense. "Session saved", not "Save the session".

The host owns no list ​

toasts comes in as a prop and entries leave when you drop them in response to dismiss. The queue lives in your app's own store rather than in a module-level singleton this package would hide — one less piece of global state, and testable without mounting anything.

dismiss fires for all three exits: the close button, a swipe, and the timer running out. Nothing is removed for you.

ts
const dismiss = (id: string | number) => {
	toasts.value = toasts.value.filter((t) => t.id !== id);
};

Same anatomy as the other notices ​

Tinted surface, tone edge, full-strength tone glyph — shared with HabBanner and HabStatusStrip. What makes a toast a toast is elevation and position, not a different look.

The left edge is --hab-edge-notice, the 4px one. A toast is the definitive case of something that happened and has stopped being true; the 8px --hab-edge-classify would claim it is a standing condition of the region.

Timing ​

Five seconds by default, per-toast with duration, and Infinity pins one until the user acts. Timers pause while the pointer is over the stack or focus is inside it, which is what stops a toast vanishing mid-read.

The action slot takes one affordance per toast, usually Undo. Give an undo-carrying toast a longer duration than a bare confirmation — the user has to notice it, read it and decide.

Accessibility ​

  • The stack is a named region, reachable with F8 from anywhere on the page — which is the only way a keyboard user can act on a toast at all.
  • Each message is announced in a live region.
  • background: true marks a toast raised by a background task rather than by something the user just did: it waits its turn instead of interrupting whatever the screen reader is currently saying. Use it for anything the user did not set off themselves.
  • Swipe-to-dismiss is a pointer convenience; the close button is the accessible path and is always present.
  • Not asserted in the specs: auto-dismissal and the hover pause. Both need real timers and real pointer events — the browser test layer's job.

API ​

Props ​

PropTypeDefaultDescription
dismissLabelstringundefinedAccessible name for every toast's dismiss button. Required: Habitus ships no default, so the string is the host application's to translate.
durationnumber5000Milliseconds each toast stays. Per-toast duration wins.
labelstringundefinedAnnounced label for the region, and for each toast. Required: Habitus ships no default, so the string is the host application's to translate. {hotkey} in the region label is substituted for you.
toastsHabToast[][]The live stack. Remove an entry when dismiss fires for it.

Events ​

EventPayloadDescription
dismissid: string | number—

Slots ​

SlotBindingsDescription
action{ toast: HabToast }One trailing affordance per toast, usually Undo. Receives the toast.
leading{ toast: HabToast }Leading adornment per toast, replacing the tone glyph. Receives the toast.

Proprietary — internal use only.