🔥 Oxide UI v0.1.0 — Dark mode only, copy-paste ready. Get started →
Skip to content

Toast / Notification

<div role="status">

Short-lived feedback about the result of an action. Appears in a corner of the viewport and auto-dismisses after a few seconds.

Overview

Preview

Variants

Success

Preview

Changes saved

Your project was updated successfully.

html
<div role="status" class="flex items-start gap-3 rounded-xl border border-emerald-800/40 bg-zinc-900 px-4 py-3.5 shadow-xl">
  <div class="mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-emerald-950">
    <!-- checkmark icon -->
  </div>
  <div>
    <p class="text-sm font-semibold text-emerald-300">Changes saved</p>
    <p class="mt-0.5 text-xs text-zinc-500">Your project was updated successfully.</p>
  </div>
</div>

Error

Preview

Failed to save

Check your connection and try again.

html
<div role="alert" class="... border-red-800/40 ...">
  <p class="text-sm font-semibold text-red-300">Failed to save</p>
</div>

Toast manager (Nuxt composable)

Create a global toast system with a Nuxt composable:

ts
// composables/useToast.ts
import { ref } from 'vue'

export type ToastType = 'success' | 'error' | 'info'

interface Toast {
  id: number
  type: ToastType
  title: string
  body?: string
}

const toasts = ref<Toast[]>([])
let nextId = 0

export function useToast() {
  function add(type: ToastType, title: string, body?: string, duration = 4000) {
    const id = nextId++
    toasts.value.push({ id, type, title, body })
    setTimeout(() => remove(id), duration)
  }
  function remove(id: number) {
    toasts.value = toasts.value.filter(t => t.id !== id)
  }
  return { toasts, add, remove }
}
vue
<!-- components/ToastContainer.vue -->
<script setup>
import { useToast } from '~/composables/useToast'
const { toasts, remove } = useToast()
</script>

<template>
  <Teleport to="body">
    <div class="fixed bottom-4 right-4 z-50 flex flex-col gap-2 w-80">
      <TransitionGroup
        enter-active-class="transition ease-out duration-200"
        enter-from-class="opacity-0 translate-y-2"
        enter-to-class="opacity-100 translate-y-0"
        leave-active-class="transition ease-in duration-150"
        leave-from-class="opacity-100"
        leave-to-class="opacity-0 translate-x-4"
      >
        <div v-for="toast in toasts" :key="toast.id" class="...">
          <!-- toast markup -->
        </div>
      </TransitionGroup>
    </div>
  </Teleport>
</template>

Then use anywhere:

ts
const { add } = useToast()
add('success', 'Saved!', 'Your changes were applied.')

Accessibility notes

  • Success/info toasts: role="status" (polite announcement)
  • Error toasts: role="alert" (immediate announcement)
  • Include a dismiss button so keyboard and screen reader users can remove the toast manually
  • Don't auto-dismiss error toasts — users need time to read and act on failures

Released under the MIT License.