Skip to content
Destyler UI Destyler UI Destyler UI

Toast

The toast component is used to give feedback to users after an action has taken place.

Features

Install

Install the component from your command line.

Terminal window
      
        
npm install @destyler/toast @destyler/vue
Terminal window
      
        
npm install @destyler/toast @destyler/react
Terminal window
      
        
npm install @destyler/toast @destyler/svelte
Terminal window
      
        
npm install @destyler/toast @destyler/solid

Anatomy

Import all parts and piece them together.

<script setup lang="ts">
import { normalizeProps, useMachine } from '@destyler/vue'
import { computed, useId } from 'vue'
import * as toast from '@destyler/toast'
const [groupState, groupSend] = useMachine(
toast.group.machine({
id: useId(),
}),
)
const groupApi = computed(() => toast.group.connect(groupState.value as any, groupSend, normalizeProps))
const [state, send] = useActor(placement)
const api = computed(() => toast.connect(state.value, send, normalizeProps))
</script>
<template>
<Teleport to="body">
<div v-for="placement in groupApi.getPlacements()" :key="placement" v-bind="groupApi.getGroupProps({ placement })">
<div v-bind="api.getRootProps()">
<div v-bind="api.getGhostBeforeProps()" />
<p v-bind="api.getTitleProps()">
{{ api.title }}
</p>
<p v-bind="api.getDescriptionProps()">
{{ api.description }}
</p>
<button v-bind="api.getCloseTriggerProps()" />
<div v-bind="api.getGhostAfterProps()" />
</div>
</div>
</Teleport>
</template>
import { normalizeProps, Portal, useMachine } from '@destyler/react'
import * as toast from '@destyler/toast'
import { useId } from 'react'
export default function Toast() {
const [groupState, groupSend] = useMachine(
toast.group.machine({
id: useId(),
}),
)
const groupApi = toast.group.connect(groupState as any, groupSend, normalizeProps)
const [state, send] = useActor(toast)
const api = computed(() => toast.connect(state.value, send, normalizeProps))
return (
<Portal>
{groupApi.getPlacements().map(placement => (
<div data-layout="sinppets" key={placement} {...groupApi.getGroupProps({ placement })}>
{groupApi.getToastsByPlacement(placement).map(toast => (
<div {...api.getRootProps()}>
<div {...api.getGhostBeforeProps()}></div>
<p {...api.getTitleProps()}>
{api.title}
</p>
<p {...api.getDescriptionProps()}>
{api.description}
</p>
<button {...api.getCloseTriggerProps()} />
<div {...api.getGhostAfterProps()} />
</div>
))}
</div>
))}
</Portal>
)
}
<script lang="ts">
import * as toast from '@destyler/toast'
import {normalizeProps, useMachine, portal} from '@destyler/svelte'
import ToastItem from './Item.svelte';
import './style.css'
const id = $props.id()
const [groupState, groupSend] = useMachine(
toast.group.machine({
id: id,
placement: 'bottom-end',
overlap: true,
removeDelay: 200,
}),
)
const groupApi = $derived(toast.group.connect(groupState as any, groupSend, normalizeProps))
const [state, send] = useActor(placement)
const api = computed(() => toast.connect(state.value, send, normalizeProps))
</script>
<div use:portal>
{#each groupApi.getPlacements() as placement}
<div key={placement} data-layout="sinppets" {...groupApi.getGroupProps({ placement })}>
{#each groupApi.getToastsByPlacement(placement) as toast (toast.id)}
<div {...api.getRootProps()}>
<div {...api.getGhostBeforeProps()}></div>
<p {...api.getTitleProps()}>{api.title}</p>
<p {...api.getDescriptionProps()}>{api.description}</p>
<button {...api.getCloseTriggerProps()} />
<div {...api.getGhostAfterProps()}></div>
</div>
{/each}
</div>
{/each}
</div>
import { normalizeProps, useMachine } from '@destyler/solid'
import * as toast from '@destyler/toast'
import { createMemo, createUniqueId, For } from 'solid-js'
import { Portal } from 'solid-js/web'
export default function Toast() {
const [groupState, groupSend] = useMachine(
toast.group.machine({
id: createUniqueId(),
}),
)
const groupApi = createMemo(() => toast.group.connect(groupState as any, groupSend, normalizeProps))
const [state, send] = useActor(placement)
const api = computed(() => toast.connect(state.value, send, normalizeProps))
return (
<Portal>
<For each={groupApi().getPlacements()}>
{placement => (
<div {...groupApi().getGroupProps({ placement })}>
<For each={groupApi().getToastsByPlacement(placement)}>
{toast => (
<div {...api().getRootProps()}>
<div {...api().getGhostBeforeProps()}></div>
<p {...api().getTitleProps()}>
{api().title}
</p>
<p {...api().getDescriptionProps()}>
{api().description}
</p>
<button {...api().getCloseTriggerProps()}>
<div class="i-ph-x-bold"></div>
</button>
<div {...api().getGhostAfterProps()} />
</div>
)}
</For>
</div>
)}
</For>
</Portal>
)
}

Usage

Toast Group

  • toast.group.machine

The state machine representation of a group of toasts. It is responsible for spawning, updating and removing toasts.

  • toast.group.connect

function gives you access to methods you can use to add, update, and remove a toast.

Toast Item

  • toast.machine

The state machine representation of a single toast.

  • toast.connect

The function that takes the toast machine and returns methods and JSX properties.

Creating a toast

There are five toast types that can be created with the toast machine. info, success, loading, custom and error.

To create a toast, use the toast.create(...) method.

toast.create({
title: "Hello World",
description: "This is a toast",
type: "info",
})

The options you can pass in are:

title

string

The title of the toast

description

string

The description of the toast

type

string | "success" | "error" | "loading" | "info"

The type of the toast. Can be either error, success , info, loading

duration

number

The duration of the toast. The default duration is computed based on the specified type.

onStatusChange

(details: StatusChangeDetails) => void

A callback that listens for the status changes across the toast lifecycle.

placement

"top-start" | "top" | "top-end" | "bottom-start" | "bottom" | "bottom-end"

The placement of the toast.

removeDelay

number

The delay before unmounting the toast from the DOM. Useful for transition

Changing the placement

Use the placement property when you call the toast.create(...) to change the position of the toast.

toast.info({
title: "Hello World",
description: "This is a toast",
placement: "top-start",
})

Overlapping toasts

When multiple toasts are created, they are rendered in a stack. To make the toasts overlap, set the overlap property to true.

const [state, send] = useMachine(
toast.group.machine({
overlap: true,
}),
)

When using overlap, the toast’s placement must match the placement of the toast group (which is bottom by default).

::: tip Be sure to set up the required styles to make the toasts overlap correctly. :::

Changing the duration

Every toast has a default visible duration depending on the type set. Here’s the following toast types and matching default durations:

  • info - 5000ms
  • success - 5000ms
  • error - 2000ms
  • loading - indefinite (until updated)

You can override the duration of the toast by passing the duration property to the toast.create(...) function.

toast.create({
title: "Hello World",
description: "This is a toast",
type: "info",
duration: 6000,
})

You can also use the toast.upsert(...) function which creates or updates a toast.

Programmatic control

To update a toast programmatically, you need access to the unique identifier of the toast.

This identifier can be either:

  • the id passed into toast.create(...) or,

  • the returned random id when the toast.create(...) is called.

You can use any of the following methods to control a toast:

  • toast.upsert(...) — Creates or updates a toast.

  • toast.update(...) — Updates a toast.

  • toast.remove(...) — Removes a toast instantly without delay.

  • toast.dismiss(...) — Removes a toast with delay.

  • toast.pause(...) — Pauses a toast.

  • toast.resume(...) — Resumes a toast.

// grab the id from the created toast
const id = toast.create({
title: "Hello World",
description: "This is a toast",
type: "info",
duration: 6000,
placement: "top-start",
})
// update the toast
toast.update(id, {
title: "Hello World",
description: "This is a toast",
type: "success",
})
// remove the toast
toast.remove(id)
// dismiss the toast
toast.dismiss(id)

Handling promises

The toast group API exposes a toast.promise() function to allow you update the toast when it resolves or rejects.

With the promise API, you can pass the toast options for each promise lifecycle.

toast.promise(promise, {
loading: {
title: "Loading",
description: "Please wait...",
},
success: (data) => ({
title: "Success",
description: "An request has success",
}),
error: (err) => ({
title: "Error",
description: "An request has error",
}),
})

Pausing the toasts

There are three scenarios we provide to pause a toast from timing out:

  • When the document loses focus or the page is idle (e.g. switching to a new browser tab), controlled via the pauseOnPageIdle context property.

  • When the toast.pause(id) provided by the toast.group.connect(...) is called.

// Global pause options
const [state, send] = useMachine(
toast.group.machine({
pauseOnPageIdle: true,
}),
)
// Programmatically pause a toast (by `id`)
// `id` is the return value of `api.create(...)`
toast.pause(id)

Limiting the number of toasts

Toasts are great but displaying too many of them can sometimes hamper the user experience. To limit the number of visible toasts, pass the max property to the group machine’s context.

const [state, send] = useMachine(
toast.group.machine({
max: 10,
}),
)

Focus Hotkey for toasts

When a toast is created, you can focus the toast region by pressing the alt + T. This is useful for screen readers and keyboard navigation.

Set the hotkey context property to change the underlying hotkey.

const [state, send] = useMachine(
toast.group.machine({
hotkey: ["F6"],
}),
)

Listening for toast lifecycle

When a toast is created, you can listen for the status changes across its lifecycle using the onStatusChange callback when you call toast.create(...).

The status values are:

  • visible - The toast is mounted and rendered

  • dismissed - The toast is visually invisible but still mounted

  • unmounted - The toast has been completely unmounted and no longer exists

toast.info({
title: "Hello World",
description: "This is a toast",
type: "info",
onStatusChange: (details) => {
// details => { status: "visible" | "dismissed" | "unmounted" }
console.log("Toast status:", details)
},
})

Changing the gap between toasts

When multiple toasts are rendered, a gap of 16px is applied between each toast. To change this value, set the gap context property.

const [state, send] = useMachine(
toast.group.machine({
gap: 24,
}),
)

Changing the offset

The toast region has a default 16px offset from the viewport. Use the offset context property to change the offset.

const [state, send] = useMachine(
toast.group.machine({
offsets: "24px",
}),
)

Styling guide

Earlier, we mentioned that each Tabs part has a data-part attribute added to them to select and style them in the DOM.

Requirement

The toast machine injects a bunch of css variables that are required for it to work. You need to connect these variables in your styles.

[data-part="root"] {
translate: var(--x) var(--y);
scale: var(--scale);
z-index: var(--z-index);
height: var(--height);
opacity: var(--opacity);
will-change: translate, opacity, scale;
}

To make it transition smoothly, you should includes transition properties.

[data-part="root"] {
transition:
translate 400ms,
scale 400ms,
opacity 400ms;
transition-timing-function: cubic-bezier(0.21, 1.02, 0.73, 1);
}
[data-part="root"][data-state="closed"] {
transition:
translate 400ms,
scale 400ms,
opacity 200ms;
transition-timing-function: cubic-bezier(0.06, 0.71, 0.55, 1);
}

Toast styling

When a toast is created and the api.getRootProps() from the toast.connect is used, the toast will have a data-type that matches the specified type at its creation.

You can use this property to style the toast.

[data-part="root"][data-type="info"] {
/* Styles for the specific toast type */
}
[data-part="root"][data-type="error"] {
/* Styles for the error toast type */
}
[data-part="root"][data-type="success"] {
/* Styles for the success toast type */
}
[data-part="root"][data-type="loading"] {
/* Styles for the loading toast type */
}

Methods and Properties

Machine Context

The toast machine exposes the following context properties:

pauseOnPageIdle(default: false)
boolean
Whether to pause toast when the user leaves the browser tab
gap(default: 16)
number
The gap or spacing between toasts
max(default: Number.MAX_SAFE_INTEGER)
number
The maximum number of toasts that can be shown at once
offsets(default: "1rem")
string | Record<"left" | "right" | "bottom" | "top", string>
The offset from the safe environment edge of the viewport
hotkey(default: '["altKey", "KeyT"]')
string[]
The hotkey that will move focus to the toast group
overlap
boolean
Whether the toasts should overlap each other
placement
Placement
The placement of the toast
removeDelay(default: 200)
number
The duration for the toast to kept alive before it is removed. Useful for exit transitions.
duration
number
The duration the toast will be visible
dir(default: "ltr")
"ltr" | "rtl"
The document's text/writing direction.
id
string
The unique identifier of the machine.
getRootNode
() => ShadowRoot | Node | Document
A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.

Machine API

The switch api exposes the following methods:

getCount
() => number
The total number of toasts
getPlacements
() => Placement[]
The placements of the active toasts
getToastsByPlacement
(placement: Placement) => Service<O>[]
The active toasts by placement
isVisible
(id: string) => boolean
Returns whether the toast id is visible
create
(options: Options<O>) => string
Function to create a toast.
upsert
(options: Options<O>) => string
Function to create or update a toast.
update
(id: string, options: Options<O>) => void
Function to update a toast's options by id.
success
(options: Options<O>) => string
Function to create a success toast.
error
(options: Options<O>) => string
Function to create an error toast.
loading
(options: Options<O>) => string
Function to create a loading toast.
resume
(id?: string) => void
Function to resume a toast by id.
pause
(id?: string) => void
Function to pause a toast by id.
dismiss
(id?: string) => void
Function to dismiss a toast by id. If no id is provided, all toasts will be dismissed.
dismissByPlacement
(placement: Placement) => void
Function to dismiss all toasts by placement.
remove
(id?: string) => void
Function to remove a toast by id. If no id is provided, all toasts will be removed.
promise
<T>(promise: Promise<T> | (() => Promise<T>), options: Options<O>>) => string
Function to create a toast from a promise. - When the promise resolves, the toast will be updated with the success options. - When the promise rejects, the toast will be updated with the error options.
subscribe
(callback: (toasts: Options<O>[]) => void) => VoidFunction
Function to subscribe to the toast group.

Data Attributes

Root

attribute
description
data-scope
toast
data-part
root
data-state
"open" | "closed"
data-type
The type of the item
data-placement
The placement of the toast
data-align
data-side
data-mounted
Present when mounted
data-paused
Present when paused
data-first
data-sibling
data-stack
data-overlap
Present when overlapping

Ghost Before

attribute
description
data-scope
toast
data-part
ghost-before
data-ghost

Ghost After

attribute
description
data-scope
toast
data-part
ghost-after
data-ghost