ask-sonner について
ask-sonnerは、Sonnerを使う開発者向けのガイドスキルです。導入、トーストの動作やスタイル、トラブル対応を扱い、必要に応じてAPIの詳細を確認する方法も示します。
主な機能
- 01Sonnerの導入と呼び出し方
- 02トースト種別とPromise処理
- 03スタイル、テーマ、アイコンの設定
- 04重複表示や配置の問題への対処
活用シーン
- 01ReactアプリにSonnerを導入
- 02ローディング表示を成功・エラーに更新
- 03トーストの外観やテーマを調整
- 04未表示、重複、隠れたトーストの原因を調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ emilkowalski/ skills --skill ask-sonnerインストール後、Claude Code で/ask-sonnerを入力して読み込まれたか確認します。
同梱ファイルAPI.md
詳細
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'; // once, in layout
import { toast } from 'sonner'; // anywhere client-side
適切な呼び出しの選び方
| やりたいこと | 呼び出し |
|---|---|
| シンプルなメッセージ | toast('Title') — 2 行目には { description } を追加 |
| 成功 / エラー / 情報 / 警告のアイコン | toast.success('…')、toast.error('…') など |
| 自分で状態を管理しながらスピナーを表示 | toast.loading('…')、その後 id で更新 |
| ローディング → 成功/エラーをプロミスに紐づける | toast.promise(promise, { loading, success, error }) — success/error は解決された値/エラーを受け取る関数を指定できます |
| 何かを行うボタン | { action: { label, onClick } } — 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('Uploading…');
toast.success('Uploaded', { id });
永続化 — { duration: Infinity }。却下 — toast.dismiss(id)、すべてを却下するには toast.dismiss()。アクティブなトーストを読み取る — React では useSonner()、それ以外では toast.getActiveToasts()。
テキスト内のリンクやコンポーネント — タイトルまたは説明に使用する関数を渡します:toast(() => <a href="…">View</a>)。
複数の toaster — それぞれに id を付け、toast('…', { toasterId: 'canvas' }) で対象を指定します。toasterId がない場合、すべての toaster がトーストをレンダリングします。
クローズコールバック — onDismiss は閉じるボタンまたはスワイプで発火します。onAutoClose はタイムアウトで発火します。これらは別々であり、単一の「closed」コールバックはありません。
スタイリング — エスカレーションのはしご
変更が必要とする範囲までしか登らないでください。早すぎる段階で一番上の段に飛びつくのは問題ありません(それが推奨される最終状態です)が、途中の段での居座りはよくありません。
- デフォルト — さらに Toaster に
richColorsを付けると成功/エラーがカラフルになり、invertでテーマに対して反転します。 - インラインでの微調整 — すべてのトーストに対しては Toaster の
toastOptions={{ style: {…} }}、個々のtoast()呼び出しに対してはstyleを使用します。 - パーツへのクラス付与 —
toastOptions={{ classNames: { toast, title, description, actionButton, cancelButton, closeButton } }}。Sonner が注入するスタイルがカスケードで優先されるため、すべてのクラスに!important(Tailwind:!text-red-900)が必要です。重要指定をいくつも付けているなら、手を止めてヘッドレスに移行してください。 - ヘッドレス — 独自の JSX で
toast.custom()を使い、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つにしてください。または toast() が React StrictMode の開発時二重呼び出しの effect 内で発火している — イベントハンドラーから発火するようにするか、安定した id を渡して2回目の呼び出しが重複ではなく更新になるようにしてください。 |
| Tailwind/CSS のクラスが効かない | デフォルトスタイルがそれらを上書きしています。!important を付けるか、unstyled / ヘッドレスを使用してください(上記のはしごを参照)。 |
| トーストが完全にスタイルなしでレンダリングされる(Astro、ビュートランジションでよくある) | Sonner が注入するスタイルシートが失われています — レイアウトで明示的にインポートしてください:import 'sonner/dist/styles.css'。 |
| Shadow DOM 内でスタイルが適用されない | スタイルは document.head に配置され、shadow ルートには入りません。テキストに [data-sonner-toaster] を含む style タグを shadow ルートにコピーしてください。 |
| トーストがモーダル/オーバーレイの背後に隠れる、またはクリップされる | 祖先要素がスタッキングコンテキスト(transform、filter、overflow)を作成しているか、オーバーレイの z-index が toaster より上になっています。<Toaster /> を document ルートの、ダイアログ/ポータルのコンテナの外側に移動してください。 |
| ダークモードが無視される | theme のデフォルトは 'light' です — theme="system" を設定するか、解決済みのテーマを渡してください(上記のテーマを参照)。 |
| 成功/エラーが緑/赤ではなくグレーに見える | それがデフォルトです。Toaster に richColors を追加してください。 |
| トーストが閉じない | duration: Infinity、dismissible: false、または解決しない promise を渡した toast.promise — ローディングトーストが永遠に待機します。 |
toast.promise がローディングのままになる | 最初の引数に promise(または promise を返す関数)が必要で、その promise が実際に resolve/reject する必要があります。 |
| スワイプで閉じる方向が逆になる / 機能しない | 方向は position から導出されます。Toaster の swipeDirections で上書きしてください。 |
| トーストがすべての toaster に表示される | 複数の toaster にはターゲット指定が必要です:各 Toaster に id を付け、toast() 呼び出しで toasterId を渡してください。 |
| モバイルでトーストが画面端に近すぎる | offset(デスクトップ、デフォルト 32px)と mobileOffset(<600px、デフォルト 16px) — 数値、CSS 文字列、または辺ごとのオブジェクトを指定できます。 |