Back

Cómo Implementar Notificaciones Toast en Vue

Cómo Implementar Notificaciones Toast en Vue

Necesitas mostrar retroalimentación a los usuarios: un formulario guardado, una llamada API fallida, una carga exitosa. Las notificaciones toast resuelven esto de manera elegante: mensajes breves que aparecen, entregan información y desaparecen sin interrumpir el flujo de trabajo.

Esta guía cubre dos enfoques para notificaciones toast en Vue 3: construir un sistema personalizado basado en composables e integrar bibliotecas establecidas como Vue Toastification o Notivue. Ambos usan patrones modernos de la Composition API con sintaxis <script setup>.

Puntos Clave

  • Un sistema toast personalizado en Vue 3 necesita solo tres piezas: estado reactivo compartido, una función composable y un componente contenedor basado en Teleport.
  • Las bibliotecas de terceros como Vue Toastification y Notivue manejan la gestión de colas, animaciones, accesibilidad y tematización de forma predeterminada.
  • En Nuxt 3, las notificaciones toast requieren ejecución solo en el cliente—usa envoltorios <ClientOnly> o sufijos de plugin .client.ts.
  • Los toasts accesibles usan regiones aria-live, botones de cierre con etiquetas claras y respetan las preferencias prefers-reduced-motion.

Construyendo un Sistema Toast Personalizado con Composables de Vue

Un sistema de notificaciones composable mínimo en Vue requiere tres piezas: estado reactivo, una función composable y un componente renderizado usando Teleport.

El Composable Toast

Crea un almacén reactivo compartido al que cualquier componente pueda acceder:

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

interface Toast {
  id: number
  message: string
  type: 'success' | 'error' | 'info' | 'warning'
}

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

export function useToast() {
  const add = (message: string, type: Toast['type'] = 'info', duration = 3000) => {
    const toast = { id: ++id, message, type }
    toasts.value.push(toast)

    if (duration > 0) {
      setTimeout(() => remove(toast.id), duration)
    }
  }

  const remove = (toastId: number) => {
    toasts.value = toasts.value.filter(t => t.id !== toastId)
  }

  return {
    toasts: readonly(toasts),
    success: (msg: string) => add(msg, 'success'),
    error: (msg: string) => add(msg, 'error'),
    info: (msg: string) => add(msg, 'info'),
    warning: (msg: string) => add(msg, 'warning'),
    remove
  }
}

El ref toasts se declara fuera de la función composable para que cada llamada a useToast() comparta el mismo array reactivo. El envoltorio readonly previene que los consumidores muten el estado directamente—solo add y remove pueden modificar la lista.

El Componente Contenedor de Toast

Renderiza los toasts usando Teleport para posicionarlos fuera de tu árbol de componentes:

<!-- components/ToastContainer.vue -->
<script setup lang="ts">
import { useToast } from '@/composables/useToast'

const { toasts, remove } = useToast()
</script>

<template>
  <Teleport to="body">
    <div
      class="toast-container"
      role="region"
      aria-live="polite"
      aria-label="Notifications"
    >
      <div
        v-for="toast in toasts"
        :key="toast.id"
        :class="['toast', `toast--${toast.type}`]"
        role="status"
      >
        {{ toast.message }}
        <button @click="remove(toast.id)" aria-label="Dismiss notification">×</button>
      </div>
    </div>
  </Teleport>
</template>

Coloca <ToastContainer /> en tu App.vue, luego llama a useToast() desde cualquier componente para activar notificaciones.

Nota sobre roles ARIA: El contenedor usa aria-live="polite" para que los lectores de pantalla anuncien nuevos toasts sin interrumpir al usuario. Los toasts individuales usan role="status" en lugar de role="alert", que se empareja correctamente con la región live polite. Reserva role="alert" (que implica aria-live="assertive") para mensajes de error urgentes que demandan atención inmediata.

Usando Bibliotecas de Terceros

Para aplicaciones en producción, las bibliotecas mantenidas manejan casos extremos que de otro modo tendrías que construir tú mismo: gestión de colas, animaciones, accesibilidad y tematización.

Configuración de Vue Toastification

Instala y registra el plugin:

// main.ts
import { createApp } from 'vue'
import Toast from 'vue-toastification'
import 'vue-toastification/dist/index.css'
import App from './App.vue'

const app = createApp(App)
app.use(Toast, {
  position: 'top-right',
  timeout: 5000
})
app.mount('#app')

Usa el composable en los componentes:

<script setup>
import { useToast } from 'vue-toastification'

const toast = useToast()

const handleSave = async () => {
  try {
    await saveData()
    toast.success('Changes saved')
  } catch {
    toast.error('Save failed')
  }
}
</script>

Alternativa Notivue

Notivue ofrece una API composable similar con opciones adicionales de personalización y características de accesibilidad integradas.

Notificaciones Toast en Nuxt 3

Las notificaciones toast en Nuxt 3 deben activarse en contextos del lado del cliente (por ejemplo, en manejadores de eventos o onMounted) porque manipulan el DOM y no deben ejecutarse durante el renderizado del lado del servidor:

<!-- app.vue -->
<template>
  <NuxtPage />
  <ClientOnly>
    <ToastContainer />
  </ClientOnly>
</template>

Si estás usando Nuxt UI, proporciona su propio composable useToast—no se necesita biblioteca adicional. Para otras configuraciones, crea un plugin de Nuxt:

// plugins/toast.client.ts
import Toast from 'vue-toastification'
import 'vue-toastification/dist/index.css'

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.use(Toast)
})

El sufijo .client.ts asegura que el plugin solo se ejecute en el navegador.

Consideraciones de Accesibilidad

Los toasts deben ser accesibles para usuarios de lectores de pantalla:

  • Usa aria-live="polite" en el contenedor de toasts para que los nuevos mensajes se anuncien sin interrupción
  • Proporciona botones de cierre con atributos aria-label descriptivos
  • Respeta prefers-reduced-motion deshabilitando animaciones cuando se solicite
  • Evita el cierre automático de mensajes de error—los usuarios necesitan tiempo para leer y actuar sobre ellos
@media (prefers-reduced-motion: reduce) {
  .toast {
    animation: none
  }
}

Conclusión

Construye un sistema toast personalizado cuando necesites un tamaño de bundle mínimo o comportamiento altamente específico. Usa Vue Toastification o Notivue cuando quieras accesibilidad probada, animaciones y opciones de configuración sin sobrecarga de mantenimiento.

Ambos enfoques funcionan en Vue 3 y Nuxt 3. El patrón composable mantiene tu código testeable y tus componentes desacoplados de la lógica de notificaciones. Comienza con el composable personalizado para entender la mecánica, luego evalúa si una biblioteca sirve mejor las necesidades a largo plazo de tu proyecto.

Preguntas Frecuentes

Sí. Debido a que el ref toasts se declara fuera de la función useToast a nivel de módulo, cada componente que llama a useToast comparte el mismo estado reactivo. Esto significa que cualquier componente puede activar o cerrar toasts sin pasar props o emitir eventos a través de componentes padre.

Teleport mueve los nodos DOM del toast al elemento body, fuera de tu jerarquía de componentes. Esto previene que el CSS padre como overflow hidden o contextos de apilamiento z-index recorten u oculten tus toasts. También mantiene el posicionamiento del toast consistente independientemente de dónde se monte el componente contenedor.

En el composable personalizado, pasa cero o un número negativo como argumento de duración para omitir la llamada setTimeout. Con Vue Toastification, establece timeout en false para toasts específicos. Los mensajes de error deben persistir hasta que el usuario los cierre manualmente para que tengan suficiente tiempo para leer y responder.

Sí. Las notificaciones toast manipulan el DOM, que no está disponible durante el renderizado del lado del servidor. Envuelve tu contenedor de toast en un componente ClientOnly o registra tu biblioteca de toast como un plugin de Nuxt solo para cliente usando el sufijo de archivo .client.ts. Esto asegura que la lógica de toast solo se ejecute en el navegador.

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay