On this page
Overlays

Toast released

A short message on top of the page, said out loud without taking focus from anybody.

  • Import@pacit/components/toast

Implements the W3C ARIA APG Alert pattern ↗

Undone 0 time(s).

toast.demo.ts — the same file runs here and is highlighted under Code

Start here

Usage

One import per component, and the shortest possible use — the package itself is installed once, on the start page.

Import this component
import { PctToaster, PctToastViewport } from '@pacit/components/toast';
ExportedWhat it is
PctToastActioninterfaceThe one thing a toast can offer besides going away: a label and what pressing it does.
PctToastNoticeinterfaceA message that says something happened and then goes away by itself.
PctToastStandinginterfaceA message that stays until somebody deals with it: one that can be acted on, or one urgent enough to interrupt what a screen reader is saying.
PctToastSpectypePctToastNotice | PctToastStandingWhat {@link PctToaster.show} takes: a bare sentence, or one of the two shapes above.
PctToastRefinterfaceThe handle a caller keeps — enough to take back a message that has stopped being true.
PctToastStateinterfaceA message as the viewport draws it: what is on the screen and nothing else. The clock, the action's callback and the politeness the region was opened with stay with the service — a view that cannot reach them cannot get them wrong.
PctToastBlocktype'start' | 'end'Which edge of the block axis the stack stands at — start is the top of a Latin page.
PctToastInlinetype'start' | 'center' | 'end'Where on the inline axis: start/end mirror with the writing direction, center does not.
PctToastConfiginterfaceThe application's answers to the four questions a toast cannot ask per message.
PCT_DEFAULT_TOAST_CONFIGconstThe toast configuration a page gets without asking: bottom right, six seconds, four at a time.
PCT_TOAST_CONFIGtokenThe token the toast configuration is read from; unprovided, it falls back to the default above.
providePctToastConfigfunctionRegisters the toast configuration (the provideX pattern, req-api-config). It is its own token rather than four fields of PctConfig, for the reason that keeps the texts apart as well: an application that never raises a toast should not carry the answers to questions it never asks.
PctToastHostinterfaceWhat the viewport is given, and the whole of it. The view is created by a service rather than written into somebody's template, so the two are joined by a token instead of by an import — which is also what keeps the two files from importing each other.
PCT_TOAST_HOSTtokenThe token the toaster provides itself under, so the region it creates reaches it — for the messages to draw, and to say that one was dismissed or its action pressed.
PctToastViewportcomponentpct-toast-viewportThe place the messages appear in — one per application, created by {@link PctToaster} and never written into anybody's template.
PctToasterinjectableThe library's toasts — messages that arrive on top of the page rather than in it.
// once, in the shell:  <pct-toast-viewport />
inject(PctToaster).show('Draft saved.');
Examples

Every state, running

Each example is the component itself, not a screenshot. Open the code under any of them.

A message with an answer

show() takes text, or text with an action — the toast then carries a button, and the callback runs when the reader presses it. Undo is the pattern this exists for.

Archived: 0, restored: 0.

API

What it reads, and what it writes back

Read from the source at build time — the types, the defaults and the lines of documentation are the library's own.

The library's toasts — messages that arrive on top of the page rather than in it.

Inputs

NameTypeWhat it does
No inputs — the element is configured by its content.

Outputs

None of its own. The native events — (click), (focus), the form's submit — are the platform's, and two-way models above carry the rest.
Styling

Parts to select, tokens to override

Two ways in, both stable. No class names to depend on.

Parts

In a test, PctToastViewportHarness from @pacit/components/testing reads a part by the name below and a state by its attribute — the harness gate holds the names to the package.

PartSelectorWhat it is
action[data-pct-part="action"]the button that answers it — Undo, Retry
close[data-pct-part="close"]the button that dismisses it
icon[data-pct-part="icon"]the tone's mark, drawn only when a message carries a tone
item[data-pct-part="item"]one toast
message[data-pct-part="message"]its text

Tokens

Every value the component reads, with the light and the dark default. Override a token at any scope and it cascades like any custom property.

TokenStyles
--pct-toast-action-fg{pct.primary}#2563eb · #60a5faText colour of the action button.
--pct-toast-action-font-weight{pct.font.weight.medium}500Font weight of the action button.
--pct-toast-action-radius{pct.radius.md}8pxCorner radius of the action button.
--pct-toast-close-bg-hover{pct.surface-100}#f1f5f9 · #1e293bBackground of the close button on hover.
--pct-toast-close-fg{pct.text-muted}#334155 · #e2e8f0Text colour of the close button.
--pct-toast-close-fg-hover{pct.text}#0f172a · #ffffffText colour of the close button on hover.
--pct-toast-close-radius{pct.radius.md}8pxCorner radius of the close button.
--pct-toast-close-size{pct.target.min}24pxSize of the close button.
--pct-toast-gap{pct.space.3}8pxGap between stacked toasts.
--pct-toast-icon-fg{pct.text}#0f172a · #ffffffColour of the tone's mark when a message carries no tone of its own.
--pct-toast-icon-fg-danger{pct.danger}#b91c1c · #f87171Colour of the mark on a message that reports a failure.
--pct-toast-icon-fg-info{pct.info}#2563eb · #60a5faColour of the mark on an informational message.
--pct-toast-icon-fg-success{pct.success}#15803d · #4ade80Colour of the mark on a message that reports success.
--pct-toast-icon-fg-warning{pct.warning}#b45309 · #fbbf24Colour of the mark on a message that reports a warning.
--pct-toast-icon-size16pxSize of the box the tone's mark is drawn in.
--pct-toast-inset{pct.space.5}16pxDistance of the toast from the edge of the viewport.
--pct-toast-item-bg{pct.surface}#ffffff · #0f172aBackground of an item.
--pct-toast-item-border{pct.border-strong}#64748bEdge colour of an item.
--pct-toast-item-border-danger{pct.danger}#b91c1c · #f87171Edge of a message that reports a failure.
--pct-toast-item-border-info{pct.info}#2563eb · #60a5faEdge of an informational message.
--pct-toast-item-border-success{pct.success}#15803d · #4ade80Edge of a message that reports success.
--pct-toast-item-border-warning{pct.warning}#b45309 · #fbbf24Edge of a message that reports a warning.
--pct-toast-item-fg{pct.text}#0f172a · #ffffffText colour of an item.
--pct-toast-item-gap{pct.space.3}8pxGap between a toast's message and its action.
--pct-toast-item-max-width360pxMaximum width of an item.
--pct-toast-item-padding-x{pct.space.4}12pxHorizontal padding of an item.
--pct-toast-item-padding-y{pct.space.3}8pxVertical padding of an item.
--pct-toast-item-radius{pct.radius.md}8pxCorner radius of an item.
--pct-toast-item-shadow{pct.shadow-panel}0 8px 24px rgba(15, 23, 42, 0.14) · 0 8px 24px rgba(0, 0, 0, 0.55)Shadow of an item.
--pct-toast-message-font-size{pct.font.size.sm}13pxFont size of the message.
--pct-toast-z-index1100Stacking level of the toast.

Theming

A brand override in a few declarations. Scope it to a theme attribute, a page or one container — the component never notices.

[data-theme='brand'] {
  --pct-toast-item-bg: #134e4a;
  --pct-toast-item-fg: #f0fdfa;
  --pct-toast-action-fg: #5eead4;
}

Undone 0 time(s).

Accessibility

Measured, not assumed

Pattern: Alert — for the urgent message, and the log role for the place it lands in; named in the class JSDoc

Keyboard

keyeffecttest
Tabwalks into the stack, and every clock stops while it is in thereapps/sandbox-e2e/src/toast.spec.ts
Enter / Spacepresses the action or the cross — the platform's, on two <button>sapps/sandbox-e2e/src/toast.spec.ts

There is no key of the library's own. A toast takes no focus when it arrives (the APG's alert pattern), and the stack is reached the way any other part of the page is — which is also its sharpest limitation, below.

What is measured, and what is not yet

Every line is a test in three engines or a gate on every commit. The gaps are listed the same way, each with the requirement that will close it.

MeasuredARIA APG pattern in the JSDoclibs/components/toast/src/toaster.ts, libs/components/toast/src/toast-viewport.ts
MeasuredKeyboard map testedapps/sandbox-e2e/src/toast.spec.ts — the walk in, the hold it puts on the clocks, and both buttons pressed
Measuredaxe auditapps/sandbox-e2e/src/a11y.spec.ts — the /toast view, and separately a stack of three messages with an alert nested in the log, which the walk over the routes cannot reach
MeasuredVisual screenshotapps/sandbox-e2e/src/visual.spec.ts — toast-stack and toast-stack-rtl, the whole viewport rather than a card: where the stack lands against the window's own edges is what this component draws — the sandbox's navigation blanked by the stage, so a row in that list is not a pixel here (req-quality-e2e)
Measuredforced-colors: activeapps/sandbox-e2e/src/forced-colors.spec.ts — the edge in CanvasText (a card of Canvas on a page of Canvas has nothing else), and the action in LinkText against the sentence's CanvasText: the one distinction inside the card that the mode keeps on its own terms
Measuredprefers-reduced-motionlibs/components/toast/src/toast-viewport.scss — the enter is opacity over --pct-motion-transition-duration, and the preference is answered by the token build (req-a11y-motion). There is no leave to reduce
MeasuredTouch target ≥ 24×24 pxlibs/tokens/src/component.toast.json — the cross's box is {pct.target.min} and the action carries min-block-size: var(--pct-target-min), which one line of small type is nowhere near
By designSize axisnone — deliberately: a toast is not a control. req-api-size is about --pct-control-height-*, and the only width here is --pct-toast-item-max-width, which a consumer overrides
GapDensity axisnone — gap. The same one every component here has (req-token-density)
MeasuredRTLapps/sandbox-e2e/src/toast.spec.ts (the direction carried onto the box and the box pinned to the other edge) plus the toast-stack-rtl screenshot. The placement is inset-inline-*, so mirroring needs no rule of its own — but the dir has to REACH a child of body, and that is the half a stylesheet cannot prove
MeasuredSSR + hydrationapps/sandbox-e2e/src/hydration.spec.ts — the /toast view renders and hydrates with no NG05xx; the viewport contributes no markup at all, being created by a browser render
By designFormsnone — deliberately: a toast is not a form control. It holds no value and implements no FormValueControl
MeasuredThe region precedes the messagelibs/components/toast/src/toast.spec.ts — after the first render the document holds an empty role="log", and it is a child of body
MeasuredThe region says nothing extralibs/components/toast/src/toast.spec.ts + apps/sandbox-e2e/src/toast.spec.ts — the ABSENCE of aria-live, aria-atomic and aria-relevant asserted in both, which is the only way to hold an attribute that must not be written (lesson-112)
MeasuredA modal does not silence itapps/sandbox-e2e/src/toast.spec.ts — the viewport is not inert and sits in no inert subtree while a dialog is up, plus libs/components/core/src/core.spec.ts for which elements the background marks
MeasuredThe top layerapps/sandbox-e2e/src/toast.spec.ts — a message raised while the modal is up can be pressed through the veil, and an older one cannot: the order in the top layer is the order things were shown in (lesson-122)
MeasuredThe clocklibs/components/toast/src/toast.spec.ts (the duration, the default, the pause and the resumed remainder) and apps/sandbox-e2e/src/toast.spec.ts (the same under a real pointer and real focus)
MeasuredParts in the inventorylibs/components/parts.snapshot.md, tools/check-parts.mjs (target check-parts)
MeasuredTokens + contrast.policy.jsonlibs/tokens/src/contrast.policy.json — fourteen pairs. The one worth having among the first five is the action against the card: it is drawn as text and measured as text, not as a control. The other eight are the tones, each measured twice — the mark against the card and the edge against the page — because a policy that measured "the tone" once would be measuring whichever tone somebody wrote first
MeasuredStrings through PCT_TEXTStools/check-texts.mjs + libs/components/toast/src/toast.spec.ts — an application's own dismiss label replaces the default
MeasuredSize budgetlibs/components/size.snapshot.md — the ./toast row, and the ./core row beside it
MeasuredScreen-reader logRead 2026-09-28 from docs/acr/at/ — three readers, whole sandbox, and the act that raises a message. A message in the log is read as it arrives, by the two readers that heard it: NVDA Copied to the clipboard. Dismiss — the text and its dismiss button in one breath — and Orca Notifications. Copied to the clipboard., the region's name first. VoiceOver said nothing in the act's window and answered the Escape after it with its own hint (You are currently on a button. To click this button, press Control-Option-Space.), the reader talking about itself; whether the message reached it is not in this log. Whether an alert nested in the log interrupts is a second act, not this one. The viewport still costs no tab stop when empty — no reader announced one
Measureddocs page/components/toast on the published site — prerendered, the demo's own source is the code tab
Evidence

What the repository holds for this component

Read at build time from the tracked snapshots, not typed here.

65.91%mutation score116 of 176 mutants killed
15 × 3e2e cases × enginesplus the shared audits: axe, forced colors, preferences, size, hydration
14colour pairs measuredlight and dark, at every token build
2visual baselinesChromium, the sandbox's own pictures
5elements in the preview7 listeners · 1 render to settle · 2418 µs in jsdom, held exactly but for the clock

Why it is built this way

The whole component is one decision applied three times over (0044): the region is a place that was already there, opened empty by a render, so every message after that is a change inside a region an assistive technology has registered — and never a region arriving with its text, which is what nobody hears.

Decisions behind it

  • 0044A toast is a change in a region that was already there, and its place is the top layer
  • 0026One channel per politeness, and a message with a home announces from it
  • 0039A state the platform publishes is not ours to write
  • 0024The overlay layer carries what an overlay severs, and borrows the closing stack
  • 0007Configuration apart from texts
  • 0028An icon set is a component, and an icon's box is the contract
  • lesson-112An ARIA attribute over a native state is written by us and read by nobody
  • lesson-122A z-index cannot get above the top layer, and the number that says it can is the dependency's

Known limitations

  • A tone is not announced. tone reaches the eye and no further: the mark is drawn inside a pct-icon, which is aria-hidden, so what a screen-reader user gets is the sentence. That is deliberate — "Could not save." already says what the red says — and it is why the tone is never the only place a message carries its meaning. Until 2026-09-07 this row said there were no tones at all, and it gave the right reason: an icon per tone is four public names and a decision no single component should make. Two components wanted the same four, and the set is 0076's.
  • Urgency and tone are separate, and both are optional. What INTERRUPTS is urgent; what HAPPENED is tone. A failure a user can deal with later is danger and not urgent; a session about to expire is urgent whatever its tone.
  • No leave transition. A message fades in and goes at once. The honest version of a leave is a layout animation — every card under the one going moves up at the same time — and that is not a fade.
  • The stack is at the end of the page's tab order, and the key that jumps to it is the application's to install. A keyboard user reaches Undo after every control on the page — which is why an action makes a message standing, so the clock does not run out during the walk. Since 2026-09-07 there is a way out of the walk: the stack registers itself as a region and answers [pctRegionKey], F6 by default, mounted by the consumer (0072). The library still installs nothing on the document: with no key mounted anywhere, this row reads exactly as it did before. And a page whose focus is still on body needs listenOn="document" for the first press to be heard at all — the honest default's real hole, written down where the choice is made.
  • The oldest message goes when the limit is reached, and nothing says the user read it. Four is the default and it is configuration, because only the application knows how tall its window is.
  • A modal opened after a message stands over it. The top layer orders by recency and that is the platform's rule, not ours; the case it is chosen for is the opposite one — a message raised while the modal is up stands over the modal, which is when a report matters most.
  • One place for the whole application. block and inline are configuration, not per-message, so an application cannot put errors in one corner and confirmations in another. Two stacks would be two live regions competing to be read, which is the thing 0026 is about.