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.
A short message on top of the page, said out loud without taking focus from anybody.
@pacit/components/toastImplements the W3C ARIA APG Alert pattern ↗
Undone 0 time(s).
import { Component, inject, signal } from '@angular/core';
import { PctButton } from '@pacit/components/button';
import { PctToaster } from '@pacit/components/toast';
/** No element to place — a message about what just happened is raised by the code that did it. */
@Component({
selector: 'demo-toast',
imports: [PctButton],
styles:
':host { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }',
template: `
<button pctButton variant="outline" (click)="toaster.show('Draft saved.')">
Save
</button>
<button pctButton variant="outline" (click)="undoable()">
Move to bin
</button>
<p>Undone {{ undone() }} time(s).</p>
`,
})
export class ToastDemo {
protected readonly toaster = inject(PctToaster);
readonly undone = signal(0);
undoable(): void {
this.toaster.show({
text: 'Message moved to the bin.',
action: { label: 'Undo', run: () => this.undone.update((n) => n + 1) },
});
}
}toast.demo.ts — the same file runs here and is highlighted under Code
One import per component, and the shortest possible use — the package itself is installed once, on the start page.
import { PctToaster, PctToastViewport } from '@pacit/components/toast';| Exported | What it is |
|---|---|
PctToastActioninterface | The one thing a toast can offer besides going away: a label and what pressing it does. |
PctToastNoticeinterface | A message that says something happened and then goes away by itself. |
PctToastStandinginterface | A 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 | PctToastStanding | What {@link PctToaster.show} takes: a bare sentence, or one of the two shapes above. |
PctToastRefinterface | The handle a caller keeps — enough to take back a message that has stopped being true. |
PctToastStateinterface | A 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. |
PctToastConfiginterface | The application's answers to the four questions a toast cannot ask per message. |
PCT_DEFAULT_TOAST_CONFIGconst | The toast configuration a page gets without asking: bottom right, six seconds, four at a time. |
PCT_TOAST_CONFIGtoken | The token the toast configuration is read from; unprovided, it falls back to the default above. |
providePctToastConfigfunction | Registers 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. |
PctToastHostinterface | What 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_HOSTtoken | The 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-viewport | The place the messages appear in — one per application, created by {@link PctToaster} and never written into anybody's template. |
PctToasterinjectable | The 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.');Each example is the component itself, not a screenshot. Open the code under any of them.
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.
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.
| Name | Type | What it does |
|---|---|---|
| No inputs — the element is configured by its content. | ||
(click), (focus), the form's submit — are the platform's, and two-way models above carry the rest. Two ways in, both stable. No class names to depend on.
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.
| Part | Selector | What 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 |
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.
| Token | Styles |
|---|---|
--pct-toast-action-fg{pct.primary}#2563eb · #60a5fa | Text colour of the action button. |
--pct-toast-action-font-weight{pct.font.weight.medium}500 | Font weight of the action button. |
--pct-toast-action-radius{pct.radius.md}8px | Corner radius of the action button. |
--pct-toast-close-bg-hover{pct.surface-100}#f1f5f9 · #1e293b | Background of the close button on hover. |
--pct-toast-close-fg{pct.text-muted}#334155 · #e2e8f0 | Text colour of the close button. |
--pct-toast-close-fg-hover{pct.text}#0f172a · #ffffff | Text colour of the close button on hover. |
--pct-toast-close-radius{pct.radius.md}8px | Corner radius of the close button. |
--pct-toast-close-size{pct.target.min}24px | Size of the close button. |
--pct-toast-gap{pct.space.3}8px | Gap between stacked toasts. |
--pct-toast-icon-fg{pct.text}#0f172a · #ffffff | Colour of the tone's mark when a message carries no tone of its own. |
--pct-toast-icon-fg-danger{pct.danger}#b91c1c · #f87171 | Colour of the mark on a message that reports a failure. |
--pct-toast-icon-fg-info{pct.info}#2563eb · #60a5fa | Colour of the mark on an informational message. |
--pct-toast-icon-fg-success{pct.success}#15803d · #4ade80 | Colour of the mark on a message that reports success. |
--pct-toast-icon-fg-warning{pct.warning}#b45309 · #fbbf24 | Colour of the mark on a message that reports a warning. |
--pct-toast-icon-size16px | Size of the box the tone's mark is drawn in. |
--pct-toast-inset{pct.space.5}16px | Distance of the toast from the edge of the viewport. |
--pct-toast-item-bg{pct.surface}#ffffff · #0f172a | Background of an item. |
--pct-toast-item-border{pct.border-strong}#64748b | Edge colour of an item. |
--pct-toast-item-border-danger{pct.danger}#b91c1c · #f87171 | Edge of a message that reports a failure. |
--pct-toast-item-border-info{pct.info}#2563eb · #60a5fa | Edge of an informational message. |
--pct-toast-item-border-success{pct.success}#15803d · #4ade80 | Edge of a message that reports success. |
--pct-toast-item-border-warning{pct.warning}#b45309 · #fbbf24 | Edge of a message that reports a warning. |
--pct-toast-item-fg{pct.text}#0f172a · #ffffff | Text colour of an item. |
--pct-toast-item-gap{pct.space.3}8px | Gap between a toast's message and its action. |
--pct-toast-item-max-width360px | Maximum width of an item. |
--pct-toast-item-padding-x{pct.space.4}12px | Horizontal padding of an item. |
--pct-toast-item-padding-y{pct.space.3}8px | Vertical padding of an item. |
--pct-toast-item-radius{pct.radius.md}8px | Corner 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}13px | Font size of the message. |
--pct-toast-z-index1100 | Stacking level of the toast. |
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).
Pattern: Alert — for the urgent message, and the log role for the place it lands in; named in the class JSDoc
| key | effect | test |
|---|---|---|
Tab | walks into the stack, and every clock stops while it is in there | apps/sandbox-e2e/src/toast.spec.ts |
Enter / Space | presses the action or the cross — the platform's, on two <button>s | apps/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.
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.
libs/components/toast/src/toaster.ts, libs/components/toast/src/toast-viewport.tsapps/sandbox-e2e/src/toast.spec.ts — the walk in, the hold it puts on the clocks, and both buttons pressedapps/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 reachapps/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)forced-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 termsprefers-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 reducelibs/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 nearreq-api-size is about --pct-control-height-*, and the only width here is --pct-toast-item-max-width, which a consumer overridesapps/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 proveapps/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 renderFormValueControllibs/components/toast/src/toast.spec.ts — after the first render the document holds an empty role="log", and it is a child of bodylibs/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)apps/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 marksapps/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)libs/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)libs/components/parts.snapshot.md, tools/check-parts.mjs (target check-parts)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 firstPCT_TEXTStools/check-texts.mjs + libs/components/toast/src/toast.spec.ts — an application's own dismiss label replaces the defaultlibs/components/size.snapshot.md — the ./toast row, and the ./core row beside itdocs/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/components/toast on the published site — prerendered, the demo's own source is the code tabRead at build time from the tracked snapshots, not typed here.
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.
z-index cannot get above the top layer, and the number that says it can is the dependency'stone 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.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.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.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.