Sonner の操作ガイド
初回応答
このスキルが特定の質問なしで最初に呼び出された場合、次のようにのみ応答します:
Sonner のセットアップ、スタイリング、トラブルシューティングのお手伝いをします。知識は作者の Emil Kowalski から得ています。
ユーザーが質問するまで、それ以上の情報は提供しないでください。
これは Sonner(トーストライブラリ)のガイドスキルです。Sonner に関連するタスク(配線、トーストのレンダリング、スタイリング、修正)が発生した場合は、最初にこのファイルから回答してください。<Toaster /> と toast() の完全なプロップテーブルは API.md にあります。正確なプロップ名、型、デフォルト値が必要な場合はそれを参照してください。
セットアップ
2つの要素だけです:
- 1つの
<Toaster />を1回だけマウントし、可能な限りルートに近い場所に配置します(Next.js ではlayout.tsx— サーバーコンポーネント内でも動作します)。ページごとや条件付きでレンダリングしないでください。2つ目の Toaster をマウントすると、すべてのトーストが重複します。 - クライアントコードから
toast()を呼び出します — イベントハンドラ、エフェクト、コールバックなど。これは単なる関数であり、フックやプロバイダーは不要ですが、サーバー側では何も行いません。サーバーアクション内では、結果を返し、それを受け取るクライアントコードでtoast()を呼び出してください。
import { Toaster } from 'sonner'; // 1回、レイアウトで
import { toast } from 'sonner'; // クライアント側のどこでも
適切な呼び出しの選択
| やりたいこと | 呼び出し方法 |
|---|---|
| プレーンメッセージ | toast('Title') — 2行目には { description } を追加 |
| 成功/エラー/情報/警告アイコン | toast.success('…')、toast.error('…') など |
| 自分で状態管理しながらスピナー表示 | toast.loading('…')、その後 ID で更新 |
| Promise に連動したローディング→成功/エラー | toast.promise(promise, { loading, success, error }) — success/error は解決値/エラーを受け取る関数も可 |
| アクション付きボタン | { action: { label, onClick } } — event.preventDefault() を呼ばない限り、クリックでトーストを閉じる。cancel はセカンダリーバリアント |
| カスタム JSX(デフォルトのトーストシェル付き) | toast(<jsx />) |
| カスタム JSX(スタイルなし) | toast.custom((t) => <jsx />) — ヘッドレス。t は破棄用の ID |
レシピ集
トーストの更新 — 同じ id で toast() を再度呼び出します。渡したプロップだけが変更されます。toast.success(…, { id }) に切り替えるとタイプが変わります。これが toast.promise を使わずにローディング→成功のフローを実現する方法です:
const id = toast.loading('アップロード中…');
toast.success('アップロード完了', { id });
永続化 — { duration: Infinity }。破棄 — toast.dismiss(id)、またはすべてを破棄する場合は toast.dismiss()。アクティブなトーストの読み取り — React 内では useSonner()、それ以外では toast.getActiveToasts()。
テキスト内のリンクやコンポーネント — タイトルまたは説明に関数を渡します:toast(() => <a href="…">表示</a>)。
複数の Toaster — それぞれに id を付け、toast('…', { toasterId: 'canvas' }) でターゲット指定します。toasterId がない場合、すべての Toaster がトーストをレンダリングします。
クローズコールバック — onDismiss は閉じるボタンまたはスワイプで発火。onAutoClose はタイムアウトで発火。これらは別々であり、単一の「閉じた」コールバックはありません。
スタイリング — エスカレーションラダー
変更の必要に応じてのみ段階を上がってください。最上位に早期にジャンプしても問題ありません(推奨される最終状態です)、中間で留まるのは避けてください。
- デフォルト — さらに Toaster に
richColorsを追加してカラフルな成功/エラー表示、invertでテーマに合わせて反転。 - インライン調整 — Toaster の
toastOptions={{ style: {…} }}ですべてのトーストに適用、またはtoast()呼び出しごとにstyleを指定。 - パーツへのクラス適用 —
toastOptions={{ classNames: { toast, title, description, actionButton, cancelButton, closeButton } }}。Sonner の注入スタイルがカスケードで優先されるため、すべてのクラスに!importantが必要です(Tailwind:!text-red-900)。多くに important を付けているなら、やめてヘッドレスに移行しましょう。 - ヘッドレス —
toast.custom()で独自の JSX を使用し、Sonner の配置、スタック、スワイプ機能は維持します。デザインシステムのトーストに推奨されるアプローチ:独自のtoast()抽象化でラップします。(unstyled: trueは中間的な選択肢として存在しますが、同じ労力ならヘッドレスの方が制御性が高いです。)
アイコン — Toaster の icons プロップでタイプごとにデフォルトを交換、トーストごとに icon で交換、null で削除。
テーマ — theme はデフォルトで 'light' であり、OS の設定を追跡しません。theme="system" を渡すか、テーマプロバイダーと連携します:next-themes から <Toaster theme={resolvedTheme} />。
トラブルシューティング
| 症状 | 原因 → 修正 |
|---|---|
| トーストが表示されない | <Toaster /> がマウントされていないか、アンマウントされた(条件付きレンダリング、ページごとの配置)。ルートに 1 つマウントしてください。サーバーアクションから呼び出す場合:toast() はクライアント専用です — アクションの結果をクライアントで受け取ったときに呼び出してください。 |
| 同じトーストが 2 つ表示される | 2 つの Toaster がマウントされている(レイアウト と ページ) — 1 つにしてください。または、React StrictMode の開発時ダブル呼び出しによりエフェクト内で toast() が 2 回発火している — イベントハンドラから呼び出すか、安定した id を渡して 2 回目の呼び出しが重複ではなく更新になるようにしてください。 |
| Tailwind/CSS クラスが効かない | デフォルトスタイルが上書きします。!important を付けるか、unstyled / ヘッドレスを使用してください(上のラダーを参照)。 |
| トーストが完全にスタイルなしで表示される(Astro、ビュートランジションでよくある) | Sonner の注入されたスタイルシートが失われた — レイアウトで明示的にインポート:import 'sonner/dist/styles.css'。 |
| Shadow DOM 内でスタイルなし | スタイルは document.head に配置され、シャドウルートには適用されません。テキストに [data-sonner-toaster] を含むスタイルタグをコピーしてシャバドウルートに配置してください。 |
| トーストがモーダル/オーバーレイの背後に隠れる、またはクリップされる | 祖先要素が重ね合わせコンテキストを作成している(transform、filter、overflow)か、オーバーレイの z-index が Toaster より高い。<Toaster /> をドキュメントルートに移動し、ダイアログやポータルコンテナの外に配置してください。 |
| ダークモードが無視される | theme はデフォルトで 'light' — theme="system" を設定するか、解決されたテーマを渡してください(上記のテーマを参照)。 |
| 成功/エラーがグレーで表示され、緑/赤にならない | それがデフォルトです。Toaster に richColors を追加してください。 |
| トーストが閉じない | duration: Infinity、dismissible: false、または toast.promise の Promise が解決されない — ローディングトーストが永遠に待機します。 |
toast.promise がローディングのまま止まる | 第一引数に Promise(または Promise を返す関数)が必要であり、その Promise が実際に resolve/reject する必要があります。 |
| スワイプで閉じる方向が間違っている/動作しない | 方向は position から派生します。Toaster の swipeDirections で上書きしてください。 |
| すべての Toaster にトーストが表示される | 複数の Toaster にはターゲット指定が必要です:各 Toaster に id を付け、toast() 呼び出しで toasterId を渡してください。 |
| モバイルでトーストが画面端に近すぎる | offset(デスクトップ、デフォルト 32px)と mobileOffset(<600px、デフォルト 16px)— 数値、CSS 文字列、または辺ごとのオブジェクト。 |