Skip to content

Toast

A succinct message that is displayed temporarily.
vue
vue
<script setup lang="ts">
import { ToastAction, ToastDescription, ToastRoot, ToastTitle, ToastViewport, useToastProvider } from '@oku-ui/primitives'
import { ref } from 'vue'

const open = ref(false)
const eventDateRef = ref(new Date())
const timerRef = ref(0)

function oneWeekAway() {
  const now = new Date()
  const inOneWeek = now.setDate(now.getDate() + 7)
  return new Date(inOneWeek)
}

function prettyDate(date: Date) {
  return new Intl.DateTimeFormat('en-US', { dateStyle: 'full', timeStyle: 'short' }).format(date)
}

function handleClick() {
  open.value = false
  window.clearTimeout(timerRef.value)
  timerRef.value = window.setTimeout(() => {
    eventDateRef.value = oneWeekAway()
    open.value = true
  }, 100)
}
useToastProvider()
</script>

<template>
  <button
    class="inline-flex items-center justify-center rounded font-medium text-[15px] px-[15px] leading-[35px] h-[35px] bg-white text-mauve12 shadow-[0_2px_10px] shadow-blackA7 outline-none hover:bg-mauve3 focus:shadow-[0_0_0_2px] focus:shadow-black"
    @click="handleClick"
  >
    Add to calendar
  </button>

  <ToastRoot
    v-model:open="open"
    class="bg-white rounded-md shadow-[hsl(206_22%_7%_/_35%)_0px_10px_38px_-10px,_hsl(206_22%_7%_/_20%)_0px_10px_20px_-15px] p-[15px] grid [grid-template-areas:_'title_action'_'description_action'] grid-cols-[auto_max-content] gap-x-[15px] items-center data-[state=open]:animate-slideIn data-[state=closed]:animate-hide data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=cancel]:translate-x-0 data-[swipe=cancel]:transition-[transform_200ms_ease-out] data-[swipe=end]:animate-swipeOut"
  >
    <ToastTitle class="[grid-area:_title] mb-[5px] font-medium text-slate12 text-[15px]">
      Scheduled: Catch up
    </ToastTitle>
    <ToastDescription as="template">
      <time
        class="[grid-area:_description] m-0 text-slate11 text-[13px] leading-[1.3]"
        :dateTime="eventDateRef.toISOString()"
      >
        {{ prettyDate(eventDateRef) }}
      </time>
    </ToastDescription>
    <ToastAction
      class="[grid-area:_action]"
      as="template"
      alt-text="Goto schedule to undo"
    >
      <button class="inline-flex items-center justify-center rounded font-medium text-xs px-[10px] leading-[25px] h-[25px] bg-indigo2 text-indigo11 shadow-[inset_0_0_0_1px] shadow-indigo7 hover:shadow-[inset_0_0_0_1px] hover:shadow-indigo8 focus:shadow-[0_0_0_2px] focus:shadow-indigo8">
        Undo
      </button>
    </ToastAction>
  </ToastRoot>
  <ToastViewport class="[--viewport-padding:_25px] fixed bottom-0 right-0 flex flex-col p-[var(--viewport-padding)] gap-[10px] w-[390px] max-w-[100vw] m-0 list-none z-[2147483647] outline-none" />
</template>
  • Automatically closes.
  • Pauses closing on hover, focus and window blur.
  • Supports hotkey to jump to toast viewport.
  • Supports closing via swipe gesture.
  • Exposes CSS variables for swipe gesture animations.
  • Can be controlled or uncontrolled.

Installation

Install the component from your command line.

sh
sh
$ npm add @oku-ui/primitives

Anatomy

Import the component.

vue
vue
<script setup lang="ts">
import { ToastAction, ToastClose, ToastDescription, ToastProvider, ToastRoot, ToastTitle, ToastViewport } from '@oku-ui/primitives'
</script>

<template>
  <ToastProvider>
    <ToastRoot>
      <ToastTitle />
      <ToastDescription />
      <ToastAction />
      <ToastClose />
    </ToastRoot>

    <ToastViewport />
  </ToastProvider>
</template>

API Reference

Provider

The provider that wraps your toasts and toast viewport. It usually wraps the application.

PropDefaultType
duration
number

Time in milliseconds that each toast should remain visible for.

label
string

An author-localized label for each toast. Used to help screen reader users associate the interruption with a toast.

swipeDirection
MaybeRef<SwipeDirection>

Direction of pointer swipe that should close the toast.

swipeThreshold
MaybeRefOrGetter<number>

Distance in pixels that the swipe must pass before a close is triggered.

Viewport

The fixed area where toasts appear. Users can jump to the viewport by pressing a hotkey. It is up to you to ensure the discoverability of the hotkey for keyboard users.

PropDefaultType
as
'ol'
object | AsTag
hotkey
['F8']
string[]

The keys to use as the keyboard shortcut that will move focus to the toast viewport.

label
'Notifications ({hotkey})'
string

An author-localized label for the toast viewport to provide context for screen reader users when navigating page landmarks. The available {hotkey} placeholder will be replaced for you.

Root

The toast that automatically closes. It should not be held open to acquire a user response.

PropDefaultType
defaultOpen
boolean
forceMount
boolean

Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.

open
boolean
EmitPayload
update:open
[open: boolean]
Data AttributeValue
[data-state]"open" | "closed"
[data-swipe]"start" | "move" | "cancel" | "end"
[data-swipe-direction]"up" | "down" | "left" | "right"
CSS VariableDescription
--radix-toast-swipe-move-x
The offset position of the toast when horizontally swiping
--radix-toast-swipe-move-y
The offset position of the toast when vertically swiping
--radix-toast-swipe-end-x
The offset end position of the toast after horizontally swiping
--radix-toast-swipe-end-y
The offset end position of the toast after vertically swiping

Title

An optional title for the toast

Description

The toast message.

Action

An action that is safe to ignore to ensure users are not expected to complete tasks with unexpected side effects as a result of a time limit.

When obtaining a user response is necessary, portal an "AlertDialog" styled as a toast into the viewport instead.

PropDefaultType
altText
string
as
'div'
object | AsTag

Close

A button that allows users to dismiss the toast before its duration has elapsed.

PropDefaultType
as
'div'
object | AsTag

Examples

Custom hotkey

Override the default hotkey using the event.code value for each key from keycode.info.

html
html
<ToastProvider>
  ...
  <ToastViewport :hotkey="['altKey', 'KeyT']" />
</ToastProvider>

Custom duration

Customise the duration of a toast to override the provider value.

vue
vue
<ToastRoot :duration="3000">
  <ToastDescription>Saved!</ToastDescription>
</ToastRoot>

Duplicate toasts

When a toast must appear every time a user clicks a button, use state to render multiple instances of the same toast (see below). Alternatively, you can abstract the parts to create your own imperative API.

html
html
<div>
  <form @submit="count++">
    ...
    <button>save</button>
  </form>

  <ToastRoot v-for="(_, index) in count" :key="index">
    <ToastDescription>Saved!</ToastDescription>
  </ToastRoot>
</div>

Animating swipe gesture

Combine --radix-toast-swipe-move-[x|y] and --radix-toast-swipe-end-[x|y] CSS variables with data-swipe="[start|move|cancel|end]" attributes to animate a swipe to close gesture. Here's an example:

html
html
<ToastProvider swipeDirection="right">
  <ToastRoot class="ToastRoot">...</ToastRoot>
  <ToastViewport />
</ToastProvider>
css
css
/* styles.css */
.ToastRoot[data-swipe='move'] {
  transform: translateX(var(--radix-toast-swipe-move-x));
}
.ToastRoot[data-swipe='cancel'] {
  transform: translateX(0);
  transition: transform 200ms ease-out;
}
.ToastRoot[data-swipe='end'] {
  animation: slideRight 100ms ease-out;
}

@keyframes slideRight {
  from {
    transform: translateX(var(--radix-toast-swipe-end-x));
  }
  to {
    transform: translateX(100%);
  }
}

Accessibility

Adheres to the aria-live requirements.

Sensitivity

Control the sensitivity of the toast for screen readers using the type prop.

For toasts that are the result of a user action, choose foreground. Toasts generated from background tasks should use background.

Foreground

Foreground toasts are announced immediately. Assistive technologies may choose to clear previously queued messages when a foreground toast appears. Try to avoid stacking distinct foreground toasts at the same time.

Background

Background toasts are announced at the next graceful opportunity, for example, when the screen reader has finished reading its current sentence. They do not clear queued messages so overusing them can be perceived as a laggy user experience for screen reader users when used in response to a user interaction.

html
html
<ToastRoot type="foreground">
  <ToastDescription>File removed successfully.</ToastDescription>
  <ToastClose>Dismiss</ToastClose>
</ToastRoot>

<ToastRoot type="background">
  <ToastDescription>We've just released Radix 1.0.</ToastDescription>
  <ToastClose>Dismiss</ToastClose>
</ToastRoot>

Alternative action

Use the altText prop on the Action to instruct an alternative way of actioning the toast to screen reader users.

You can direct the user to a permanent place in your application where they can action it or implement your own custom hotkey logic. If implementing the latter, use foreground type to announce immediately and increase the duration to give the user ample time.

html
html
<ToastRoot type="background">
  <ToastTitle>Upgrade Available!</ToastTitle>
  <ToastDescription>We've just released Radix 1.0.</ToastDescription>
  <ToastAction altText="Goto account settings to upgrade">
    Upgrade
  </ToastAction>
  <ToastClose>Dismiss</ToastClose>
</ToastRoot>

<ToastRoot type="foreground" :duration="10000">
  <ToastDescription>File removed successfully.</ToastDescription>
  <ToastAction altText="Undo (Alt+U)">
    Undo <kbd>Alt</kbd>+<kbd>U</kbd>
  </ToastAction>
  <ToastClose>Dismiss</ToastClose>
</ToastRoot>

Close icon button

When providing an icon (or font icon), remember to label it correctly for screen reader users.

html
html
<ToastRoot type="foreground">
  <ToastDescription>Saved!</ToastDescription>
  <ToastClose aria-label="Close">
    <span aria-hidden>×</span>
  </ToastClose>
</ToastRoot>

Keyboard Interactions

KeyDescription
F8
Focuses toasts viewport.
Tab
Moves focus to the next focusable element.
Shift + Tab
Moves focus to the previous focusable element.
Space
When focus is on a ToastAction or ToastClose, closes the toast
Enter
When focus is on a ToastAction or ToastClose, closes the toast
Esc
When focus is on a Toast, closes the toast

Custom APIs

Abstract parts

Create your own API by abstracting the primitive parts into your own component.

Usage

vue
vue
<script setup lang="ts">
import Toast from './your-toast.vue'
</script>

<template>
  <Toast
    title="Upgrade available"
    content="We've just released Radix 3.0!"
  >
    <button @click="handleUpgrade">
      Upgrade
    </button>
  </Toast>
</template>

Implementation

vue
vue
// your-toast.vue
<script setup lang="ts">
import { ToastAction, ToastClose, ToastDescription, ToastRoot, ToastTitle } from '@oku-ui/primitives'

defineProps<{
  title: string
  content: string
}>()
</script>

<template>
  <ToastRoot>
    <ToastTitle v-if="title">
      {{ title }}
    </ToastTitle>
    <ToastDescription>{{ content }}</ToastDescription>
    <ToastAction
      as="template"
      alt-text="toast"
    >
      <slot />
    </ToastAction>
    <ToastClose aria-label="Close">
      <span aria-hidden>×</span>
    </ToastClose>
  </ToastRoot>
</template>

Imperative API

Create your own imperative API to allow toast duplication if preferred.

Usage

vue
vue
<script setup lang="ts">
import Toast from './your-toast.vue'

const savedRef = ref<InstanceType<typeof Toast>>()
</script>

<template>
  <div>
    <form @submit="savedRef.publish()">
      ...
    </form>
    <Toast ref="savedRef">
      Saved successfully!
    </Toast>
  </div>
</template>

Implementation

vue
vue
// your-toast.vue
<script setup lang="ts">
import { ToastClose, ToastDescription, ToastRoot, ToastTitle } from '@oku-ui/primitives'
import { ref } from 'vue'

const count = ref(0)

function publish() {
  count.value++
}

defineExpose({
  publish
})
</script>

<template>
  <ToastRoot
    v-for="index in count"
    :key="index"
  >
    <ToastDescription>
      <slot />
    </ToastDescription>
    <ToastClose>Dismiss</ToastClose>
  </ToastRoot>
</template>