Toast
PreviewShort messages at the edge of the screen that close on their own.
Features
Permalink to heading "Features"- Settings as Fluid props, toasts from any script through one
toastexport success,info,warning,errorandloadingtoasts, styled throughdata-type- Markup per type, such as an icon, and a template per type, which change in place with the type
- Closes after a duration per type and waits while you hover or focus it, or while the tab is in the background
- Lets the toasts overlap or lists them, and queues them beyond
max - Updates a toast in place instead of adding the same one twice
- Action trigger, close trigger and loading toasts that follow a promise
Alt+Tmoves the focus to the toasts,Escapecloses the focused one- Screen readers announce new toasts through a live region
<primitives:toaster>
<primitives:toaster.toastTemplate>
<primitives:toast.root>
<primitives:toast.indicator />
<primitives:toast.title />
<primitives:toast.description />
<primitives:toast.actionTrigger />
<primitives:toast.closeTrigger />
</primitives:toast.root>
</primitives:toaster.toastTemplate>
</primitives:toaster>
The toaster is the region the toasts appear in and holds their settings. Its toastTemplate is the template of one toast: the client clones it for every toast and fills in the title, the description and the label of the action trigger. A part keeps what the template puts into it while the toast has no text for it. An indicator shows markup for one type, and a toastTemplate with a type is the template of one type.
A toast template holds plain markup. A component that hydrates, such as ui:button, or a ui:portal inside it is cloned without its script, so it doesn't work in a toast.
Installation
Permalink to heading "Installation"typo3 ui:add toaster toast
Read more about installing Components and Primitives.
Render the toaster once in your page layout, with the rootId its entry file mounts:
<ui:toaster rootId="toaster" />
Without content, it renders the toast template you see in the demo above, with an icon for four types. Put a ui:toaster.toastTemplate inside to use one of your own. That replaces the default template, so one of yours has no type, for the toasts of every type without a template of its own. A toast template has exactly one root element, a ui:toast.root. The entry file exports the store of the toaster as toast. Import it wherever you create a toast:
import { toast } from '../Toaster/Toaster.entry';
toast.create({ type: 'success', title: 'Message sent' });
Examples
Permalink to heading "Examples"Create a toast
Permalink to heading "Create a toast"toast.create() returns the id of the toast, for toast.update() and toast.dismiss(). A toast takes a title, a description, a type, a duration, an action, closable and a priority from 1 (highest) to 8 for the queue. The store also has remove() (without the exit animation), pause() and resume(), promise(), and success(), info(), warning(), error() and loading() as shortcuts for the type.
Pick a type
Permalink to heading "Pick a type"Each button below shows a toast of another type. The toast renders it as data-type and shows the toast.indicator of its type, the others are hidden. The toaster of the first demo has an indicator with an icon for four types, an info toast shows none. When the type changes, as in a promise, the indicators change with it. Only these five types can have an indicator and a template of their own. An icon in an indicator is decoration, so give it aria-hidden="true". For screen readers, name the type in the title, or put visually hidden text such as "Error:" into the indicator. A success toast stays for 2 seconds, the others for 5 seconds. Set duration on the toast or on the toaster to change that.
Add an action
Permalink to heading "Add an action"Pass an action with a label and an onClick. Click "Undo" in the toast: the action runs, and the toast closes.
Follow a promise
Permalink to heading "Follow a promise"toast.promise() shows a loading toast until the promise settles, then turns it into the success or the error toast.
Update a toast instead of adding it twice
Permalink to heading "Update a toast instead of adding it twice"Give the toast an id, and update it while toast.isVisible() says it is still there. Click "Save draft" a few times: one toast counts the saves and shakes on every click. Screen readers announce the new description.
List the toasts
Permalink to heading "List the toasts"The toasts overlap by default and spread out while you hover or focus them. Set overlap to false to list them one below the other. This demo has a toaster of its own, with another placement and its own mount(). Give every toaster of a page its own placement: two toasters with the same one share the id of their region, and Alt+T focuses only one of them.
Give a type a template of its own
Permalink to heading "Give a type a template of its own"A toastTemplate with a type is the template of the toasts of that type, the one without a type the template of all others. When a toast changes to a type with another template, as when a promise fails, it stays in its place and takes the content of the new template:
<ui:toaster rootId="toaster">
<ui:toaster.toastTemplate>...</ui:toaster.toastTemplate>
<ui:toaster.toastTemplate
type="{f:constant(name: 'Jramke\FluidPrimitives\Enum\ToastType::Error')}"
>
<ui:toast.root class="border-destructive">
<div class="grid flex-1 gap-1">
<ui:toast.title>Something went wrong</ui:toast.title>
<ui:toast.description />
<a
href="{f:uri.page(pageUid: settings.supportPage)}"
class="text-sm underline underline-offset-4"
>Contact support</a
>
</div>
<ui:toast.closeTrigger />
</ui:toast.root>
</ui:toaster.toastTemplate>
</ui:toaster>
The title of this template says "Something went wrong" while the toast has no title of its own.
Fill an element of your own
Permalink to heading "Fill an element of your own"Pass the data of a toast as meta, and mark the element in the template with ui:ref. A subclass of Toast fills it, and a subclass of Toaster creates that toast. This demo has a toaster of its own for the cart, and each button passes the image of its product:
Give such an element a fixed size, an image a width and a height: the toaster measures a toast when its content changes, not when an image loads.
A type change can swap every part of a toast except its root for the ones of another template. Query the parts in render(), as the cart toast does, and don't keep them or add listeners to them in init(). To animate the swap, override replaceContent() and call super.replaceContent() inside.
Style the toasts with your own CSS
Permalink to heading "Style the toasts with your own CSS"The client places every toast through CSS variables on its root, so your CSS has to use them: --x and --y for translate, --scale for scale, and --z-index, --height and --opacity. Transition them, and the toasts slide in, pile up and spread out. A closing toast has data-state="closed", and a toast behind the front one has data-sibling:
<primitives:toast.root class="toast">...</primitives:toast.root>
.toast {
translate: var(--x) var(--y);
scale: var(--scale);
z-index: var(--z-index);
height: var(--height);
opacity: var(--opacity);
transition-property: translate, scale, opacity, height;
transition-duration: 400ms;
transition-timing-function: cubic-bezier(0.21, 1.02, 0.73, 1);
}
.toast[data-state='closed'] {
transition-duration: 200ms;
}
/* a toast behind the front one takes the height of the front one, so hide what it holds */
.toast > * {
transition: opacity 400ms;
}
.toast[data-overlap][data-sibling] > * {
opacity: 0;
}
Show TYPO3 flash messages
Permalink to heading "Show TYPO3 flash messages"Write a component of your own that renders the flash messages as JSON and marks the <script> with ui:ref. f:page.footerData puts it at the end of the page, apart from the markup of your plugin:
Components/FlashMessages/FlashMessages.html:
<ui:prop name="queueIdentifier" type="string" optional="{true}" />
<f:flashMessages as="flashMessages" queueIdentifier="{queueIdentifier}">
<f:page.footerData>
<script type="application/json" {ui:ref(name: 'messages')}>{flashMessages -> f:format.json() -> f:format.raw()}</script>
</f:page.footerData>
</f:flashMessages>
<vite:asset entry="EXT:my_extension/Resources/Private/Components/FlashMessages/FlashMessages.entry.ts" />
Use it where your plugin template has f:flashMessages. It reads the same queue, the one of the plugin by default:
<ui:flashMessages />
f:format.json escapes < and >, so a message cannot end the <script> element. The entry file finds the <script> by its ref and shows the messages with the toaster. The keys of types are the values of ContextualFeedbackSeverity:
Components/FlashMessages/FlashMessages.entry.ts:
import { mountAll } from 'fluid-primitives';
import { toast } from '../ui/Toaster/Toaster.entry';
const types = { [-2]: 'info', [-1]: 'info', 0: 'success', 1: 'warning', 2: 'error' } as const;
export function showFlashMessages(
messages: { severity: keyof typeof types; title: string; message: string }[]
) {
messages.forEach(({ severity, title, message }) =>
toast.create({ type: types[severity], title, description: message })
);
}
mountAll('ui:flashMessages', ({ createHydrator }) => {
const script = createHydrator().query('messages');
if (!script) return;
showFlashMessages(JSON.parse(script.textContent ?? '[]'));
script.remove();
});
Flash messages of an AJAX request
Permalink to heading "Flash messages of an AJAX request"An action you call with extbase.post() answers with JSON, so nothing renders its flash messages. Send them along in the response and flush the queue, otherwise they show up on the next page load:
public function subscribeAction(Subscriber $subscriber): ResponseInterface
{
$this->subscriberRepository->add($subscriber);
$this->addFlashMessage('Check your inbox to confirm the subscription.', 'Almost done');
return $this->jsonResponse(json_encode([
'flashMessages' => $this->getFlashMessageQueue()->getAllMessagesAndFlush(),
], JSON_THROW_ON_ERROR));
}
import { extbase } from 'fluid-primitives';
import { showFlashMessages } from '../FlashMessages/FlashMessages.entry';
const response = await extbase.post(url, values);
showFlashMessages((await response.json()).flashMessages);
API Reference
Permalink to heading "API Reference"The following tables cover the available props of the Fluid Primitives. For a full list of available client side props and methods, see the Zag.js Machine API.
The region the toasts appear in, with their settings. Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
placement | Enum\ToastPlacementWhere on the screen the toasts appear. | No | BottomEnd |
overlap | booleanWhether the toasts pile up on top of each other and spread out on hover or focus. Set it to `false` to list them one below the other. | No | true |
max | integerHow many toasts are visible at once. More toasts wait in a queue, the most important first. When they overlap, each toast behind the front one is 5% smaller than the one before, so far more than 10 turn the pile into an hourglass. | No | 10 |
gap | integerThe space between the toasts, in pixels. | No | 16 |
offsets | string|arrayThe distance from the edges of the viewport, a CSS length or an array with one for each of `top`, `right`, `bottom` and `left`. | No | '1rem' |
duration | integerHow long a toast stays, in milliseconds. Without it, the type of the toast decides: 2 seconds for `success`, 5 seconds for the others, and a `loading` toast stays until it is updated. | No | - |
removeDelay | integerHow long a dismissed toast stays in the DOM, in milliseconds, so its exit animation can play. | No | 200 |
pauseOnPageIdle | booleanWhether the toasts wait while the browser tab is in the background. | No | true |
hotkey | string[]The keys that move the focus to the toasts, as `KeyboardEvent` modifier properties and codes, `altKey` and `KeyT` (Alt+T) by default. | No | - |
translations | arrayLocalized labels: `groupLabel` names the region of the toasts for screen readers, `closeTriggerLabel` the close trigger of each toast. Use `f:translate` for per-template localization overrides when needed. | No | - |
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
rootId | stringThe root ID of the component, used for hydration and identification. | No | - |
ids | arrayThe IDs of of the component parts for composition. | No | [] |
autoMount | booleanWhether the client initializes the component on its own. Set it to false to mount it yourself with `mount()` | No | true |
provider | stringThe `id` of the `.provider` this root takes its `rootId` and props from. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
toaster.toastTemplate
Permalink to heading "toaster.toastTemplate"The template of one toast, with a toast.root as its root element, or of the toasts of one type. Renders a <template> element.
| Name | Description | Required | Default |
|---|---|---|---|
type | Enum\ToastTypeThe type of toast this template is for. A toast takes the template of its type, or the one without a type. | No | - |
The following tables cover the available props of the Fluid Primitives. For a full list of available client side props and methods, see the Zag.js Machine API.
toast.root
Permalink to heading "toast.root"One toast, the root element of the toast template. Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
rootId | stringThe root ID of the component, used for hydration and identification. | No | - |
ids | arrayThe IDs of of the component parts for composition. | No | [] |
autoMount | booleanWhether the client initializes the component on its own. Set it to false to mount it yourself with `mount()` | No | true |
provider | stringThe `id` of the `.provider` this root takes its `rootId` and props from. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
Rendered data attributes
| Attribute | Description |
|---|---|
data-toast-root | <uid> |
data-state | "open" | "closed" |
data-type | The type of the item |
data-placement | The placement of the toast |
data-align | |
data-side | The side of the trigger that the toast is positioned on |
data-mounted | Present when mounted |
data-paused | Present when paused |
data-first | |
data-sibling | |
data-stack | |
data-overlap | Present when overlapping |
toast.indicator
Permalink to heading "toast.indicator"Markup for one type of toast, such as its icon, hidden while the toast has another type. Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
type | Enum\ToastTypeThe type of toast the indicator is shown for. | Yes | - |
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
toast.title
Permalink to heading "toast.title"The title of the toast, or what the template puts into it while the toast has none. Hidden when both are empty. Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
toast.description
Permalink to heading "toast.description"The description of the toast, or what the template puts into it while the toast has none. Hidden when both are empty. Renders a <div> element.
| Name | Description | Required | Default |
|---|---|---|---|
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
toast.actionTrigger
Permalink to heading "toast.actionTrigger"Runs the action of the toast and closes it, hidden when the toast has no action. Renders a <button> element.
| Name | Description | Required | Default |
|---|---|---|---|
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
toast.closeTrigger
Permalink to heading "toast.closeTrigger"Closes the toast, hidden when it is not closable. Renders a <button> element.
| Name | Description | Required | Default |
|---|---|---|---|
attributes | arrayAdditional attributes that should be rendered on the component where ui:attributes is used. | No | [] |
asChild | booleanIf true the component uses its child only without the component template. Like Radix UI asChild or Base UI render props. | No | - |
class | stringThe CSS class(es) to be applied to the component. | No | - |
Machine JavaScript API
Permalink to heading "Machine JavaScript API"| Name | Type | Description |
|---|---|---|
getCount | () => number | The total number of toasts |
getToasts | () => ToastProps[] | The toasts |
subscribe | (callback: (toasts: Options<O>[]) => void) => VoidFunction | Subscribe to the toast group |
getGroupState | () => GroupState | Returns the state of the group |