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.

vue
<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.

vue
<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.

vue
<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, …).

vue
<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.

vue
<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>