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.

Playground.vuevue
<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.

Notifications.vuevue
<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.

Programmatic.vuevue
<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.

CustomContent.vuevue
<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 + templatesvue
// 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

NameTypeTSDefaultDescription
toasts
[
	{
		id: "toast-1",
		title: "Successfully saved",
		description: "Your changes have been stored.",
		tone: "default",
		duration: 3600,
	}
]
arrayArray<ToastsItem
type ToastsItem = {
	id?: string; // ID
	title?: string; // Title
	description?: string; // Description
	tone?: string; // Tone
	duration?: number; // Duration
};
>
[]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.
durationnumbernumber3600Default auto-dismiss delay in milliseconds. Set to 0 to keep toasts until manually dismissed.
autoDismissbooleanbooleantrueAutomatically emit dismiss after each toast duration.

Auto-generated from Toast stack.props and inline _edit hints.

Slots

NameScopeDescription
#default{ toast, dismiss }Custom toast content rendered inside the card. Use this for avatars, actions, or richer markup.

Events

NamePayloadDescription
@dismiss(id
id: string;
: string)
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.