Back

VueでToast通知を実装する方法

VueでToast通知を実装する方法

ユーザーへのフィードバックを表示する必要があります—フォームの保存、APIコールの失敗、アップロードの成功など。Toast通知はこれをエレガントに解決します:ワークフローを中断することなく、情報を伝えて消える簡潔なメッセージです。

このガイドでは、Vue 3のToast通知に対する2つのアプローチを取り上げます:カスタムのコンポーザブルベースのシステムの構築と、Vue ToastificationやNotivueなどの確立されたライブラリの統合です。どちらも<script setup>構文を使用した最新のComposition APIパターンを採用しています。

重要なポイント

  • Vue 3のカスタムToastシステムには、共有のリアクティブステート、コンポーザブル関数、Teleportベースのコンテナコンポーネントの3つの要素だけが必要です。
  • Vue ToastificationやNotivueなどのサードパーティライブラリは、キュー管理、アニメーション、アクセシビリティ、テーマ設定を標準で処理します。
  • Nuxt 3では、Toast通知はクライアント側での実行が必要です—<ClientOnly>ラッパーまたは.client.tsプラグインサフィックスを使用してください。
  • アクセシブルなToastは、aria-live領域、明確なラベルを持つ閉じるボタンを使用し、prefers-reduced-motion設定を尊重します。

Vueコンポーザブルを使用したカスタムToastシステムの構築

最小限のVueコンポーザブル通知システムには3つの要素が必要です:リアクティブステート、コンポーザブル関数、そしてTeleportを使用したレンダリングコンポーネントです。

Toastコンポーザブル

任意のコンポーネントがアクセスできる共有のリアクティブストアを作成します:

// 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
  }
}

toasts refはコンポーザブル関数の外側で宣言されているため、useToast()への全ての呼び出しが同じリアクティブ配列を共有します。readonlyラッパーは、コンシューマーが直接ステートを変更することを防ぎます—addremoveのみがリストを変更できます。

Toastコンテナコンポーネント

Teleportを使用してToastをレンダリングし、コンポーネントツリーの外側に配置します:

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

App.vue<ToastContainer />を配置し、任意のコンポーネントからuseToast()を呼び出して通知をトリガーします。

ARIAロールに関する注意: コンテナはaria-live="polite"を使用しているため、スクリーンリーダーはユーザーを中断することなく新しいToastをアナウンスします。個々のToastはrole="alert"ではなくrole="status"を使用します。これはpoliteなライブ領域と正しくペアになります。role="alert"(aria-live="assertive"を暗黙的に含む)は、即座の注意を要求する緊急のエラーメッセージのために予約してください。

サードパーティライブラリの使用

本番アプリケーションでは、メンテナンスされたライブラリが、自分で構築する必要があるエッジケースを処理します:キュー管理、アニメーション、アクセシビリティ、テーマ設定などです。

Vue Toastificationのセットアップ

プラグインをインストールして登録します:

// 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')

コンポーネント内でコンポーザブルを使用します:

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

Notivueの代替案

Notivueは、追加のカスタマイズオプションと組み込みのアクセシビリティ機能を備えた、類似のコンポーザブルAPIを提供します。

Nuxt 3でのToast通知

Nuxt 3でのToast通知は、クライアント側のコンテキスト(例えば、イベントハンドラーやonMounted内)でトリガーする必要があります。これは、DOMを操作するため、サーバーサイドレンダリング中に実行すべきではないからです:

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

Nuxt UIを使用している場合、独自のuseToastコンポーザブルを提供しているため、追加のライブラリは不要です。その他のセットアップの場合は、Nuxtプラグインを作成します:

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

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

.client.tsサフィックスにより、プラグインはブラウザでのみ実行されます。

アクセシビリティに関する考慮事項

Toastはスクリーンリーダーユーザーにとってアクセシブルである必要があります:

  • Toastコンテナにaria-live="polite"を使用して、新しいメッセージが中断なくアナウンスされるようにします
  • 説明的なaria-label属性を持つ閉じるボタンを提供します
  • prefers-reduced-motionを尊重し、要求された場合はアニメーションを無効にします
  • エラーメッセージの自動消去を避けます—ユーザーは読んで行動するための時間が必要です
@media (prefers-reduced-motion: reduce) {
  .toast {
    animation: none
  }
}

まとめ

最小限のバンドルサイズや非常に特定の動作が必要な場合は、カスタムToastシステムを構築してください。実績のあるアクセシビリティ、アニメーション、設定オプションをメンテナンスのオーバーヘッドなしで求める場合は、Vue ToastificationまたはNotivueを使用してください。

どちらのアプローチもVue 3とNuxt 3で機能します。コンポーザブルパターンにより、コードはテスト可能になり、コンポーネントは通知ロジックから分離されます。まずカスタムコンポーザブルで仕組みを理解し、その後、ライブラリがプロジェクトの長期的なニーズにより適しているかどうかを評価してください。

よくある質問

はい。toasts refはモジュールレベルでuseToast関数の外側で宣言されているため、useToastを呼び出すすべてのコンポーネントは同じリアクティブステートを共有します。つまり、任意のコンポーネントが親コンポーネントを通じてプロップを渡したりイベントを発行したりすることなく、Toastをトリガーまたは閉じることができます。

TeleportはToastのDOMノードをbody要素に移動し、コンポーネント階層の外側に配置します。これにより、overflow hiddenやz-indexスタッキングコンテキストなどの親CSSがToastをクリッピングまたは非表示にすることを防ぎます。また、コンテナコンポーネントがどこにマウントされているかに関係なく、Toastの配置を一貫させます。

カスタムコンポーザブルでは、duration引数としてゼロまたは負の数を渡してsetTimeOut呼び出しをスキップします。Vue Toastificationでは、特定のToastに対してtimeoutをfalseに設定します。エラーメッセージは、ユーザーが読んで対応するのに十分な時間を持てるように、手動で閉じるまで持続する必要があります。

はい。Toast通知はDOMを操作するため、サーバーサイドレンダリング中は利用できません。ToastコンテナをClientOnlyコンポーネントでラップするか、.client.tsファイルサフィックスを使用してToastライブラリをクライアント専用のNuxtプラグインとして登録します。これにより、Toastロジックはブラウザでのみ実行されます。

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