Lzh on GitHub
提供信息或反馈给用户的简洁消息。

用法

使用 useToast 可组合函数在你的应用程序中显示 Toast。

请确保用 App 组件包裹你的应用,它使用我们的 Toaster 组件,而 Toaster 组件又使用了 Reka UI 的 ToastProvider 组件。
你可以查看 App 组件的 toaster prop,了解如何全局配置 Toaster。

标题

toast.add 方法传递 title 字段以显示标题。

<script setup lang="ts">
// 注意,这里定义为 props 是因为需要在 ComponentExample 组件中使用且可以动态修改
const props = defineProps<{
  title: string
}>()

const toast = useToast()

function showToast() {
  toast.add(props)
}
</script>

<template>
  <UButton label="Show toast" color="neutral" variant="outline" @click="showToast" />
</template>

描述

toast.add 方法传递 description 字段以显示描述。

<script setup lang="ts">
const props = defineProps<{
  title: string
  description: string
}>()

const toast = useToast()

function showToast() {
  toast.add(props)
}
</script>

<template>
  <UButton label="Show toast" color="neutral" variant="outline" @click="showToast" />
</template>

图标 (Icon)

toast.add 方法传递 icon 字段以显示 Icon

<script setup lang="ts">
const props = defineProps<{
  icon: string
}>()

const toast = useToast()

function showToast() {
  toast.add({
    title: 'Uh oh! Something went wrong.',
    description: 'There was a problem with your request.',
    icon: props.icon
  })
}
</script>

<template>
  <UButton label="Show toast" color="neutral" variant="outline" @click="showToast" />
</template>

头像 (Avatar)

toast.add 方法传递 avatar 字段以显示 Avatar

<script setup lang="ts">
import type { AvatarProps } from '@nuxt/ui'

const props = defineProps<{
  avatar: AvatarProps
}>()

const toast = useToast()

function showToast() {
  toast.add({
    title: 'User invited',
    description: 'benjamincanac was invited to the team.',
    avatar: props.avatar
  })
}
</script>

<template>
  <UButton label="Invite user" color="neutral" variant="outline" @click="showToast" />
</template>

颜色 (Color)

toast.add 方法传递 color 字段以更改 Toast 的颜色。

TODO 存在问题

<script setup lang="ts">
import type { ToastProps } from '@nuxt/ui'

const props = defineProps<{
  color: ToastProps['color']
}>()

const toast = useToast()

function showToast() {
  toast.add({
    title: 'Uh oh! Something went wrong.',
    description: 'There was a problem with your request.',
    icon: 'i-lucide-wifi',
    color: props.color
  })
}
</script>

<template>
  <UButton
    label="Show toast"
    color="neutral"
    variant="outline"
    @click="showToast"
  />
</template>

关闭 (Close)

传递 close 字段来自定义或隐藏关闭按钮(使用 false 值)。

<script setup lang="ts">
const toast = useToast()

function showToast() {
  toast.add({
    title: 'Uh oh! Something went wrong.',
    description: 'There was a problem with your request.',
    icon: 'i-lucide-wifi',
    close: {
      color: 'primary',
      variant: 'outline',
      class: 'rounded-full'
    }
  })
}
</script>

<template>
  <UButton label="Show toast" color="neutral" variant="outline" @click="showToast" />
</template>

关闭图标 (Close Icon)

传递 closeIcon 字段以自定义关闭按钮的 Icon。默认为 i-lucide-x

<script setup lang="ts">
const props = defineProps<{
  closeIcon: string
}>()

const toast = useToast()

function showToast() {
  toast.add({
    title: 'Uh oh! Something went wrong.',
    description: 'There was a problem with your request.',
    closeIcon: props.closeIcon
  })
}
</script>

<template>
  <UButton label="Show toast" color="neutral" variant="outline" @click="showToast" />
</template>
你可以在 app.config.ts 中的 ui.icons.close 键下全局自定义此图标。
你可以在 vite.config.ts 中的 ui.icons.close 键下全局自定义此图标。

动作 (Actions)

传递 actions 字段以向 Alert 添加一些 Button 动作。

<script setup lang="ts">
const toast = useToast()

const props = defineProps<{
  description: string
}>()

function showToast() {
  toast.add({
    title: 'Uh oh! Something went wrong.',
    description: props.description,
    actions: [{
      icon: 'i-lucide-refresh-cw',
      label: 'Retry',
      color: 'neutral',
      variant: 'outline',
      onClick: (e) => {
        e?.stopPropagation()
      }
    }]
  })
}
</script>

<template>
  <UButton label="Show toast" color="neutral" variant="outline" @click="showToast" />
</template>

进度条(Progress) New

传递一个 progress 字段来自定义或隐藏 进度条(使用 false 值)。

进度条默认继承 Toast 的颜色,但你可以使用 progress.color 字段来覆盖它。
<script setup lang="ts">
const toast = useToast()

function showToast() {
  toast.add({
    title: 'Uh oh! Something went wrong.',
    description: 'There was a problem with your request.',
    icon: 'i-lucide-wifi',
    progress: false
  })
}
</script>

<template>
  <UButton label="Show toast" color="neutral" variant="outline" @click="showToast" />
</template>

方向 (Orientation)

orientation 字段传递给 toast.add 方法即可更改 Toast 的方向。

TODO 存在问题

<script setup lang="ts">
const toast = useToast()

const props = defineProps<{
  orientation: 'horizontal' | 'vertical'
}>()

function showToast() {
  toast.add({
    title: 'Uh oh! Something went wrong.',
    orientation: props.orientation,
    actions: [{
      icon: 'i-lucide-refresh-cw',
      label: 'Retry',
      color: 'neutral',
      variant: 'outline',
      onClick: (e) => {
        e?.stopPropagation()
      }
    }]
  })
}
</script>

<template>
  <UButton
    label="Show toast"
    color="neutral"
    variant="outline"
    @click="showToast"
  />
</template>

示例

更改全局位置

更改 App 组件上的 toaster.position prop 以更改 Toast 的位置。

app.vue
<script setup lang="ts">
const toaster = { position: 'bottom-right' }
</script>

<template>
  <UApp :toaster="toaster">
    <NuxtPage />
  </UApp>
</template>
<script setup lang="ts">
const toast = useToast()

function addToCalendar() {
  const eventDate = new Date(Date.now() + Math.random() * 31536000000)
  const formattedDate = eventDate.toLocaleDateString('en-US', {
    month: 'short',
    day: 'numeric',
    year: 'numeric'
  })

  toast.add({
    title: 'Event added to calendar',
    description: `This event is scheduled for ${formattedDate}.`,
    icon: 'i-lucide-calendar-days'
  })
}
</script>

<template>
  <UButton
    label="Add to calendar"
    color="neutral"
    variant="outline"
    icon="i-lucide-plus"
    @click="addToCalendar"
  />
</template>
在此示例中,我们使用 AppConfig 全局配置 Toaster 组件的 position prop。

更改全局持续时间

更改 App 组件上的 toaster.duration prop 以更改 Toast 的持续时间。

app.vue
<script setup lang="ts">
const toaster = { duration: 5000 }
</script>

<template>
  <UApp :toaster="toaster">
    <NuxtPage />
  </UApp>
</template>
<script setup lang="ts">
const toast = useToast()

function addToCalendar() {
  const eventDate = new Date(Date.now() + Math.random() * 31536000000)
  const formattedDate = eventDate.toLocaleDateString('en-US', {
    month: 'short',
    day: 'numeric',
    year: 'numeric'
  })

  toast.add({
    title: 'Event added to calendar',
    description: `This event is scheduled for ${formattedDate}.`,
    icon: 'i-lucide-calendar-days'
  })
}
</script>

<template>
  <UButton
    label="Add to calendar"
    color="neutral"
    variant="outline"
    icon="i-lucide-plus"
    @click="addToCalendar"
  />
</template>
在此示例中,我们使用 AppConfig 全局配置 Toaster 组件的 duration prop。

堆叠 Toast

App 组件上的 toaster.expand prop 设置为 false 以显示堆叠 Toast。

你可以将鼠标悬停在 Toast 上以展开它们。这也会暂停 Toast 的计时器。
app.vue
<script setup lang="ts">
const toaster = { expand: true }
</script>

<template>
  <UApp :toaster="toaster">
    <NuxtPage />
  </UApp>
</template>
<script setup lang="ts">
const toast = useToast()

function addToCalendar() {
  const eventDate = new Date(Date.now() + Math.random() * 31536000000)
  const formattedDate = eventDate.toLocaleDateString('en-US', {
    month: 'short',
    day: 'numeric',
    year: 'numeric'
  })

  toast.add({
    title: 'Event added to calendar',
    description: `This event is scheduled for ${formattedDate}.`,
    icon: 'i-lucide-calendar-days'
  })
}
</script>

<template>
  <UButton
    label="Add to calendar"
    color="neutral"
    variant="outline"
    icon="i-lucide-plus"
    @click="addToCalendar"
  />
</template>
在此示例中,我们使用 AppConfig 全局配置 Toaster 组件的 expand prop。

API

Props

Prop Default Type
as

'li'

any

The element or component this component should render as.

title

string | VNode<RendererNode, RendererElement, { [key: string]: any; }> | (): VNode<RendererNode, RendererElement, { [key: string]: any; }>

description

string | VNode<RendererNode, RendererElement, { [key: string]: any; }> | (): VNode<RendererNode, RendererElement, { [key: string]: any; }>

icon

string

avatar

AvatarProps

color

'primary'

"error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral"

orientation

'vertical'

"horizontal" | "vertical"

The orientation between the content and the actions.

close

true

boolean | Partial<ButtonProps>

Display a close button to dismiss the toast. { size: 'md', color: 'neutral', variant: 'link' }

closeIcon

appConfig.ui.icons.close

string

The icon displayed in the close button.

actions

ButtonProps[]

Display a list of actions:

  • under the title and description when orientation is vertical
  • next to the close button when orientation is horizontal{ size: 'xs' }
progress

true

boolean | Pick<ProgressProps, "color">

Display a progress bar showing the toast's remaining duration. { size: 'sm' }

type

"foreground" | "background"

Control the sensitivity of the toast for accessibility purposes.

For toasts that are the result of a user action, choose foreground. Toasts generated from background tasks should use background.

duration

number

Time in milliseconds that toast should remain visible for. Overrides value given to ToastProvider.

defaultOpen

boolean

The open state of the dialog when it is initially rendered. Use when you do not need to control its open state.

open

boolean

The controlled open state of the dialog. Can be bind as v-model:open.

ui

{ root?: ClassNameValue; wrapper?: ClassNameValue; title?: ClassNameValue; description?: ClassNameValue; icon?: ClassNameValue; ... 4 more ...; close?: ClassNameValue; }

Slots

Slot Type
leading

{}

title

{}

description

{}

actions

{}

close

{ ui: { root: (props?: Record<string, any> | undefined) => string; wrapper: (props?: Record<string, any> | undefined) => string; title: (props?: Record<string, any> | undefined) => string; ... 6 more ...; close: (props?: Record<...> | undefined) => string; }; }

Emits

Event Type
pause

[]

update:open

[value: boolean]

escapeKeyDown

[event: KeyboardEvent]

resume

[]

swipeStart

[event: { currentTarget: EventTarget & HTMLElement; } & Omit<CustomEvent<{ originalEvent: PointerEvent; delta: { x: number; y: number; }; }>, "currentTarget">]

swipeMove

[event: { currentTarget: EventTarget & HTMLElement; } & Omit<CustomEvent<{ originalEvent: PointerEvent; delta: { x: number; y: number; }; }>, "currentTarget">]

swipeCancel

[event: { currentTarget: EventTarget & HTMLElement; } & Omit<CustomEvent<{ originalEvent: PointerEvent; delta: { x: number; y: number; }; }>, "currentTarget">]

swipeEnd

[event: { currentTarget: EventTarget & HTMLElement; } & Omit<CustomEvent<{ originalEvent: PointerEvent; delta: { x: number; y: number; }; }>, "currentTarget">]

Theme

app.config.ts
export default defineAppConfig({
  ui: {
    toast: {
      slots: {
        root: 'relative group overflow-hidden bg-default shadow-lg rounded-lg ring ring-default p-4 flex gap-2.5 focus:outline-none',
        wrapper: 'w-0 flex-1 flex flex-col',
        title: 'text-sm font-medium text-highlighted',
        description: 'text-sm text-muted',
        icon: 'shrink-0 size-5',
        avatar: 'shrink-0',
        avatarSize: '2xl',
        actions: 'flex gap-1.5 shrink-0',
        progress: 'absolute inset-x-0 bottom-0',
        close: 'p-0'
      },
      variants: {
        color: {
          primary: {
            root: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary',
            icon: 'text-primary'
          },
          secondary: {
            root: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-secondary',
            icon: 'text-secondary'
          },
          success: {
            root: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-success',
            icon: 'text-success'
          },
          info: {
            root: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-info',
            icon: 'text-info'
          },
          warning: {
            root: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-warning',
            icon: 'text-warning'
          },
          error: {
            root: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-error',
            icon: 'text-error'
          },
          neutral: {
            root: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-inverted',
            icon: 'text-highlighted'
          }
        },
        orientation: {
          horizontal: {
            root: 'items-center',
            actions: 'items-center'
          },
          vertical: {
            root: 'items-start',
            actions: 'items-start mt-2.5'
          }
        },
        title: {
          true: {
            description: 'mt-1'
          }
        }
      },
      defaultVariants: {
        color: 'primary'
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        toast: {
          slots: {
            root: 'relative group overflow-hidden bg-default shadow-lg rounded-lg ring ring-default p-4 flex gap-2.5 focus:outline-none',
            wrapper: 'w-0 flex-1 flex flex-col',
            title: 'text-sm font-medium text-highlighted',
            description: 'text-sm text-muted',
            icon: 'shrink-0 size-5',
            avatar: 'shrink-0',
            avatarSize: '2xl',
            actions: 'flex gap-1.5 shrink-0',
            progress: 'absolute inset-x-0 bottom-0',
            close: 'p-0'
          },
          variants: {
            color: {
              primary: {
                root: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary',
                icon: 'text-primary'
              },
              secondary: {
                root: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-secondary',
                icon: 'text-secondary'
              },
              success: {
                root: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-success',
                icon: 'text-success'
              },
              info: {
                root: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-info',
                icon: 'text-info'
              },
              warning: {
                root: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-warning',
                icon: 'text-warning'
              },
              error: {
                root: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-error',
                icon: 'text-error'
              },
              neutral: {
                root: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-inverted',
                icon: 'text-highlighted'
              }
            },
            orientation: {
              horizontal: {
                root: 'items-center',
                actions: 'items-center'
              },
              vertical: {
                root: 'items-start',
                actions: 'items-start mt-2.5'
              }
            },
            title: {
              true: {
                description: 'mt-1'
              }
            }
          },
          defaultVariants: {
            color: 'primary'
          }
        }
      }
    })
  ]
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import uiPro from '@nuxt/ui-pro/vite'

export default defineConfig({
  plugins: [
    vue(),
    uiPro({
      ui: {
        toast: {
          slots: {
            root: 'relative group overflow-hidden bg-default shadow-lg rounded-lg ring ring-default p-4 flex gap-2.5 focus:outline-none',
            wrapper: 'w-0 flex-1 flex flex-col',
            title: 'text-sm font-medium text-highlighted',
            description: 'text-sm text-muted',
            icon: 'shrink-0 size-5',
            avatar: 'shrink-0',
            avatarSize: '2xl',
            actions: 'flex gap-1.5 shrink-0',
            progress: 'absolute inset-x-0 bottom-0',
            close: 'p-0'
          },
          variants: {
            color: {
              primary: {
                root: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary',
                icon: 'text-primary'
              },
              secondary: {
                root: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-secondary',
                icon: 'text-secondary'
              },
              success: {
                root: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-success',
                icon: 'text-success'
              },
              info: {
                root: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-info',
                icon: 'text-info'
              },
              warning: {
                root: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-warning',
                icon: 'text-warning'
              },
              error: {
                root: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-error',
                icon: 'text-error'
              },
              neutral: {
                root: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-inverted',
                icon: 'text-highlighted'
              }
            },
            orientation: {
              horizontal: {
                root: 'items-center',
                actions: 'items-center'
              },
              vertical: {
                root: 'items-start',
                actions: 'items-start mt-2.5'
              }
            },
            title: {
              true: {
                description: 'mt-1'
              }
            }
          },
          defaultVariants: {
            color: 'primary'
          }
        }
      }
    })
  ]
})