Skip to content

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.

React
tsx
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.' });
dart
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

PropTypeDefaultDescription
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
timeoutnumber5000How 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
limitnumber3How many are shown at once. The rest are kept and revealed as the stack drains rather than being thrown away
widthnumber | string380How wide a toast is allowed to get. Numbers are pixels
closeLabelstring'Close'Accessible name of every toast’s × button. Never drawn
childrenReactNodeThe application. Wrap it once
PropTypeDefaultDescription
child * WidgetThe application. Wrap it once
positionPlToastPositionPlToastPosition.bottomEndWhere 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
timeoutDurationDuration(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
limitint3How many are shown at once. The rest are kept and revealed as the stack drains rather than being thrown away
widthdouble380How wide a toast is allowed to get, in logical pixels
closeLabelString'Close'Accessible name of every toast’s × button. Never drawn
variantsharedPlassVariantPlassVariant.glassWhat a toast is made of. The two undyed materials are the glass at its most opaque, because what is behind a toast is arbitrary
sizesharedPlassSizePlassSize.mdPadding and type scale of a toast
colorsharedPlassColorPlassColor.primaryThe default colour family. A single toast overrides it in add
densitysharedPlassDensityPlassDensity.standardPadding 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

PropTypeDefaultDescription
add(options: PlToastOptions) => stringRaises a toast and returns its id
update(id: string, options: PlToastOptions) => voidChanges a toast already on screen. Reusing an id updates it in place and restarts its timer
close(id?: string) => voidCloses one toast, or every toast when called with nothing
promisepromise(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
toastsToastObject[]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

PropTypeDefaultDescription
titleReactNodeThe headline
descriptionReactNodeThe detail under it. A toast with only this is a one-line toast
idstringReusing one updates that toast in place
timeoutnumberThis 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
iconReactNode | falseThe glyph at the start. Omitted it follows color, false drops it, a node replaces it
actionLabelReactNodeThe label of the action button. Passing it is what makes the button appear
onAction(event: MouseEvent) => voidCalled when the action button is pressed
onClose() => voidCalled when the toast closes, however it closed
onRemove() => voidCalled once it has finished animating out and left the DOM
classNamestringClasses on this toast, alongside the component's own rather than in place of them
styleCSSPropertiesInline 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.

PropTypeDefaultDescription
showString Function(PlToast toast)Raises a toast and hands back its id. An id already on screen is updated in place
updatevoid Function(String id, PlToast toast)Changes a toast already on screen. Reusing an id updates it in place and restarts its timer
closevoid Function([String? id])Closes one toast, or every toast when called with nothing
showFutureFuture<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.

PropTypeDefaultDescription
titleWidget?The headline
descriptionWidget?The detail under it. A toast with only this is a one-line toast
idString?Reusing one updates that toast in place
timeoutDuration?This toast's own lifetime. Duration.zero keeps it up until it is closed
priorityPlToastPriorityPlToastPriority.lowhigh is announced the moment it arrives; low waits until the reader reaches it
colorsharedPlassColor?Overrides the provider’s colour family for this toast alone
variantsharedPlassVariant?Overrides the provider’s material for this toast alone
iconWidget?The glyph before the message. The severity's own mark if it is left out
showIconbooltrueDraws a glyph at all. Dart has no value that is neither null nor a widget, so "take it away" gets its own name
actionLabelWidget?The label of the action button. Passing it is what makes the button appear
onActionVoidCallback?Called when the action button is pressed
onCloseVoidCallback?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.

React

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.

React
React

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.

React

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.

React

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.
  • priority picks the live region. high interrupts whatever is being read and low waits 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 limit stays in the DOM so it can come back, and says nothing while it waits.
  • The stack is pointer-events-none across 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.
  • priority decides whether a toast is a live region. high is announced the moment it arrives and low waits 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 two roles 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 limit has 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

ReactFlutterWhy
usePlToast()PlToastProvider.of(context)Flutter's own way of reaching the thing above you.
add, close, update, promiseshow, close, update, showFutureThe same four, in Dart's words.
PlToastOptionsPlToastThe message is the thing named after the component, because it is the thing a caller writes.
timeout in millisecondsDurationDart's own type for a length of time. Duration.zero still means "until it is closed".
icon: falseshowIcon: falseDart 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 notFlutter has one live-region flag rather than two politeness levels.
a portal, and pointer-events-nonea layer inside the providerThe 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 hotkeyNeither 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 hoverthe clock starts over when the pointer leavesA toast the reader has just finished reading deserves its full life back rather than the two seconds it had left.
className, styleThere is no class list and no style attribute to pass through.

Released under the MIT License