Skip to main content
You are viewing Pre-Alpha documentation.
Vuetify0 Logo
Theme
Mode
Palettes
Accessibility
Vuetify One

Sign in to Vuetify One

Access premium tools across the Vuetify ecosystem — Bin, Play, Studio, and more.

Not a subscriber? See what's included

Snackbar

A headless compound component for rendering toast and snackbar notifications. Pairs with useNotifications for queue-driven toast stacks with auto-dismiss, pause on hover/focus, and adapter integration.


Renders elementIntermediateMar 23, 2026

Usage

A single snackbar — render directly when you control the lifecycle yourself.

<script setup lang="ts">
  import { shallowRef } from 'vue'
  import { Snackbar } from '@vuetify/v0'

  const visible = shallowRef(false)

  function onShow () {
    visible.value = true
  }

  function onDismiss () {
    visible.value = false
  }
</script>

<template>
  <div class="relative flex items-center justify-center min-h-48 p-6 bg-background rounded-lg border border-divider overflow-hidden">
    <button
      class="px-4 py-2 bg-primary text-on-primary rounded-md text-sm font-medium"
      @click="onShow"
    >
      Show Snackbar
    </button>

    <Snackbar.Portal class="absolute bottom-4 right-4" :teleport="false">
      <Snackbar.Root
        v-if="visible"
        class="flex items-center gap-3 px-4 py-2.5 rounded-lg shadow-lg text-sm bg-surface border border-divider"
        role="status"
        @dismiss="onDismiss"
      >
        <Snackbar.Content>
          Changes saved successfully
        </Snackbar.Content>

        <Snackbar.Close class="p-1 -mr-1 opacity-50 hover:opacity-100">
          <svg aria-hidden="true" class="w-4 h-4" viewBox="0 0 24 24">
            <path d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" fill="currentColor" />
          </svg>
        </Snackbar.Close>
      </Snackbar.Root>
    </Snackbar.Portal>
  </div>
</template>

Anatomy

Anatomy
<script setup lang="ts">
  import { Snackbar } from '@vuetify/v0'
</script>

<template>
  <!-- Standalone -->
  <Snackbar.Portal>
    <Snackbar.Root>
      <Snackbar.Content />

      <Snackbar.Close />
    </Snackbar.Root>
  </Snackbar.Portal>

  <!-- Queue-driven -->
  <Snackbar.Portal>
    <Snackbar.Queue>
      <Snackbar.Root>
        <Snackbar.Content />

        <Snackbar.Close />
      </Snackbar.Root>
    </Snackbar.Queue>
  </Snackbar.Portal>
</template>

Examples

Notification queue

Snackbar.Queue connects to useNotifications and exposes queue items newest-first. Snackbar.Close auto-wires dismiss to the nearest Snackbar.Root — no @click needed.

Warning

Inside a Snackbar.Queue, clicking Snackbar.Close permanently removes the notification from both the queue and the registry. To remove from the toast surface while keeping the notification in the inbox, call ticket.dismiss() directly on the NotificationTicket.

Cycles through info → success → warning → error

<script setup lang="ts">
  import { computed, shallowRef } from 'vue'
  import { Snackbar, useNotifications } from '@vuetify/v0'
  import type { NotificationSeverity } from '@vuetify/v0'

  const notifications = useNotifications()

  const severities: NotificationSeverity[] = ['info', 'success', 'warning', 'error']
  const index = shallowRef(0)

  const messages: Record<NotificationSeverity, string> = {
    info: 'Deployment started for production',
    success: 'Changes saved successfully',
    warning: 'API rate limit at 80%',
    error: 'Build failed — check logs',
  }

  const classes: Record<NotificationSeverity, string> = {
    info: 'bg-info text-on-info',
    success: 'bg-success text-on-success',
    warning: 'bg-warning text-on-warning',
    error: 'bg-error text-on-error',
  }

  function onShow () {
    const severity = severities[index.value % severities.length]
    index.value++
    notifications.send({ subject: messages[severity], severity, timeout: 4000 })
  }

  // Stacking behavior — consumer owns this
  const hovered = shallowRef(false)

  const ITEM_H = 44
  const GAP = 8
  const PEEK = 16
  const MAX_STACK = 3

  const containerHeight = computed(() => {
    const n = Math.min(notifications.queue.values().length, MAX_STACK)
    if (!n) return 0
    return hovered.value
      ? notifications.queue.values().length * ITEM_H + (notifications.queue.values().length - 1) * GAP
      : ITEM_H + (n - 1) * PEEK
  })

  function itemStyle (i: number) {
    if (hovered.value) {
      return {
        bottom: `${i * (ITEM_H + GAP)}px`,
        left: 0,
        right: 0,
        transform: 'none',
        opacity: 1,
        pointerEvents: 'auto' as const,
      }
    }

    if (i >= MAX_STACK) {
      const depth = MAX_STACK - 1
      return {
        bottom: 0,
        left: `${depth * 8}px`,
        right: `${depth * 8}px`,
        transform: `translateY(${-depth * PEEK}px) scale(${1 - depth * 0.04})`,
        transformOrigin: 'bottom center',
        opacity: 0,
        pointerEvents: 'none' as const,
        zIndex: -1,
      }
    }

    return {
      bottom: 0,
      left: `${i * 8}px`,
      right: `${i * 8}px`,
      transform: `translateY(${-i * PEEK}px) scale(${1 - i * 0.04})`,
      transformOrigin: 'bottom center',
      opacity: Math.max(0, 1 - i * 0.2),
      zIndex: MAX_STACK - i,
      pointerEvents: i === 0 ? 'auto' as const : 'none' as const,
    }
  }

  let leaveTimer: ReturnType<typeof setTimeout> | null = null

  function onEnter () {
    if (leaveTimer) {
      clearTimeout(leaveTimer)
      leaveTimer = null
    }
    hovered.value = true
  }

  function onLeave () {
    leaveTimer = setTimeout(() => {
      hovered.value = false
      leaveTimer = null
    }, 150)
  }
</script>

<template>
  <div class="flex flex-col items-center gap-6 min-h-48 p-6 bg-background rounded-lg border border-divider">
    <button
      class="px-4 py-2 bg-primary text-on-primary rounded-md text-sm font-medium"
      @click="onShow"
    >
      Show Toast
    </button>

    <p class="text-sm opacity-40">
      Cycles through info → success → warning → error
    </p>
  </div>

  <Snackbar.Portal
    class="fixed bottom-4 right-4 w-72"
    @mouseenter="onEnter"
    @mouseleave="onLeave"
  >
    <Snackbar.Queue v-slot="{ items }">
      <div
        class="relative transition-all duration-300 ease-out"
        :style="{ height: `${containerHeight}px` }"
      >
        <div
          v-for="(item, i) in items"
          :key="item.id"
          class="absolute left-0 right-0 transition-all duration-300 ease-out"
          :style="itemStyle(i)"
        >
          <Snackbar.Root
            :id="item.id"
            class="flex items-center gap-3 px-4 py-2.5 rounded-lg shadow-lg text-sm"
            :class="classes[item.severity ?? 'info']"
          >
            <Snackbar.Content class="flex-1">
              {{ item.subject }}
            </Snackbar.Content>
            <Snackbar.Close
              v-show="hovered || i === 0"
              class="p-1 -mr-1 opacity-70 hover:opacity-100 shrink-0"
            >
              <svg aria-hidden="true" class="w-4 h-4" viewBox="0 0 24 24">
                <path d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" fill="currentColor" />
              </svg>
            </Snackbar.Close>
          </Snackbar.Root>
        </div>
      </div>
    </Snackbar.Queue>
  </Snackbar.Portal>
</template>

Recipes

ARIA role

Set role directly on Snackbar.Root to control how screen readers announce the notification:

vue
<template>
  <!-- Polite — waits for user to be idle -->
  <Snackbar.Root role="status">
    <Snackbar.Content>Changes saved</Snackbar.Content>
    <Snackbar.Close />
  </Snackbar.Root>

  <!-- Assertive — interrupts immediately -->
  <Snackbar.Root role="alert">
    <Snackbar.Content>Build failed — check logs</Snackbar.Content>
    <Snackbar.Close />
  </Snackbar.Root>
</template>

Inline rendering

Pass :teleport="false" to render the portal inline instead of teleporting to <body>. Useful in docs, Storybook, or scoped container layouts:

vue
<template>
  <div class="relative h-48">
    <Snackbar.Portal :teleport="false" class="absolute bottom-4 right-4">
      <Snackbar.Root>
        <Snackbar.Content>Changes saved</Snackbar.Content>
        <Snackbar.Close />
      </Snackbar.Root>
    </Snackbar.Portal>
  </div>
</template>

Accessibility

ConcernImplementation
Live regionSnackbar.Root defaults to role="status". Override with role="alert" for urgent notifications. No aria-live on Portal to avoid nesting conflicts.
role="status"Implicit aria-live="polite" — screen reader waits for idle. Use for confirmations and info.
role="alert"Implicit aria-live="assertive" — screen reader interrupts. Use for errors and warnings.
Close buttonaria-label="Close" hardcoded on Snackbar.Close.
TimingAuto-dismiss pauses on hover and focus (WCAG 2.2.1). Tabbing into a snackbar pauses the queue; focus leaving the container resumes it.
FocusNo focus trap — snackbars are non-modal.

API Reference

The following API details are for all variations of the Snackbar component.

Snackbar.Root

Props

namespace

string

Namespace for dependency injection.

Default: "v0:notifications"

id

ID

Unique identifier. Auto-generated if not provided.

Default: useId()

Events

dismiss

[id: ID]

Slots

default

SnackbarRootSlotProps

Snackbar.Close

Props

namespace

string

Namespace for dependency injection.

Default: "v0:notifications"

Slots

default

SnackbarCloseSlotProps

Snackbar.Portal

Props

teleport

string | false

Teleport target. `false` renders inline.

Default: "body"

Slots

default

SnackbarPortalSlotProps

Snackbar.Queue

Props

namespace

string

Which notifications instance to connect to.

Default: "v0:notifications"

Slots

default

SnackbarQueueSlotProps
Was this page helpful?

© 2016-1970 Vuetify, LLC
Ctrl+/