Component
Toast
<dom-toast-region>A live-region stack for transient, dismissible feedback.
Playground
Try every prop live
Toast playground
Edit the stack position, auto-dismiss behaviour, and toast rows in the inspector.
<script setup>
import { reactive } from 'vue';
import { DomButton, DomToastStack } from '@getdom/studio/vue';
let id = 0;
const data = reactive({
duration: 3600,
autoDismiss: false,
toasts: [],
});
function pushToast() {
id += 1;
data.toasts = [
...data.toasts,
{
id: `toast-${id}`,
title: 'Preview toast',
description: 'Edit this row in the inspector.',
tone: id % 2 === 0 ? 'success' : 'default',
duration: data.autoDismiss ? data.duration : 0,
},
];
}
function dismiss(toastId) {
data.toasts = data.toasts.filter((toast) => toast.id !== toastId);
}
defineExpose({ data });
</script>
<template>
<div class="grid gap-3">
<DomButton variant="secondary" @click="pushToast">Add preview toast</DomButton>
<DomToastStack v-bind="data" @dismiss="dismiss" />
</div>
</template>
Demo
Notifications
Trigger normal, success, danger, and persistent toasts. Each toast animates in and out, and timed toasts automatically dismiss.
<script setup>
import { ref } from 'vue';
import { DomButton, DomToastStack } from '@getdom/studio/vue';
let id = 0;
const toasts = ref([]);
const copy = {
default: {
title: 'Draft updated',
description: 'Your latest changes are ready to review.',
},
success: {
title: 'Successfully saved',
description: 'Your changes have been stored.',
},
danger: {
title: 'Could not publish',
description: 'Check the required fields and try again.',
},
warning: {
title: 'Sync paused',
description: 'Reconnect your account before new changes can sync.',
},
};
function pushToast(tone = 'default') {
id += 1;
toasts.value = [
...toasts.value,
{
id: `toast-${id}`,
title: copy[tone].title,
description: copy[tone].description,
tone,
duration: tone === 'warning' ? 0 : tone === 'danger' ? 5200 : 3600,
},
];
}
function dismiss(toastId) {
toasts.value = toasts.value.filter((toast) => toast.id !== toastId);
}
</script>
<template>
<div class="flex flex-wrap gap-2">
<DomButton variant="secondary" @click="pushToast('default')">Normal</DomButton>
<DomButton variant="secondary" @click="pushToast('success')">Success</DomButton>
<DomButton variant="danger" @click="pushToast('danger')">Danger</DomButton>
<DomButton variant="secondary" @click="pushToast('warning')">Needs dismiss</DomButton>
<DomToastStack :toasts="toasts" @dismiss="dismiss" />
</div>
</template>
Demo
Programmatic API
Use useToasts() when an action, such as saving a form, needs to trigger feedback from plain application code.
<script setup>
import { DomButton, DomToastStack, useToasts } from '@getdom/studio/vue';
const { toasts, show, success, danger, dismiss } = useToasts();
function save() {
success('Successfully saved', {
description: 'Your changes have been stored.',
});
}
function publishError() {
danger('Could not publish', {
description: 'Check the required fields and try again.',
duration: 5200,
});
}
function needsDismissal() {
show({
title: 'Sync paused',
description: 'Reconnect your account before new changes can sync.',
tone: 'warning',
duration: 0,
});
}
</script>
<template>
<div class="flex flex-wrap gap-2">
<DomButton variant="secondary" @click="save">Save</DomButton>
<DomButton variant="danger" @click="publishError">Fail publish</DomButton>
<DomButton variant="secondary" @click="needsDismissal">Needs dismiss</DomButton>
<DomToastStack :toasts="toasts" position="bottom-right" @dismiss="dismiss" />
</div>
</template>
Demo
Custom content
Use the default slot to render richer markup such as avatars, action buttons, and component-driven content.
<script setup>
import { ref } from 'vue';
import { DomButton, DomToastStack } from '@getdom/studio/vue';
let id = 0;
const toasts = ref([]);
function showAssignee() {
id += 1;
toasts.value = [
...toasts.value,
{
id: `person-${id}`,
tone: 'success',
title: 'Assigned to Maya',
description: 'Senior product designer',
avatar: `https://i.pravatar.cc/96?img=${(id % 12) + 1}`,
duration: 0,
},
];
}
function dismiss(toastId) {
toasts.value = toasts.value.filter((toast) => toast.id !== toastId);
}
</script>
<template>
<div class="flex flex-wrap gap-2">
<DomButton variant="secondary" @click="showAssignee">Show rich toast</DomButton>
<DomToastStack :toasts="toasts" position="bottom-left" @dismiss="dismiss">
<template #default="{ toast, dismiss: dismissToast }">
<div class="flex items-center gap-3">
<img :src="toast.avatar" alt="" class="size-10 rounded-full object-cover ring-1 ring-border" />
<div class="min-w-0 flex-1">
<p class="text-sm font-semibold text-fg">{{ toast.title }}</p>
<p class="mt-0.5 text-sm text-muted-fg">{{ toast.description }}</p>
</div>
<button
type="button"
class="rounded-full bg-secondary px-3 py-1 text-xs font-medium text-fg hover:bg-accent"
@click="dismissToast(toast.id)"
>
Done
</button>
</div>
</template>
</DomToastStack>
</div>
</template>
Demo
Toast templates
Pass a separate SFC to show(Component, props, options) when different notifications need their own markup and data shape.
Action: none
// ComponentToasts.vue
<script setup>
import { ref } from 'vue';
import { DomButton, DomToastStack, useToasts } from '@getdom/studio/vue';
import AvatarToast from './toasts/AvatarToast.vue';
import SoftwareUpdateToast from './toasts/SoftwareUpdateToast.vue';
const { toasts, show, dismiss } = useToasts();
const lastAction = ref('none');
function showAvatarToast() {
show(AvatarToast, {
avatar: 'https://i.pravatar.cc/96?img=5',
name: 'Maya Patel',
role: 'Senior product designer',
}, {
tone: 'success',
duration: 0,
});
}
function showUpdateToast() {
show(SoftwareUpdateToast, {
version: 'DOM Studio 2.4',
size: '48 MB',
}, {
duration: 3000,
});
}
function onAction(event) {
lastAction.value = [event.action, event.value || event.person || event.version].filter(Boolean).join(': ');
if (event.action !== 'dismiss') dismiss(event.id);
}
</script>
<template>
<div class="grid gap-3">
<div class="flex flex-wrap gap-2">
<DomButton variant="secondary" @click="showAvatarToast">Avatar toast</DomButton>
<DomButton variant="secondary" @click="showUpdateToast">Software update</DomButton>
</div>
<p class="text-xs text-muted-fg">Action: <code class="text-fg">{{ lastAction }}</code></p>
<DomToastStack
:toasts="toasts"
position="bottom-right"
@dismiss="dismiss"
@action="onAction"
/>
</div>
</template>
// toasts/AvatarToast.vue
<script setup>
import { DomButton } from '@getdom/studio/vue';
const props = defineProps({
avatar: {
type: String,
required: true,
},
name: {
type: String,
required: true,
},
role: {
type: String,
default: '',
},
});
const emit = defineEmits(['action', 'dismiss']);
</script>
<template>
<div class="flex items-center gap-3">
<img
:src="avatar"
:alt="`${name} avatar`"
class="size-10 rounded-full object-cover ring-1 ring-border"
/>
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-semibold text-fg">{{ name }}</p>
<p v-if="role" class="mt-0.5 truncate text-sm text-muted-fg">{{ role }}</p>
</div>
<DomButton
size="sm"
variant="secondary"
@click="emit('action', { action: 'view-profile', person: props.name })"
>View</DomButton>
<button
type="button"
aria-label="Dismiss notification"
class="-mr-1 rounded-full px-2 text-lg leading-none text-muted-fg transition hover:bg-secondary hover:text-fg"
@click="emit('dismiss')"
>×</button>
</div>
</template>
// toasts/SoftwareUpdateToast.vue
<script setup>
import { DomButton, DomDropdown } from '@getdom/studio/vue';
const props = defineProps({
version: {
type: String,
required: true,
},
size: {
type: String,
default: '',
},
});
const emit = defineEmits(['action', 'dismiss']);
const laterItems = [
{ label: 'Try in an hour', value: 'hour' },
{ label: 'Try tonight', value: 'tonight' },
{ label: 'Remind me tomorrow', value: 'tomorrow' },
{ separator: true },
{ label: 'Turn on automatic updates', value: 'automatic-updates' },
];
function installNow() {
emit('action', { action: 'install-now', version: props.version });
}
function remindLater(value) {
emit('action', { action: 'remind-later', value });
}
</script>
<template>
<div class="grid gap-3">
<div class="flex items-start gap-3">
<div class="grid size-10 shrink-0 place-items-center rounded-xl bg-primary text-sm font-semibold text-primary-fg">OS</div>
<div class="min-w-0 flex-1">
<p class="text-sm font-semibold text-fg">Install {{ version }}</p>
<p class="mt-1 text-sm text-muted-fg">
<span v-if="size">A {{ size }} update is ready.</span>
<span v-else>An update is ready.</span>
Restart may be required.
</p>
</div>
<button
type="button"
aria-label="Dismiss update"
class="-mr-1 -mt-1 rounded-full px-2 text-lg leading-none text-muted-fg transition hover:bg-secondary hover:text-fg"
@click="emit('dismiss')"
>×</button>
</div>
<div class="flex flex-wrap gap-2 pl-[3.25rem]">
<DomButton size="sm" @click="installNow">Install now</DomButton>
<DomDropdown
:items="laterItems"
label="Later"
align="right"
width="min-w-[15rem]"
@select="remindLater"
/>
</div>
</div>
</template>
Reference
Props
Control props
| Name | Type | TS | Default | Description |
|---|---|---|---|---|
toasts | array | Array< | [] | Visible toasts. Set duration to 0 for a toast that requires manual dismissal. |
position | 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | string | 'top-right' | Viewport corner. |
duration | number | number | 3600 | Default auto-dismiss delay in milliseconds. Set to 0 to keep toasts until manually dismissed. |
autoDismiss | boolean | boolean | true | Automatically emit dismiss after each toast duration. |
Auto-generated from Toast stack.props and inline _edit hints.
Slots
| Name | Scope | Description |
|---|---|---|
| #default | { toast, dismiss } | Custom toast content rendered inside the card. Use this for avatars, actions, or richer markup. |
Events
| Name | Payload | Description |
|---|---|---|
| @dismiss | ( | Emitted when a toast is dismissed. |
| @action | ({ id, toast, ...payload }) | Emitted by toast template content, useful for buttons and menus inside a toast. |
Names auto-detected from defineEmits and source emit() calls; payload and description from __doc.events when present.