<bmx-toast>
A short message about something that has already happened: saved, sent,
copied, or failed. It appears in a corner, says one sentence, and goes away
on its own.
12 properties · 3 events · 2 methods · 7 parts
Example
Save
Delete message
Fail to save
Send six at once
Draft saved.
Message moved to Deleted.
Undo
We could not reach the server. Your changes are still here.
accounts-q3.xlsx uploaded.
payroll-august.csv uploaded.
contract-northwind.pdf uploaded.
site-photos.zip uploaded.
handover-notes.docx uploaded.
brand-assets.sketch uploaded.
The first is an ordinary confirmation and goes after five seconds. The second carries an action and stays twice as
long, because it has to be read, decided on, and then reached — and a keyboard user has further to travel than
a pointer user. The third is an alert: it interrupts a screen reader rather than waiting for a gap, and
it stays until it is dismissed, because interrupting somebody and then taking the message away unread is the worst
of both.
Press Send six at once and watch two things. New toasts are placed beyond the ones already on
screen rather than at the corner, so nothing you are halfway through reading slides away underneath you. And only
three are shown at a time — the rest wait, and take a place as one frees.
Rest the pointer anywhere in the stack, or Tab into one of the actions, and every clock in that corner stops until
you leave. Switching to another tab does the same: a notification sent to a window nobody is looking at has not been
seen. What is left of the time is kept rather than restarted, so a hover cannot pin a toast on screen for ever.
Show markup
<div class="row">
<bmx-button id="ex-toast-save" variant="outline" tone="neutral">Save</bmx-button>
<bmx-button id="ex-toast-delete" variant="outline" tone="neutral">Delete message</bmx-button>
<bmx-button id="ex-toast-fail" variant="outline" tone="danger">Fail to save</bmx-button>
<bmx-button id="ex-toast-burst" variant="outline" tone="neutral">Send six at once</bmx-button>
</div>
<bmx-toast id="ex-toast-saved" tone="success" position="bottom-end">Draft saved.</bmx-toast>
<bmx-toast id="ex-toast-undo" position="bottom-end">
Message moved to Deleted.
<bmx-button slot="action" size="sm" variant="ghost" id="ex-toast-undo-button">Undo</bmx-button>
</bmx-toast>
<bmx-toast id="ex-toast-error" tone="danger" priority="alert" heading="Could not save" position="bottom-end">
We could not reach the server. Your changes are still here.
</bmx-toast>
<!--
A fixed pool for the burst, reused rather than created on each press. The demo
runtime counts every element the library registers and stops at a hundred, and
the count never comes back down when one is removed - so a demonstration that
made a toast per press would work for a minute and then start showing
"Demo element limit reached". Opening the same elements again costs nothing.
-->
<bmx-toast class="ex-toast-file" tone="info" position="bottom-end">accounts-q3.xlsx uploaded.</bmx-toast>
<bmx-toast class="ex-toast-file" tone="info" position="bottom-end">payroll-august.csv uploaded.</bmx-toast>
<bmx-toast class="ex-toast-file" tone="info" position="bottom-end">contract-northwind.pdf uploaded.</bmx-toast>
<bmx-toast class="ex-toast-file" tone="info" position="bottom-end">site-photos.zip uploaded.</bmx-toast>
<bmx-toast class="ex-toast-file" tone="info" position="bottom-end">handover-notes.docx uploaded.</bmx-toast>
<bmx-toast class="ex-toast-file" tone="info" position="bottom-end">brand-assets.sketch uploaded.</bmx-toast>
<div class="row">
<span class="note">
The first is an ordinary confirmation and goes after five seconds. The second carries an action and stays twice as
long, because it has to be read, decided on, and then reached — and a keyboard user has further to travel than
a pointer user. The third is an <code>alert</code>: it interrupts a screen reader rather than waiting for a gap, and
it stays until it is dismissed, because interrupting somebody and then taking the message away unread is the worst
of both.
</span>
</div>
<div class="row">
<span class="note">
Press <em>Send six at once</em> and watch two things. New toasts are placed <em>beyond</em> the ones already on
screen rather than at the corner, so nothing you are halfway through reading slides away underneath you. And only
three are shown at a time — the rest wait, and take a place as one frees.
</span>
</div>
<div class="row">
<span class="note">
Rest the pointer anywhere in the stack, or Tab into one of the actions, and every clock in that corner stops until
you leave. Switching to another tab does the same: a notification sent to a window nobody is looking at has not been
seen. What is left of the time is kept rather than restarted, so a hover cannot pin a toast on screen for ever.
</span>
</div>
<script>
(function () {
// Reopening one that is already on screen has to close it first: `open` is
// already true, so setting it to true again is not a change and the
// component rightly does nothing.
const open = id => {
const toast = document.getElementById(id);
toast.open = false;
requestAnimationFrame(() => (toast.open = true));
};
document.getElementById('ex-toast-save').addEventListener('bmxActivate', () => open('ex-toast-saved'));
document.getElementById('ex-toast-delete').addEventListener('bmxActivate', () => open('ex-toast-undo'));
document.getElementById('ex-toast-fail').addEventListener('bmxActivate', () => open('ex-toast-error'));
document.getElementById('ex-toast-undo-button').addEventListener('bmxActivate', () => {
// The toast closes itself once its action is pressed, so an Undo never
// leaves its own message sitting there looking as though it did nothing.
open('ex-toast-saved');
});
document.getElementById('ex-toast-burst').addEventListener('bmxActivate', () => {
document.querySelectorAll('.ex-toast-file').forEach((toast, index) => {
setTimeout(() => {
toast.open = false;
requestAnimationFrame(() => (toast.open = true));
}, index * 200);
});
});
})();
</script>
<bmx-toast id="saved" tone="success">Draft saved.</bmx-toast>
<script>
document.getElementById('saved').open = true;
</script>
WHEN NOT TO REACH FOR ONE
If the user has to decide something, this is the wrong component: a message
that leaves on a timer cannot be one somebody must act on, and a toast is
easy to miss by design - it is in the corner, it is brief, and it is gone.
Use bmx-dialog when an answer is required, and the page itself when the
information is part of the task rather than about it. A toast may carry one
action - Undo, View - as a convenience, but the sentence has to make sense
to somebody who never reaches it.
WHAT MAKES THIS ONE DIFFERENT FROM A BOX WITH A TIMER
Everything that only happens when there is more than one of them, or when
somebody is actually reading:
- A stack that does not move. New toasts are placed beyond the ones
already on screen rather than at the corner, so nothing you are halfway
through reading slides away as another arrives.
- A clock that stops. Hovering or focusing anywhere in the corner pauses
every toast in it, and so does switching tabs - a notification sent to a
window nobody is looking at has not been seen. What is left of the time is
kept rather than restarted, so a hover cannot pin a toast on screen for
ever.
- A queue with an end. Three at a time, eight more waiting, and past
that the oldest waiting message is dropped rather than the newest -
see
src/core/toast.ts for why round that way.
status OR alert
status is polite and waits for a gap in whatever a screen reader is
saying. alert interrupts. Nearly everything is status; keep alert for
the cases where being interrupted is better than not knowing, and note that
an alert stays until it is dismissed, because interrupting somebody and
then removing the message unread is the worst of both. The judgement is the
same one that separates bmx-tooltip from bmx-popover, in different
clothes.
Properties
Events
Methods
Slots
CSS shadow parts
CSS custom properties