PlToast
A message that appears on its own, says what happened, and leaves. Wrap the application in a PlToastProvider once, and raise one from anywhere under it.
import { PlToastProvider, usePlToast } from 'plass-ui';
<PlToastProvider>
<App />
</PlToastProvider>;
// anywhere under it
const toast = usePlToast();
toast.add({ color: 'success', title: 'Saved', description: 'Your changes are live.' });import 'package:plass_ui/plass_ui.dart';
PlToastProvider(child: const App());
// anywhere under it
PlToastProvider.of(context).show(
const PlToast(
color: PlassColor.success,
title: Text('Saved'),
description: Text('Your changes are live.'),
),
);Props
PlToastProvider
| Prop | Type | Default | Description |
|---|---|---|---|
| variantshared | 'solid' | 'glass' | 'ghost' | 'glass' | What a toast is made of. The two undyed materials are the glass at its most opaque, because what is behind a toast is arbitrary |
| sizeshared | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | Padding and type scale of a toast |
| colorshared | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | The default colour family. A single toast overrides it in add |
| densityshared | 'default' | 'compact' | 'default' | Padding only |
| position | 'top-start' | 'top-center' | 'top-end' | 'bottom-start' | 'bottom-center' | 'bottom-end' | 'bottom-end' | Where the stack sits. One word rather than a side plus an align pair, because they are not independent: a toast stack is always pinned to the top or the bottom, never to a side |
| timeout | number | 5000 | How long a toast lasts by default, in milliseconds. 0 keeps it up until it is closed, which is right for anything the reader has to act on: a toast that leaves before it is read said nothing |
| limit | number | 3 | How many are shown at once. The rest are kept and revealed as the stack drains rather than being thrown away |
| width | number | string | 380 | How wide a toast is allowed to get. Numbers are pixels |
| closeLabel | string | 'Close' | Accessible name of every toast’s × button. Never drawn |
| children | ReactNode | — | The application. Wrap it once |
| Prop | Type | Default | Description |
|---|---|---|---|
| child * | Widget | — | The application. Wrap it once |
| position | PlToastPosition | PlToastPosition.bottomEnd | Where the stack sits. One word rather than a side plus an align pair, because they are not independent: a toast stack is always pinned to the top or the bottom, never to a side |
| timeout | Duration | Duration(seconds: 5) | How long a toast lasts by default. Duration.zero keeps it up until it is closed, which is right for anything the reader has to act on |
| limit | int | 3 | How many are shown at once. The rest are kept and revealed as the stack drains rather than being thrown away |
| width | double | 380 | How wide a toast is allowed to get, in logical pixels |
| closeLabel | String | 'Close' | Accessible name of every toast’s × button. Never drawn |
| variantshared | PlassVariant | PlassVariant.glass | What a toast is made of. The two undyed materials are the glass at its most opaque, because what is behind a toast is arbitrary |
| sizeshared | PlassSize | PlassSize.md | Padding and type scale of a toast |
| colorshared | PlassColor | PlassColor.primary | The default colour family. A single toast overrides it in add |
| densityshared | PlassDensity | PlassDensity.standard | Padding only |
Everything about how a toast looks is decided on the provider (where the stack sits, how wide it is, which material it wears, how long it lasts), so the call site stays the one thing it should be: what happened.
There is no elevation. A toast floats over the page, so its shadow is always at the top of the ladder, the same as the PlSelect list, the PlModal sheet and the PlTooltip plate.
usePlToast
| Prop | Type | Default | Description |
|---|---|---|---|
| add | (options: PlToastOptions) => string | — | Raises a toast and returns its id |
| update | (id: string, options: PlToastOptions) => void | — | Changes a toast already on screen. Reusing an id updates it in place and restarts its timer |
| close | (id?: string) => void | — | Closes one toast, or every toast when called with nothing |
| promise | promise(work, { loading, success, error }) | — | One toast that follows a promise. Base UI applies timeout 0 to the loading state, so a slow request cannot dismiss its own toast |
| toasts | ToastObject[] | — | Every toast currently in the stack, newest first |
usePlToast is not in the Flutter package yet.
A hook rather than a component, because the thing a caller has at the moment a toast is warranted is a click handler, not a place in the tree, and a <PlToast open={…} /> they would have to keep mounted, with a piece of state per message, is the shape this component exists to avoid.
PlToastOptions
| Prop | Type | Default | Description |
|---|---|---|---|
| title | ReactNode | — | The headline |
| description | ReactNode | — | The detail under it. A toast with only this is a one-line toast |
| id | string | — | Reusing one updates that toast in place |
| timeout | number | — | This toast’s own lifetime. 0 keeps it up until it is closed |
| priority | 'low' | 'high' | 'low' | high interrupts a screen reader; low waits for a pause. An error is worth interrupting for and a save confirmation is not |
| colorshared | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | — | Overrides the provider’s colour family for this toast alone |
| variantshared | 'solid' | 'glass' | 'ghost' | — | Overrides the provider’s material for this toast alone |
| icon | ReactNode | false | — | The glyph at the start. Omitted it follows color, false drops it, a node replaces it |
| actionLabel | ReactNode | — | The label of the action button. Passing it is what makes the button appear |
| onAction | (event: MouseEvent) => void | — | Called when the action button is pressed |
| onClose | () => void | — | Called when the toast closes, however it closed |
| onRemove | () => void | — | Called once it has finished animating out and left the DOM |
| className | string | — | Classes on this toast, alongside the component's own rather than in place of them |
| style | CSSProperties | — | Inline styles on this toast, applied over the custom properties it wrote |
PlToastOptions is not in the Flutter package yet.
PlToastController
PlToastController is not in the React package yet.
| Prop | Type | Default | Description |
|---|---|---|---|
| show | String Function(PlToast toast) | — | Raises a toast and hands back its id. An id already on screen is updated in place |
| update | void Function(String id, PlToast toast) | — | Changes a toast already on screen. Reusing an id updates it in place and restarts its timer |
| close | void Function([String? id]) | — | Closes one toast, or every toast when called with nothing |
| showFuture | Future<T> Function(Future<T>, {loading, success, failure}) | — | One toast that follows a future. The loading state is held open, so a slow request cannot dismiss its own toast |
PlToastProvider.of(context) hands one back. A controller rather than a widget, because the thing a caller has at the moment a toast is warranted is a callback, not a place in the tree, and a PlToast(open: …) they would have to keep mounted, with a piece of state per message, is the shape this exists to avoid.
PlToast
PlToast is not in the React package yet.
| Prop | Type | Default | Description |
|---|---|---|---|
| title | Widget? | — | The headline |
| description | Widget? | — | The detail under it. A toast with only this is a one-line toast |
| id | String? | — | Reusing one updates that toast in place |
| timeout | Duration? | — | This toast's own lifetime. Duration.zero keeps it up until it is closed |
| priority | PlToastPriority | PlToastPriority.low | high is announced the moment it arrives; low waits until the reader reaches it |
| colorshared | PlassColor? | — | Overrides the provider’s colour family for this toast alone |
| variantshared | PlassVariant? | — | Overrides the provider’s material for this toast alone |
| icon | Widget? | — | The glyph before the message. The severity's own mark if it is left out |
| showIcon | bool | true | Draws a glyph at all. Dart has no value that is neither null nor a widget, so "take it away" gets its own name |
| actionLabel | Widget? | — | The label of the action button. Passing it is what makes the button appear |
| onAction | VoidCallback? | — | Called when the action button is pressed |
| onClose | VoidCallback? | — | Called when the toast closes, however it closed |
PlToast is the message, not a widget: it is what show is handed. Nothing here is put in the tree by the caller.
What the shared axes (variant size color density) mean across the library is in prop conventions.
Examples
position
One word rather than a side plus an align pair, because the two are not independent: a toast stack is always pinned to the top or the bottom, never to a side, and offering left/right as a "side" would invite a stack down the middle of the screen that nothing in the layout survives.
variant and color
Both are provider defaults that a single toast overrides, so a page can have one house style and still make the one error look like an error.
Each family draws its own shape as well as its own colour, a toast that says "this went wrong" only in red says it only to some readers.
The action, and timeout: 0
Passing actionLabel is what makes the action button appear. Anything the reader has to act on should also carry timeout: 0, because a toast that leaves before it is read said nothing.
update
Reusing an id updates that toast in place and restarts its timer, which is what "uploading… / uploaded" wants: one toast that changed its mind, not two stacked on each other.
promiseshowFuture
One toast that follows a promisefuture: the loading message while it runs, then the success or the failure. The loading state is held open whatever it asked for, so a slow request cannot dismiss its own toast, and the same toast becomes the answer, so a reader who watched it start sees it finish rather than seeing a second one appear beside it.
Accessibility
- Base UI owns the parts that are genuinely hard and invisible when they work: the timers and their pausing on hover and on window blur, the limit, the swipe, the F6 focus hotkey, and the live region that makes a message which appeared out of nowhere reach a screen reader at all.
prioritypicks the live region.highinterrupts whatever is being read andlowwaits for a pause. An error is worth interrupting for and a save confirmation is not.- The × is deliberately not in the page's tab order and is hidden from the accessibility tree. A screen reader reaches a toast with F6 and is given the close action there, rather than finding a stray button from a message that may already be gone.
- A toast pushed out by
limitstays in the DOM so it can come back, and says nothing while it waits. - The stack is
pointer-events-noneacross its full width, so the strip along the top or the bottom of the page is not a wall the rest of the app is behind. The toasts themselves take their events back.
prioritydecides whether a toast is a live region.highis announced the moment it arrives andlowwaits until the reader reaches it. An error is worth interrupting for and a save confirmation is not. Flutter has one live-region flag rather than two politeness levels, so what the React build says with tworoles this says with one switch.- The pointer resting on the stack stops the clock, because a pointer resting on a toast is a reader reading it. It starts over when the pointer leaves, rather than resuming where it left off.
- A toast waiting behind
limithas no clock at all: it is not being read, so its life has not started. It gets one when it reaches the screen. - Nothing here is told to ignore the pointer, and nothing has to be: the strip is an
Align, which hit-tests its child and not the room around it, so the page under the empty part of the strip is reached normally. - The × and the action are ordinary focus stops on the toast itself.
Differences from the React build
| React | Flutter | Why |
|---|---|---|
usePlToast() | PlToastProvider.of(context) | Flutter's own way of reaching the thing above you. |
add, close, update, promise | show, close, update, showFuture | The same four, in Dart's words. |
PlToastOptions | PlToast | The message is the thing named after the component, because it is the thing a caller writes. |
timeout in milliseconds | Duration | Dart's own type for a length of time. Duration.zero still means "until it is closed". |
icon: false | showIcon: false | Dart has no value that is neither null nor a widget, so "take it away" gets its own name. |
priority: 'high' | 'low' | a live region, or not | Flutter has one live-region flag rather than two politeness levels. |
a portal, and pointer-events-none | a layer inside the provider | The provider is already above everything it has to cover, so there is nothing to portal into, and an Align lets the pointer past without being told to. |
| swipe to dismiss, the F6 hotkey | — | Neither has a Flutter equivalent that is not a second gesture competing with the app's own. The × is always there. |
| timers pause and resume on hover | the clock starts over when the pointer leaves | A toast the reader has just finished reading deserves its full life back rather than the two seconds it had left. |
className, style | — | There is no class list and no style attribute to pass through. |