Components
Toast
Confirmation that something happened, in the corner, that goes away on its own. The constraint that makes it usable is what it is forbidden from carrying.
When to use it
To confirm a completed action whose result is not already visible. If the change is on screen — the row disappeared, the badge updated — a toast is noise on top of an answer the user already has.
When not to
- Never for anything requiring action. It disappears. A toast with an "Undo" or "Retry" button hands someone a control on a timer, which is hostile to anyone reading slowly, using a screen reader, or simply looking away. Put the action where it persists.
- Never for a field error. That belongs next to the field, where the person will be looking.
- Never stacked. Three toasts at once means the interface is narrating rather than reporting. Show the most recent, or a single summary.
- Not for anything a user must not miss. If missing it matters, it is a dialog or an inline message.
Roles
Invoice sent
A copy is in your outbox.
Export queued
You will get an email when it is ready.
Connection lost
Reconnecting automatically.
The live region must already exist
A live region is only announced when its contents change. Insert the container and the message in the same tick and many screen readers say nothing at all — the region was not being watched yet. Render an empty container on page load and append into it.
<!-- The live region exists from page load, empty.
Adding role="status" at the same moment as the message
is the single most common reason a toast is silent. -->
<div id="toasts" role="status" aria-live="polite"></div>
<!-- Appended into it later -->
<div class="ht-toast ht-toast--positive">
<div class="ht-toast__body">
<p class="ht-toast__title">Invoice sent</p>
<p>A copy is in your outbox.</p>
</div>
<button class="ht-toast__close" aria-label="Dismiss">✕</button>
</div> Timing
| Rule | Reason |
|---|---|
| Minimum 5 seconds | Below that, a message is gone before someone who glanced away can return to it. |
| Longer for longer text | Roughly a second per dozen words on top of the minimum. A fixed duration punishes the longest message. |
| Pause on hover and focus | Someone reaching for the dismiss button should not have it vanish under the cursor. |
| Always dismissible | A close button, so the timer is never the only way out. WCAG 2.2.1 for anything on a timer. |
Accessibility
- The dismiss button has an aria-label — an unlabelled ✕ is announced as "button" and nothing more.
- The role is carried in the title text as well as the tint, so "Connection lost" reads as a problem without the colour.
- Toasts do not take focus. Moving focus to a message that will disappear strands the keyboard user wherever it was when it went.
- Entry and exit transitions resolve to instant under reduced motion — see Motion.