Toast
A transient notification used to provide feedback for actions, success states, warnings, or errors.
Use the toast helper anywhere in your app to trigger a notification — no setup needed beyond mounting FrappeUIProvider once at the root.
<script setup lang="ts">
import { Button, toast } from 'frappe-ui'
</script>
<template>
<Button label="Show toast" @click="toast.success('Workspace created')" />
</template>Examples
Toasts come in four visual types — message, info, success, warning, and error — and accept an optional description for secondary context.
<script setup lang="ts">
import { Button, toast } from 'frappe-ui'
</script>
<template>
<Button label="Workspace created" @click="toast.success('Workspace created')" />
<Button label="Link copied" @click="toast.info('Link copied to clipboard')" />
<Button
label="Comment posted"
@click="
toast.message('Comment posted', {
description: '“Looks good — shipping it!”',
})
"
/>
<Button
label="Profile updated"
@click="
toast.success('Profile updated', {
description: 'Your changes have been saved.',
})
"
/>
<Button
label="Payment failed"
@click="
toast.error('Payment failed', {
description: 'Your card was declined. Try a different one.',
})
"
/>
<Button
label="Storage almost full"
@click="
toast.warning('Storage almost full', {
description: 'You have used 9.2 GB of 10 GB. Upgrade to keep syncing.',
})
"
/>
</template>With action
Add an action to give the user a way to respond. Combine with duration: Infinity for messages that should wait for an explicit decision.
<script setup lang="ts">
import { Button, toast } from 'frappe-ui'
function postArchived() {
toast.success('Post archived', {
action: {
label: 'Undo',
onClick: () => toast.info('Archive undone'),
},
})
}
function newVersion() {
toast.info('A new version is available', {
description: 'Refresh to get the latest features.',
duration: Infinity,
action: {
label: 'Refresh',
onClick: () => toast.loading('Refreshing…'),
},
})
}
function backgroundSync() {
toast.info('Background sync in progress', {
duration: Infinity,
action: {
label: 'Cancel',
onClick: () => toast.dismiss(),
},
})
}
</script>
<template>
<Button label="Post archived" @click="postArchived" />
<Button label="New version" @click="newVersion" />
<Button label="Background sync" @click="backgroundSync" />
</template>Custom icon
Pass a render function to icon to replace the default type icon — handy for branding moments, custom imagery, or domain-specific glyphs. Use the lucide-* class form for any Lucide icon; size and color are controlled with regular utilities (size-4, text-ink-red-2, …).
<script setup lang="ts">
import { h } from 'vue'
import { Button, toast } from 'frappe-ui'
function celebrate() {
toast.success('You unlocked a new badge', {
icon: () => h('span', { class: 'lucide-sparkles size-4' }),
})
}
function favorited() {
toast('Added to favorites', {
description: 'Find it later in your saved items.',
icon: () => h('span', { class: 'lucide-heart size-4 text-ink-red-2' }),
})
}
function reminder() {
toast.message('Standup in 5 minutes', {
icon: () => h('span', { class: 'lucide-bell size-4' }),
})
}
</script>
<template>
<Button label="Celebrate" @click="celebrate" />
<Button label="Favorited" @click="favorited" />
<Button label="Reminder" @click="reminder" />
</template>Asynchronous
Use toast.promise to wire a single toast to a promise lifecycle — the same row transitions through loading → success/error in place, no stacking.
success and error accept either a string or a callback that returns a string or full toast config. Returning an object unlocks action, description, duration, etc. — and lets the callback close over the resolved value (or thrown error) to wire up follow-ups like Undo on success or Retry on failure.
For multi-step progress without a single promise, pass an id to toast.loading / toast.success to update an existing toast in place.
<script setup lang="ts">
import { Button, toast } from 'frappe-ui'
function sendInvite() {
toast.promise(new Promise<void>((resolve) => setTimeout(resolve, 1500)), {
loading: 'Sending invite to alex@example.com…',
success: 'Invite sent to alex@example.com',
error: 'Could not send invite',
})
}
function deleteFile() {
const file = { id: 'f_42', name: 'report.pdf' }
const willFail = Math.random() < 0.5
toast.promise(
new Promise<{ id: string; name: string }>((resolve, reject) =>
setTimeout(
() =>
willFail
? reject(new Error('Network error'))
: resolve(file),
1500,
),
),
{
loading: `Deleting ${file.name}…`,
success: (deleted) => ({
message: `Deleted ${deleted.name}`,
action: {
label: 'Undo',
onClick: () => toast.success(`Restored ${deleted.name}`),
},
}),
error: (err: Error) => ({
message: `Couldn't delete ${file.name} — ${err.message}`,
action: {
label: 'Retry',
onClick: () => deleteFile(),
},
}),
},
)
}
function deployPipeline() {
const steps = [
{ label: 'Linting…', delay: 0 },
{ label: 'Type-checking…', delay: 600 },
{ label: 'Running tests…', delay: 1200 },
{ label: 'Building…', delay: 1900 },
{ label: 'Deploying to production…', delay: 2700 },
]
const id = toast.loading(steps[0].label)
steps.slice(1).forEach((step) => {
setTimeout(() => toast.loading(step.label, { id }), step.delay)
})
setTimeout(
() =>
toast.success('Deployed to production', {
id,
description: 'v2.4.1 is live • took 3.6s',
}),
3500,
)
}
</script>
<template>
<Button label="Send invite" @click="sendInvite" />
<Button label="Delete file" @click="deleteFile" />
<Button label="Deploy pipeline" @click="deployPipeline" />
</template>