処理中・読み込み中であることを回転アニメーションで視覚的に伝えるUIコンポーネント。ボタン内・フォーム・ページ中央など様々な場所に配置して使われる。Skeleton(コンテンツ形状のプレースホルダー)と比べて実装が簡単だが、「何が来るか」をユーザーに予告できない点で使い所が限られる。
この記事を読むと、Skeleton・Progress Bar との使い分け・遅延表示による点滅防止・サイズ設計・role="status" と aria-label の実装・prefers-reduced-motion 対応が自分でできるようになります。
1. UI例(Preview / Live)
このスピナーは、デザインシステム GUNJO の Spinner 実装です。サイズ、ボタン内のインライン表示、領域のオーバーレイを確認できます(右上 Code で編集可)。
実装で見る(GunjoUI)
この部品を、デザインシステム GUNJO の実装で確かめられます。
2. 定義(Definition)
処理中・読み込み中を回転アニメーション(円弧のボーダーがくるくる回る)で視覚的に伝えるUIコンポーネント。CSSの border-radius: 50% + border-top-color + @keyframes spin で実装される最もシンプルなローディングUI。
Spinnerが適しているケース vs Skeleton が適しているケース:
| Spinner | Skeleton | |
|---|---|---|
| コンテンツ形状がわかる | ✗ 不向き | ✅ 向いている |
| 処理時間が短い(〜2秒) | ✅ 向いている | △ 過剰 |
| ボタン内・インライン | ✅ 向いている | ✗ 不向き |
| フィード・リスト | ✗ 不向き | ✅ 向いている |
3. 使い分け(When to use / When NOT to use)
3.1 When to use
- ボタンの処理中(送信・保存・削除の実行中)
- 認証・API呼び出しの短い待機(〜2秒程度)
- コンテンツ形状が事前に分からないデータ取得
- オーバーレイで操作をブロックしながら処理中を示す
3.2 When NOT to use
- コンテンツ形状が分かっている(カード・リスト・テーブル)→ Skeleton を使う
- 100ms以下の処理 → 表示しない(点滅防止)
- 進捗が計測できる処理 → Progress Bar を使う
3.3 代替UI(Alternatives)
- コンテンツ形状が分かっている →
Skeleton(Loading記事参照) - 進捗が計測できる →
Progress Bar - 処理完了の通知 →
Toast/Alert
4. 設計判断の核(Decision Principles)
Spinnerの核は「遅延表示」——200ms以下の高速処理にSpinnerを即時表示すると、ローディングが一瞬点滅してUIが不安定に見える。処理完了より先にSpinnerが出てはいけない。
判断の優先順位:① 遅延表示(200ms)→ ② Skeleton との使い分け → ③ サイズ選択 → ④ aria 対応
- 200ms遅延してから表示する:
setTimeout(() => setShowSpinner(true), 200)を設定し、200ms以内に処理が完了した場合は Spinner を表示しない。これで高速処理時の「チラつき」を防ぐ - サイズはコンテキストに合わせる:ボタン内は
xs(12px)〜sm(16px)、セクション内はmd(24px)〜lg(32px)、ページ全体はxl(48px)以上。小さすぎるSpinnerは見えにくく、大きすぎると圧迫感を与える - 色はコンテキストの主色を使う:青系ブランドなら青、暗い背景上なら白を使う。グレーはニュートラルな場面に使う
5. 状態設計(States)
5.1 必須状態(Required)
- Visible(表示中):アニメーション回転
- Hidden(非表示):処理完了後または200ms遅延前
5.2 条件付き状態(Conditional)
prefers-reduced-motion: reduce:アニメーションを止めて静的な表示にする
6. バリエーション設計(Variants)
| バリアント | サイズ | 用途 |
|---|---|---|
| インライン(ボタン内) | xs / sm | ボタンの処理中 |
| セクション | md / lg | カードやモーダル内の読み込み |
| ページ | xl | ページ全体の初期ロード |
| オーバーレイ | lg / xl | コンテンツを操作不能にしながら処理中 |
7. パターン集(Good / Bad / How to fix)
7.0 よく崩れる設計パターン(3つ)
- 即時表示による点滅:
onClickでsetLoading(true)を即時実行し、100ms以下で完了する処理にSpinnerが一瞬だけ表示される - ページ全体をSpinnerで覆う:リスト・カードなどコンテンツ形状が分かっているのに、ページ中央にSpinnerだけ表示してコンテンツが全く見えない(Skeletonを使うべき場面)
- 複数のSpinnerが同時表示:ページに3〜4箇所の独立したSpinnerが同時に表示されて、どこに注目すべきか分からない
7.1 Bad(典型3つ)
setLoading(true)を即時実行し、APIが50msで返ってきてもSpinnerが表示される- ユーザー一覧ページでSkeletonを使わずSpinner1個だけ表示し、リスト形状が何も伝わらない
<div className="spinner" />にroleもaria-labelもなく、スクリーンリーダーが処理中を認識できない
7.2 Good(対になる3つ)
useEffectでconst t = setTimeout(() => setShowSpinner(true), 200); return () => clearTimeout(t)を実装し、200ms以内の完了はSpinnerを表示しない- ユーザー一覧ではアバター円 + 名前行 + 説明行の Skeleton を使い、データ取得中もレイアウトが崩れない
<div role="status" aria-label="読み込み中" className="spinner" />で音声ユーザーに処理中を伝える
7.3 How to fix(手順)
const [showSpinner, setShowSpinner] = useState(false)を追加useEffect(() => { if (!isLoading) { setShowSpinner(false); return; } const t = setTimeout(() => setShowSpinner(true), 200); return () => clearTimeout(t); }, [isLoading])で遅延表示を実装<div role="status" aria-label="読み込み中" />で ARIA 対応- コンテンツ形状が分かる場所は Skeleton に置き換えを検討する
8. ルール(Must / Better)
Must(守らないと壊れる)
Better(品質が跳ねる)
9. アクセシビリティ要件(必須)
Screen Reader
<div role="status" aria-label="読み込み中">
<!-- 視覚的なSpinnerアニメーション -->
<span class="sr-only">読み込み中</span>
</div>
role="status" は aria-live="polite" と同等。sr-only(visually hidden)テキストを含めるとスクリーンリーダーがより確実に読み上げる。
Motion / Animation
@media (prefers-reduced-motion: reduce) {
.spinner { animation: none; opacity: 0.6; }
}
Tailwind では motion-reduce:animate-none クラスで対応できる。
10. 実装メモ(Implementation Notes)
- Tailwind の
animate-spinは CSSanimation: spin 1s linear infiniteと同等。border-t-[color]を付けた円形要素に適用するだけでSpinnerになる lucide-reactのLoader2アイコン(animate-spinと組み合わせ)を使うと、カスタムCSSを書かずにデザイン統一されたSpinnerを実装できる- shadcn/ui には Spinner コンポーネントは含まれていないが、公式ドキュメントにCSSアニメーションベースの実装例が記載されている
11. 関連リンク
- 関連するUIデザイン原則: フィードバック (Feedback), 状態の可視化 (Visibility of System Status)
- 用語集(定義): アクセシビリティ (Accessibility)
- 関連するUIコンポーネント(横): Loading(ローディング), Progress Bar(プログレスバー), Empty State(空状態)
12. まとめ
Spinnerの設計で最重要なのは「遅延表示」と「Skeletonとの使い分け」です。迷ったら 4. 設計判断の核 に戻り、「200msの遅延表示があるか」「コンテンツ形状が分かるならSkeletonに変えるべきではないか」「role="status" があるか」の3点を確認してください。Spinnerはすべての待機に使う万能UIではなく、「コンテンツ形状が事前に分からない短い待機」に特化したコンポーネントです。