<script setup lang="ts">
import { AToastAction, AToastDescription, AToastPortal, AToastProvider, AToastRoot, AToastTitle, AToastViewport } from 'akar';
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);
}
</script>
<template>
<AToastProvider>
<button
class="text-sm color-text font-500 px-2.5 py-1.5 rounded-md bg-background inline-flex gap-1.5 ring ring-ring-accented ring-inset transition-colors-280 items-center focus:outline-none active:bg-background-elevated hover:bg-background-elevated focus-visible:(ring-2 ring-ring-inverted)"
@click="handleClick"
>
Add to calendar
</button>
<AToastRoot
v-model:open="open"
class="p-[15px] border rounded-lg bg-background gap-x-[15px] grid grid-cols-[auto_max-content] shadow-sm [grid-template-areas:_'title_action'_'description_action'] items-center z-100 data-[swipe=cancel]:translate-x-0 data-[swipe=move]:translate-x-[var(--akar-toast-swipe-move-x)] data-[state=open]:(animate-in animate-duration-280 slide-in-from-bottom)"
>
<AToastTitle class="text-slate12 text-sm font-medium mb-[5px] [grid-area:_title]">
Scheduled: Catch up
</AToastTitle>
<AToastDescription as-child>
<time
class="text-slate11 text-xs leading-[1.3] m-0 [grid-area:_description]"
:dateTime="eventDateRef.toISOString()"
>
{{ prettyDate(eventDateRef) }}
</time>
</AToastDescription>
<AToastAction
class="[grid-area:_action]"
as-child
alt-text="Goto schedule to undo"
>
<button class="bg-green2 text-green11 shadow-green7 hover:shadow-green8 focus:shadow-green8 text-xs leading-[25px] font-medium px-[10px] rounded-md inline-flex h-[25px] shadow-[inset_0_0_0_1px] items-center justify-center focus:shadow-[0_0_0_2px] hover:shadow-[inset_0_0_0_1px]">
Undo
</button>
</AToastAction>
</AToastRoot>
<AToastPortal>
<AToastViewport class="m-0 p-[var(--viewport-padding)] outline-none list-none flex flex-col gap-[10px] max-w-[100vw] w-[390px] [--viewport-padding:_25px] bottom-0 right-0 fixed z-[2147483647]" />
</AToastPortal>
</AToastProvider>
</template>
Import the component.
<script setup lang="ts">
import { AToastAction, AToastClose, AToastDescription, AToastProvider, AToastRoot, AToastTitle, AToastViewport } from 'akar';
</script>
<template>
<AToastProvider>
<AToastRoot>
<AToastTitle />
<AToastDescription />
<AToastAction />
<AToastClose />
</AToastRoot>
<AToastViewport />
</AToastProvider>
</template>
One benefit of using Akar is its flexibility and low-level control over the components. However, this also means that you may need to manually construct more complex UI elements by combining multiple Akar components together.
If you feel there's a lot of elements that needs to be constructed manually using Akar, consider using Pohon UI instead. It provides a higher-level abstraction over Akar components with pre-defined styles and behaviors that can help you build UIs faster.
The provider that wraps your toasts and toast viewport. It usually wraps the application.
| Prop | Default | Type |
|---|---|---|
duration | 5000 | numberTime in milliseconds that each toast should remain visible for. |
label | 'Notification' | stringAn author-localized label for each toast. Used to help screen reader users associate the interruption with a toast. |
swipeDirection | 'right' | 'right' | 'left' | 'down' | 'up'Direction of pointer swipe that should close the toast. |
swipeThreshold | 50 | numberDistance in pixels that the swipe must pass before a close is triggered. |
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.
| Prop | Default | Type |
|---|---|---|
as | 'ol' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
hotkey | ['F8'] | string[]The keys to use as the keyboard shortcut that will move focus to the toast viewport. |
label | 'Notifications ({hotkey})' | string | ((hotkey: string) => string)An author-localized label for each toast. Used to help screen reader users associate the interruption with a toast. |
The toast that automatically closes. It should not be held open to acquire a user response.
| Prop | Default | Type |
|---|---|---|
as | 'li' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
defaultOpen | true | booleanThe open state of the dialog when it is initially rendered. Use when you do not need to control its open state. |
duration | numberTime in milliseconds that each toast should remain visible for. | |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. | |
open | boolean | |
type | 'foreground' | 'foreground' | 'background'Control the sensitivity of the toast for accessibility purposes. For toasts that are the result of a user action, choose |
| Event | Type |
|---|---|
escapeKeyDown | [event: KeyboardEvent]Event handler called when the escape key is down. It can be prevented by calling |
pause | []Event handler called when the dismiss timer is paused. This occurs when the pointer is moved over the viewport, the viewport is focused or when the window is blurred. |
resume | []Event handler called when the dismiss timer is resumed. This occurs when the pointer is moved away from the viewport, the viewport is blurred or when the window is focused. |
swipeCancel | [event: SwipeEvent]Event handler called when swipe interaction is cancelled. It can be prevented by calling |
swipeEnd | [event: SwipeEvent]Event handler called at the end of a swipe interaction. It can be prevented by calling |
swipeMove | [event: SwipeEvent]Event handler called during a swipe interaction. It can be prevented by calling |
swipeStart | [event: SwipeEvent]Event handler called when starting a swipe interaction. It can be prevented by calling |
update:open | [value: boolean]Event handler called when the open state changes |
| Slot | Type |
|---|---|
open | booleanThe controlled open state of the dialog. Can be bind as |
remaining | numberRemaining time (in ms) |
duration | numberTime in milliseconds that each toast should remain visible for. |
| Attribute | Value |
|---|---|
[data-state] | 'open' | 'closed' |
[data-swipe-direction] | 'up' | 'down' | 'left' | 'right' |
[data-swipe] | 'start' | 'move' | 'cancel' | 'end' |
| Variable | Description |
|---|---|
--akar-toast-swipe-move-x | The offset position of the toast when horizontally swiping |
--akar-toast-swipe-move-y | The offset position of the toast when vertically swiping |
--akar-toast-swipe-end-x | The offset end position of the toast after horizontally swiping |
--akar-toast-swipe-end-y | The offset end position of the toast after vertically swiping |
When used, portals the content part into the body.
| Prop | Default | Type |
|---|---|---|
defer | booleanDefer the resolving of a Teleport target until other parts of the application have mounted (requires Vue 3.5.0+) {@link https://vuejs.org/guide/built-ins/teleport.html#deferred-teleport} | |
disabled | booleanDisable teleport and render the component inline {@link https://vuejs.org/guide/built-ins/teleport.html#disabling-teleport} | |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. | |
to | string | HTMLElementVue native teleport component prop {@link https://vuejs.org/guide/built-ins/teleport.html#basic-usage} |
An optional title for the toast
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
The toast message.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
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.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
altText* | stringA short description for an alternate way to carry out the action. For screen reader users who will not be able to navigate to the button easily/quickly. | |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
A button that allows users to dismiss the toast before its duration has elapsed.
| Prop | Default | Type |
|---|---|---|
as | 'button' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Override the default hotkey using the event.code value for each key from keycode.info.
<template>
<AToastProvider>
...
<AToastViewport :hotkey="['altKey', 'KeyT']" />
</AToastProvider>
</template>
Customise the duration of a toast to override the provider value.
<template>
<AToastRoot :duration="3000">
<AToastDescription>Saved!</AToastDescription>
</AToastRoot>
</template>
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.
<template>
<div>
<form @submit="count++">
...
<button>save</button>
</form>
<AToastRoot
v-for="(_, index) in count"
:key="index"
>
<AToastDescription>Saved!</AToastDescription>
</AToastRoot>
</div>
</template>
Combine --akar-toast-swipe-move-[x|y] and --akar-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:
<template>
<AToastProvider swipe-direction="right">
<AToastRoot class="AToastRoot">
...
</AToastRoot>
<AToastViewport />
</AToastProvider>
</template>
/* styles.css */
.AToastRoot[data-swipe='move'] {
transform: translateX(var(--akar-toast-swipe-move-x));
}
.AToastRoot[data-swipe='cancel'] {
transform: translateX(0);
transition: transform 200ms ease-out;
}
.AToastRoot[data-swipe='end'] {
animation: slideRight 100ms ease-out;
}
@keyframes slideRight {
from {
transform: translateX(var(--akar-toast-swipe-end-x));
}
to {
transform: translateX(100%);
}
}
Adheres to the aria-live requirements.
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. AToasts generated from background tasks should use background.
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 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.
<template>
<AToastRoot type="foreground">
<AToastDescription>File removed successfully.</AToastDescription>
<AToastClose>Dismiss</AToastClose>
</AToastRoot>
<AToastRoot type="background">
<AToastDescription>We've just released akar</AToastDescription>
<AToastClose>Dismiss</AToastClose>
</AToastRoot>
</template>
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.
<template>
<AToastRoot type="background">
<AToastTitle>Upgrade Available!</AToastTitle>
<AToastDescription>We've just released akar</AToastDescription>
<AToastAction alt-text="Goto account settings to upgrade">
Upgrade
</AToastAction>
<AToastClose>Dismiss</AToastClose>
</AToastRoot>
<AToastRoot
type="foreground"
:duration="10000"
>
<AToastDescription>File removed successfully.</AToastDescription>
<AToastAction alt-text="Undo (Alt+U)">
Undo <kbd>Alt</kbd>+<kbd>U</kbd>
</AToastAction>
<AToastClose>Dismiss</AToastClose>
</AToastRoot>
</template>
When providing an icon (or font icon), remember to label it correctly for screen reader users.
<template>
<AToastRoot type="foreground">
<AToastDescription>Saved!</AToastDescription>
<AToastClose aria-label="Close">
<span aria-hidden="true">×</span>
</AToastClose>
</AToastRoot>
</template>
| Key | Description |
|---|---|
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 |
Enter | When focus is on a |
Esc | When focus is on a |
Create your own API by abstracting the primitive parts into your own component.
<script setup lang="ts">
import AToast from './your-toast.vue';
</script>
<template>
<AToast
title="Upgrade available"
content="We've just released Radix 3.0!"
>
<button @click="handleUpgrade">
Upgrade
</button>
</AToast>
</template>
// your-toast.vue
<script setup lang="ts">
import { AToastAction, AToastClose, AToastDescription, AToastRoot, AToastTitle } from 'akar';
defineProps<{
title: string;
content: string;
}>();
</script>
<template>
<AToastRoot>
<AToastTitle v-if="title">
{{ title }}
</AToastTitle>
<AToastDescription v-if="content">
{{ content }}
</AToastDescription>
<AToastAction
as-child
alt-text="toast"
>
<slot />
</AToastAction>
<AToastClose aria-label="Close">
<span aria-hidden="true">×</span>
</AToastClose>
</AToastRoot>
</template>
Create your own imperative API to allow toast duplication if preferred.
<script setup lang="ts">
import AToast from './your-toast.vue';
const savedRef = ref<InstanceType<typeof AToast>>();
</script>
<template>
<div>
<form @submit="savedRef.publish()">
...
</form>
<AToast ref="savedRef">
Saved successfully!
</AToast>
</div>
</template>
// your-toast.vue
<script setup lang="ts">
import { AToastClose, AToastDescription, AToastRoot } from 'akar';
import { ref } from 'vue';
const count = ref(0);
function publish() {
count.value++;
}
defineExpose({
publish
});
</script>
<template>
<AToastRoot
v-for="index in count"
:key="index"
>
<AToastDescription>
<slot />
</AToastDescription>
<AToastClose>Dismiss</AToastClose>
</AToastRoot>
</template>