Toast

Toasts are created from page code with toast(message, options) from js/toast.js; there is one toast region per page (bottom centre on phones above the tab bar, bottom end on desktop), created on first use. render() here only produces the same static markup for documentation and server-rendered previews.

Use a toast when the result of an action is not visible on screen ("Bestätigung an 23 Personen gesendet") and for reversible actions with an undo ("Lea als abwesend markiert" + "Rückgängig", UX guide §5.1). If the result is visible — a ticked row, a moved item — no toast is needed.

Look (design guide §9.4): inverted surface (--color-inverse-bg / --color-inverse-text), --shadow-md, a leading status icon, one line of text, the optional action as an underlined semibold text button. Status colour never goes on text there (it isn't tested on the inverse surface); success and danger put a small status fill behind the icon instead (§3.3). neutral shows the info icon without a fill.

Timing: plain toasts stay 4 s, toasts with an action at least 8 s (WCAG 2.2.1), tone: "danger" toasts stay until dismissed and use role="alert". Hovering or focusing a toast pauses the timer. Every toast has a close button.

Field errors never go into a toast — they belong to the form. Lasting problems belong in an alert.

Examples

Confirmation

Bestätigung an 23 Personen gesendet
Data
render({
  "message": "Bestätigung an 23 Personen gesendet",
  "tone": "success"
})
Markup
<div class="toast" data-status="success">
<span class="toast__icon"><svg class="icon" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check-circle"/></svg></span>
<span class="toast__text">Bestätigung an 23 Personen gesendet</span>
<button class="toast__action" type="button" data-close aria-label="Schliessen"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#x"/></svg></button>
</div>

Neutral

Training wurde in den Entwurf verschoben
Data
render({
  "message": "Training wurde in den Entwurf verschoben"
})
Markup
<div class="toast">
<span class="toast__icon"><svg class="icon" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#info"/></svg></span>
<span class="toast__text">Training wurde in den Entwurf verschoben</span>
<button class="toast__action" type="button" data-close aria-label="Schliessen"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#x"/></svg></button>
</div>

With undo

Lea Müller als abwesend markiert
Data
render({
  "message": "Lea Müller als abwesend markiert",
  "actionLabel": "Rückgängig"
})
Markup
<div class="toast">
<span class="toast__icon"><svg class="icon" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#info"/></svg></span>
<span class="toast__text">Lea Müller als abwesend markiert</span>
<button class="toast__action" type="button" data-act>Rückgängig</button>
<button class="toast__action" type="button" data-close aria-label="Schliessen"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#x"/></svg></button>
</div>

Failure

Data
render({
  "message": "Speichern fehlgeschlagen. Wird erneut versucht, sobald du online bist.",
  "tone": "danger"
})
Markup
<div class="toast" data-status="danger" role="alert">
<span class="toast__icon"><svg class="icon" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#alert-circle"/></svg></span>
<span class="toast__text">Speichern fehlgeschlagen. Wird erneut versucht, sobald du online bist.</span>
<button class="toast__action" type="button" data-close aria-label="Schliessen"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#x"/></svg></button>
</div>

Data contract

What render(data) accepts — validated against this JSON Schema in development and tests. Unknown fields are rejected. The same schema is in components.json.

FieldTypeRequiredDefaultDescription
messagestringrequired—What happened, in past tense ("Anwesenheit gespeichert").
min 1 chars
tone"neutral" | "success" | "danger"optional"neutral"Status, written as data-status with its icon: neutral (info icon), success (check-circle on a success fill, for confirmations), danger (alert-circle on a danger fill; role="alert", stays until dismissed).
actionLabelstringoptional—Text of the optional action button, usually "Rückgängig".
min 1 chars
closeLabelstringoptional"Schliessen"Accessible name of the close button.
JSON Schema
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "message"
  ],
  "properties": {
    "message": {
      "type": "string",
      "minLength": 1,
      "description": "What happened, in past tense (\"Anwesenheit gespeichert\")."
    },
    "tone": {
      "type": "string",
      "enum": [
        "neutral",
        "success",
        "danger"
      ],
      "default": "neutral",
      "description": "Status, written as `data-status` with its icon: `neutral` (info icon), `success` (check-circle on a success fill, for confirmations), `danger` (alert-circle on a danger fill; `role=\"alert\"`, stays until dismissed)."
    },
    "actionLabel": {
      "type": "string",
      "minLength": 1,
      "description": "Text of the optional action button, usually \"Rückgängig\"."
    },
    "closeLabel": {
      "type": "string",
      "default": "Schliessen",
      "description": "Accessible name of the close button."
    }
  }
}

Markup & states

Root: div (inside .toast-region[aria-live="polite"]).toast — usable without render() by writing the markup directly.

Attributes

AttributeValuesDefaultDescription
data-statussuccess | danger(none = neutral)Status: a status fill behind the icon.
role="alert"——Set for danger toasts.

Parts

PartDescription
.toast-regionFixed container created by toast(); aria-live="polite". Offset above bottom bars with --toast-offset.
.toast__iconLeading status icon (decorative): info / check-circle / alert-circle; round status fill for success and danger.
.toast__textThe message.
.toast__action[data-act]Optional action ("Rückgängig").
.toast__action[data-close]Close button (x icon with aria-label).

States

StateDescription
[data-leaving]Exit animation before removal.

CSS custom properties

PropertyDefaultDescription
--toast-offset0pxExtra distance from the bottom (set on the region or a parent), e.g. the height of a sticky bottom bar.

Behaviour & events

Module: js/toast.js

import { toast } from '@clubhub/ui/js/toast.js' and call it; no markup or mount() needed.

API

FunctionSignatureReturnsDescription
toasttoast(message: string, { tone?: "neutral" | "success" | "danger", timeout?: number, action?: { label: string, onAction: () => void } }?)() => void — dismiss functionShows a toast in the page's toast region. timeout in ms; default 4000, 8000 with an action, 0 (stay until dismissed) for danger. onAction runs when the action is pressed, then the toast closes. The timer pauses on hover/focus and resumes with half the time on pointer leave.

Accessibility

  • The region is aria-live="polite"; danger toasts use role="alert".
  • Toasts with an action stay at least 8 s and pause on hover/focus, so keyboard and screen-reader users can reach "Rückgängig".
  • Never put the only way to do something into a toast; undo must also be possible elsewhere or within the timeout.

Guidance

DoWrite what happened: "3 Streifen vergeben".
DoOffer "Rückgängig" instead of asking "Sind Sie sicher?" for reversible actions.
Don'tDon't toast what is already visible.
Don'tDon't show field or validation errors as toasts.
Don'tDon't queue several toasts for one action.

Related: Alert / banner · Save status · Confirm dialog