はじめに 🌟
画面を開いた瞬間に、コンテンツがまだ読み込まれていないとき、あなたのアプリは何を表示していますか?真っ白な空白、くるくる回るスピナー、それとも実際のレイアウトを模した灰色のプレースホルダー?
Skeleton(スケルトン) は、コンテンツが読み込まれる前に、実際の配置・形状を模したプレースホルダーを表示するコンポーネントです。ユーザーは「何かが来るんだな」という文脈を瞬時に把握でき、Spinner だけを見ているときよりも待ち時間の体感が短くなることが知られています。
この記事では、次の内容を扱います。
- Fluent UI 2(React)の Skeleton コンポーネントの概要・API・使い方
- ProgressBar・Spinner との使い分け判断
- ガイダンス・動作・アクセシビリティの要点
- Fluent UI Blazor 5 の
FluentSkeletonとの API 比較
React と Blazor の両方で Fluent UI を使っている方、これから導入を検討している方のどちらにも役立てていただければ幸いです 🌟
Fluent UI 2 とは
Fluent UI 2(Fluent 2)は Microsoft が提供するデザインシステムおよびコンポーネントライブラリです。Web 向けには @fluentui/react-components(v9 系、converged components)として提供されており、アクセシビリティ・テーマ・統一された操作モデルが設計の中心に据えられています。
"Fluent's components meet or surpass WCAG 2.1 AA standards to ensure an accessible foundation."
— Fluent 2 Accessibility
本記事は、以下のシリーズの一部です。過去記事もあわせてご参照ください。
| 記事タイトル | リンク |
|---|---|
| Fluent UI 2 の Accordion を理解する — 情報設計・アクセシビリティ・React 実装 | 記事へ |
| Fluent UI 2 の Avatar を整理しつつ Fluent UI Blazor 5 でどう実装するか | 記事へ |
| Fluent UI 2 の Badge を理解する — Fluent UI Blazor 5 との比較と実装ポイント | 記事へ |
| Fluent UI 2 の Breadcrumb を理解する — Fluent UI Blazor 5 との対応とアクセシビリティ | 記事へ |
| Fluent UI 2 の Button を理解する — 種類・レイアウト・アクセシビリティ・Blazor 5 比較 | 記事へ |
| Fluent UI 2 の Card を理解する — React と Fluent UI Blazor の違い | 記事へ |
| Fluent UI 2 の Checkbox を理解する — React と Fluent UI Blazor v5 の違い | 記事へ |
| Fluent UI 2 の Combobox を理解する — Fluent UI Blazor 5 との比較と使い分け | 記事へ |
| Fluent UI 2 の Dialog を理解する — React と Fluent UI Blazor 5 の違いと使い分け | 記事へ |
| Fluent UI 2 の Divider を理解する — Fluent UI Blazor 5 との比較 | 記事へ |
| Fluent UI 2 の Dropdown を理解する — Fluent UI Blazor 5 の近縁コンポーネント比較とアクセシビリティ | 記事へ |
| Fluent UI 2 の Field を理解する — Fluent UI Blazor 5 と比較する入力設計の基礎 | 記事へ |
| Fluent UI 2 の Icon を理解する — Fluent UI Blazor の FluentIcon 比較とアクセシビリティ | 記事へ |
| Fluent UI 2 の Image を理解する — Fluent UI Blazor 5 と比較しながらレイアウトとアクセシビリティを整理する | 記事へ |
| Fluent UI 2 の Label を理解する — Fluent UI Blazor 5 と比較するラベル設計の基礎 | 記事へ |
| Fluent UI 2 の Link を理解する — Fluent UI Blazor 5 と比較するリンク設計とアクセシビリティ | 記事へ |
| Fluent UI 2 の Menu を理解する — Fluent UI Blazor 5 と比較するガイダンス・動作・アクセシビリティ | 記事へ |
| Fluent UI 2 の MessageBar を理解する — Fluent UI Blazor 5 と比較する機能・使用方法・アクセシビリティ | 記事へ |
| Fluent UI 2 の Nav を理解する — ガイダンス・動作・レイアウト・アクセシビリティと Fluent UI Blazor 5 比較 | 記事へ |
| Fluent UI 2 の Persona を理解する — ガイダンス・レイアウト・アクセシビリティ・Fluent UI Blazor 5 比較 | 記事へ |
| Fluent UI 2 の Popover を理解する — Fluent UI Blazor 5 との比較と Tooltip / Dialog の使い分け | 記事へ |
| Fluent UI 2 の Radio Group を理解する — Fluent UI Blazor 5 との比較・使い分け・アクセシビリティ | 記事へ |
| Fluent UI 2 の Rating を理解する — Fluent UI Blazor 5 と比較する評価 UI の設計とアクセシビリティ | 記事へ |
| Fluent UI 2 の Searchbox を理解する — Fluent UI Blazor 5 との比較とアクセシビリティ・コンテンツ設計 | (未公開) |
| Fluent UI 2 の Select を理解する — Dropdown / Combobox / Field との使い分けと Fluent UI Blazor 5 比較 | (未公開) |
| Fluent UI 2 のアクセシビリティを色から読む ─ WCAG 2.1 と対比しながら整理する | 記事へ |
| Fluent UI 2 で始めるアクセシビリティ実装 — キーボード操作・支援技術・WCAG 2.1 の実践ガイド | 記事へ |
それでは本題に入っていきましょう。まず Skeleton そのものの役割を整理します。
Skeleton コンポーネントとは
Skeleton はコンテンツが読み込まれるまでの間、実際のレイアウトを模した灰色のプレースホルダーを表示するコンポーネントです。目的は次の 2 点です。
- 知覚的な待ち時間を短縮する — 何かが来ることをユーザーが把握できるため、「まだ何も起きていない」という不安を取り除きます。
- レイアウトシフト(CLS)を抑制する — コンテンツが描画されたときに画面が大きく崩れると、ユーザーは読んでいた箇所を見失います。Skeleton は本物と同じ形状・配置を模するので、コンテンツが入ってきても大きな移動が発生しません。
どんな状況で使うか
Skeleton が特に効果的な場面:
- カード・プロフィール・リストアイテムなど、形状が予測可能なコンテンツが遅延読み込みされる
- ページ全体やセクション全体が非同期で取得される
- 待ち時間が 1〜5 秒程度見込まれる(瞬時に終わるなら Skeleton は不要、長時間なら ProgressBar の方が適切)
Fluent UI 2 における Skeleton の主要コンポーネントは 2 つです。
| コンポーネント | 役割 |
|---|---|
<Skeleton> |
Skeleton 全体のラッパー。aria-label で読み込み中の説明を持ち、アニメーション・外観を制御する |
<SkeletonItem> |
個々のプレースホルダー要素。丸(circle)・矩形(rectangle)の形状を持つ |
次に、似た役割を持つ ProgressBar・Spinner と何が違うのかを比較します。
ProgressBar・Spinner との使い分け
3 つのコンポーネントはいずれも「処理中」を伝えますが、伝え方と適切な場面が異なります。
ユースケース比較表
| 特徴 | Skeleton | ProgressBar | Spinner |
|---|---|---|---|
| 主な用途 | コンテンツ読み込みのプレースホルダー | 処理の進捗(確定/不確定) | 短時間の処理中表示 |
| 形状の模倣 | ✅ 実際のコンテンツ形状を模す | ❌ 横棒のみ | ❌ 回転アイコンのみ |
| 進捗表示 | ❌ できない | ✅ 確定・不確定どちらも対応 | ❌ 不確定のみ |
| 適した待ち時間 | 1〜5 秒 | 任意(完了まで時間がかかる処理) | 0.5〜3 秒程度 |
| CLS 対策 | ✅ 優秀 | △(コンテンツ上部に配置が多い) | ❌ コンテンツ自体は非表示 |
| アクセシビリティ |
aria-busy + aria-label
|
aria-valuenow / aria-valuetext
|
role="progressbar" + aria-labelledby
|
| 推奨場面 | カード・リスト・プロフィールの遅延読み込み | ファイルアップロード・バッチ処理 | ボタン操作後の応答待ち・ページ切り替え |
React の各コンポーネント API 概要
Skeleton(@fluentui/react-components):
import { Skeleton, SkeletonItem } from "@fluentui/react-components";
// animation: "wave" | "pulse"
// appearance: "opaque" | "translucent"
<Skeleton aria-label="プロフィール情報を読み込み中" animation="wave" appearance="opaque">
<SkeletonItem shape="circle" size={40} />
<SkeletonItem style={{ width: "200px" }} />
<SkeletonItem style={{ width: "150px" }} />
</Skeleton>
ProgressBar(@fluentui/react-components):
import { ProgressBar } from "@fluentui/react-components";
// value: 0〜1の小数。undefined で不確定(indeterminate)
// shape: "rounded" | "square"
// thickness: "medium" | "large"
// color: "brand" | "success" | "warning" | "error"
<ProgressBar value={0.6} shape="rounded" thickness="medium" />
<ProgressBar /* value 未指定 */ shape="rounded" /> {/* 不確定 */}
Spinner(@fluentui/react-components):
import { Spinner } from "@fluentui/react-components";
// size: "extra-tiny" | "tiny" | "extra-small" | "small" | "medium" | "large" | "extra-large" | "huge"
// appearance: "primary" | "inverted"
<Spinner size="medium" label="処理中..." labelPosition="after" />
使い分けの基準: コンテンツの形状が予測できるなら Skeleton、処理の進捗を数値で伝えたいなら ProgressBar、ページ操作への即時フィードバックなら Spinner を選びます。3 つを混在させると、ユーザーは「何が起きているのか」を判断しにくくなります。
それでは、Skeleton を実際に使うにあたってのガイダンスを見ていきます。
ガイダンス・動作・レイアウト
Do / Don't
✅ Do — やるべきこと
- 実際のコンテンツ配置をできるだけ忠実に模した形状を使う(丸いアバターがあるなら
shape="circle"、テキスト行なら細い矩形) - ただし細部まで再現しすぎない。たとえば Persona なら「アバター・タイトル・サブタイトル」の 3 要素で十分。プレゼンスバッジのような細部まで Skeleton で表現すると、実コンテンツと食い違ったときに余計な混乱を生む
- 読み込み完了後はスムーズに実コンテンツへ切り替える(transition で CLS を最小化する)
- Skeleton に
aria-labelを付けて、何の読み込み中かを説明する -
aria-busyをコンテナに付与し、読み込み完了後にfalseか削除する - API 呼び出しは可能な限り同時に行う。データを順次取得すると Skeleton のアニメーションがバラバラになり、ページが断片的・多忙な印象になる
❌ Don't — やってはいけないこと
- コンテンツの構造が予測できない場合に Skeleton を使う。構造が不明なら ProgressBar か Spinner のほうが適切
- タブバーやナビゲーションなど、固定レイアウトの部分に Skeleton を使う。動的に変わらない要素は Skeleton を置かなくてよい
- Skeleton のアニメーションを長時間継続させる(ユーザーが操作できないと感じる)
- Skeleton と ProgressBar を同じコンテンツ領域に重複配置する
- 実際のコンテンツより極端に大きい・小さい Skeleton を使う(読み込み後のレイアウトシフトが大きくなる)
-
prefers-reduced-motionの対応を忘れる
アニメーションの選択
Fluent UI 2 の Skeleton は wave(デフォルト)と pulse の 2 種類のアニメーションを持ちます。
| アニメーション | 特徴 | 推奨場面 |
|---|---|---|
wave |
光の波が横に流れる。滑らかな印象 | デフォルト推奨。特に画面に Skeleton が多い場合 |
pulse |
要素全体が点滅するように明滅する | Skeleton が少ない場合、波方向が自然でない縦長レイアウト |
画面に多数の Skeleton が並ぶ場合は wave を統一することで、バラバラな動きを防ぎ流れるような体験になります。
複数の Skeleton が避けられない場合は、すべてのアニメーションが同期していることを確認してください。アニメーションがバラバラに動くと、ページが過剰に「動いている」印象になります。
基本的な動作
Skeleton は非同期処理の実行中だけ表示し、処理完了後に実コンテンツへ差し替えるパターンが基本です。React では次のように状態管理と組み合わせます。
import { Skeleton, SkeletonItem, Avatar, Text } from "@fluentui/react-components";
import { useState, useEffect } from "react";
const ProfileCard = () => {
const [isLoading, setIsLoading] = useState(true);
const [profile, setProfile] = useState(null);
useEffect(() => {
fetchProfile().then((data) => {
setProfile(data);
setIsLoading(false);
});
}, []);
if (isLoading) {
return (
<div aria-busy="true" aria-label="プロフィール情報を読み込み中">
<Skeleton animation="wave">
<div style={{ display: "flex", gap: "12px", alignItems: "center" }}>
<SkeletonItem shape="circle" size={40} />
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
<SkeletonItem style={{ width: "120px", height: "16px" }} />
<SkeletonItem style={{ width: "80px", height: "12px" }} />
</div>
</div>
</Skeleton>
</div>
);
}
return (
<div aria-busy="false">
<div style={{ display: "flex", gap: "12px", alignItems: "center" }}>
<Avatar name={profile.name} image={{ src: profile.avatarUrl }} />
<div>
<Text weight="semibold">{profile.name}</Text>
<Text size={200}>{profile.email}</Text>
</div>
</div>
</div>
);
};
レイアウトの考え方
Skeleton の形状は実際のコンテンツの「シルエット」であるべきです。具体的には次のルールで組み立てます。
- アバター画像 →
shape="circle"+size={40}など実寸に合わせた size - 見出しテキスト → 高さ 16〜20px の矩形、幅はコンテンツ列の 60〜80%
- 本文テキスト行 → 高さ 12〜14px の矩形、幅は 90〜100%
- アクションボタン → 高さ 32px 程度の矩形、実際のボタン幅に合わせる
ここまで基本的な使い方を確認しました。次はアクセシビリティの要点を詳しく見ていきます。
アクセシビリティ ♿
Skeleton は視覚的なフィードバックとしては優秀ですが、スクリーンリーダーのユーザーには何も伝わらない状態になりがちです。アクセシビリティ対応は後回しにしやすい部分ですが、ここは丁寧に設計することが重要です。
aria-busy の正しい使い方
aria-busy="true" は、そのコンテナがまだ完全に描画されていないことを支援技術に伝える属性です。
<!-- 読み込み中 -->
<div aria-busy="true" aria-label="ニュースフィードを読み込み中">
<!-- Skeleton を配置 -->
</div>
<!-- 読み込み完了後 -->
<div aria-busy="false">
<!-- 実コンテンツを配置 -->
</div>
aria-busy は Skeleton の 外側のコンテナに付けます。Skeleton コンポーネント自体に付けると、Skeleton の消滅とともに属性が消えてしまい、aria-busy="false" への切り替えを明示できません。
複数の Skeleton が同一エリアに存在し、すべて同時に更新されない可能性がある場合も aria-busy が有効です。aria-busy="true" の間、スクリーンリーダーはアナウンスを保留するため、すべてのコンテンツが揃ってから false に切り替えることで、途中状態を読み上げずに済みます。
aria-label / aria-labelledby
Skeleton 全体のラッパーに aria-label を付けることで、スクリーンリーダーのユーザーが「何が読み込まれているのか」を把握できます。
// ❌ 悪い例: 汎用的すぎて何が来るのか伝わらない
<Skeleton aria-label="Loading...">
// ✅ 良い例: 具体的なコンテンツを説明する
<Skeleton aria-label="ユーザープロフィール情報を読み込み中">
<Skeleton aria-label="注文履歴リストを読み込み中">
<Skeleton aria-label="ダッシュボードのメトリクスを読み込み中">
ライブリージョンで読み込み完了を伝える
aria-busy を false に変更するだけでは、スクリーンリーダーは「完了した」ことを自動的に読み上げません。読み込み完了を伝えるには aria-live リージョンを使うのが確実です。
aria-live リージョンは使いすぎると逆効果です。ページ内にライブリージョンが多すぎると、スクリーンリーダーのアナウンスが途切れなく流れ続けてユーザーの集中を妨げます。ページ全体で必要最小限の数に絞り、aria-live="polite" を基本として、緊急でない読み込み完了には割り込まない polite を使いましょう。
import { useState, useEffect, useRef } from "react";
const DataSection = () => {
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState(null);
const announceRef = useRef(null);
useEffect(() => {
fetchData().then((result) => {
setData(result);
setIsLoading(false);
// ライブリージョンにメッセージをセット
if (announceRef.current) {
announceRef.current.textContent = "注文履歴の読み込みが完了しました";
}
});
}, []);
return (
<>
{/* aria-live リージョン(非表示・スクリーンリーダー向け) */}
<div
ref={announceRef}
aria-live="polite"
aria-atomic="true"
style={{ position: "absolute", width: "1px", height: "1px", overflow: "hidden", clip: "rect(0 0 0 0)" }}
/>
<div aria-busy={isLoading} aria-label={isLoading ? "注文履歴を読み込み中" : undefined}>
{isLoading ? (
<Skeleton animation="wave">
{/* SkeletonItem を配置 */}
</Skeleton>
) : (
<OrderList orders={data} />
)}
</div>
</>
);
};
Reduced Motion への対応
prefers-reduced-motion: reduce を設定しているユーザーは、点滅・波紋などのアニメーションを不快に感じたり、前庭障害の症状が出たりすることがあります。
Fluent UI 2 の Skeleton は内部で @media (prefers-reduced-motion: reduce) に対応しており、wave・pulse アニメーションの animation-duration を 0.01ms・反復回数を 1 回に短縮することで、実質的に不可視にします。ただし、カスタム CSS でアニメーションを追加した場合は、必ず自前で対応してください。
/* カスタムアニメーションを追加した場合は必ずこのブロックも書く */
@media (prefers-reduced-motion: reduce) {
.my-skeleton-animation {
animation: none;
}
}
フォーカス管理
Skeleton を表示しているコンポーネントがフォーカス可能な場合、読み込み中であることを伝えるアクセシブルな名前またはラベルを必ず含める必要があります。また、コンテンツが読み込まれた後は、キーボードフォーカスが中断されないよう注意してください。
たとえば、フォーカスが当たったボタンをクリックしたら Skeleton が表示され、データ取得後にリストが表示される場合、フォーカスをリスト先頭や適切な要素に移動させるとユーザーは続けて操作しやすくなります。
// 読み込み完了後にフォーカスを移動する例
useEffect(() => {
if (!isLoading && listRef.current) {
listRef.current.focus();
}
}, [isLoading]);
Skeleton 自体の可視性
Skeleton の個々の <SkeletonItem> は装飾的な要素です。スクリーンリーダーに一つ一つ読み上げられても混乱するだけですが、SkeletonItem 自体に aria-hidden は設定されていません。実際には、親の <Skeleton> コンポーネントが role="progressbar" + aria-busy: true を保持することで、AT(支援技術)がコンテナ単位でコンテンツを扱う構造になっています。重要なのは外側の Skeleton ラッパーへの aria-label 付与です。
アクセシビリティの要件を押さえたところで、次はコンテンツの説明をどう設計するかをまとめます。
コンテンツの説明
Skeleton の aria-label に何を書くか、読み込み完了後のアナウンスをどう設計するか。この「コンテンツの説明設計」はコーディングと同じくらい重要です。
ラベルの書き方
| ❌ 避けるべき例 | ✅ 推奨例 |
|---|---|
Loading... |
ユーザープロフィールを読み込み中 |
Please wait |
商品一覧を読み込み中(20件) |
読み込み中 |
レポートデータを取得中。しばらくお待ちください |
| ラベルなし | 必ず付ける |
ラベルは何のコンテンツが来るのかをユーザーに伝えるものです。具体的であればあるほど、ユーザーは「自分が操作しようとしていた情報が今準備されている」という文脈を持てます。
読み込み完了後のアナウンス
スクリーンリーダーのユーザーは、Skeleton が消えて実コンテンツが表示されたことを、視覚的な変化では検知できません。aria-live="polite" のリージョンで明示的に通知することで、「操作可能になった」ことを伝えましょう。
// 読み込み完了時のアナウンス例
announceRef.current.textContent = "プロフィール情報の読み込みが完了しました。";
announceRef.current.textContent = "15 件の検索結果が表示されました。";
announceRef.current.textContent = "ダッシュボードのデータが最新の状態に更新されました。";
視覚とセマンティクスの一致
Skeleton の形状は、実際のコンテンツの視覚的な配置と一致させることが大切です。アバターが丸なら丸い SkeletonItem を、テキストが 2 行なら 2 本の細い矩形を使う。この一致が崩れると、コンテンツが表示されたときのレイアウトシフトが生まれ、ユーザーが「どこを見ればいいか」を再度探す負担が発生します。
コンテンツの説明設計を整理したところで、次は Fluent UI Blazor 5 の実装と比較します。
Fluent UI Blazor 5 との比較
Fluent UI Blazor 5 の FluentSkeleton は、React 版と目的は同じですが、API 設計が大きく異なります。Blazor 版はコンポーネント単体でレイアウトを完結させるパターンと、CSS クラスを既存コンポーネントに適用するパターンの 2 通りを提供しています。
FluentSkeleton のパラメータ一覧
| パラメータ名 | 型 | デフォルト | 説明 |
|---|---|---|---|
ChildContent |
RenderFragment<FluentSkeleton>? |
— | カスタムレイアウトを ChildContent で定義する |
Circular |
bool |
false |
true にすると円形表示 |
Height |
string? |
48px |
Skeleton の高さ |
Pattern |
SkeletonPattern? |
— | 定義済みパターン(Icon / IconTitle / IconTitleContent) |
Shimmer |
bool |
true |
シマー(波紋)アニメーションの有効/無効 |
Width |
string? |
100% |
Skeleton の幅 |
FluentSkeleton のメソッド
ChildContent 内で context(型: FluentSkeleton)経由で呼び出せるヘルパーメソッドがあります。
| メソッド | 説明 |
|---|---|
DrawCircle(radius: string) |
指定半径の円形プレースホルダーを描画 |
DrawRectangle(width, height: string) |
指定サイズの矩形プレースホルダーを描画 |
LoadingClass(when/condition, size, circular) |
既存コンポーネントにスケルトンスタイルを適用するクラスを返す(静的メソッド) |
React vs Blazor の基本使い方の違い
React(@fluentui/react-components):
import { Skeleton, SkeletonItem } from "@fluentui/react-components";
// React は Skeleton + SkeletonItem を組み合わせてレイアウトを構築する
<Skeleton aria-label="プロフィール情報を読み込み中" animation="wave">
<div style={{ display: "flex", gap: "12px", alignItems: "center" }}>
<SkeletonItem shape="circle" size={40} />
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
<SkeletonItem style={{ width: "160px", height: "16px" }} />
<SkeletonItem style={{ width: "100px", height: "12px" }} />
</div>
</div>
</Skeleton>
Blazor(FluentSkeleton)基本例:
@* 矩形と円形の基本 *@
<FluentSkeleton Shimmer="true" Circular="false" Width="200px" Height="50px" />
<FluentSkeleton Shimmer="true" Circular="true" Width="48px" Height="48px" />
Blazor — 定義済みパターンの使用:
@* Pattern を使うと典型的なレイアウトを一行で表現できる *@
<FluentSkeleton Pattern="SkeletonPattern.IconTitleContent" Width="300px" Height="150px" />
SkeletonPattern には Icon、IconTitle、IconTitleContent が用意されており、よくあるプロフィールカード的なレイアウトをすぐに作れます。
Blazor — ChildContent でカスタムレイアウト:
<FluentSkeleton Width="300px" Height="100px">
<FluentStack Orientation="Orientation.Vertical" HorizontalAlignment="HorizontalAlignment.Center">
<FluentText Weight="TextWeight.Bold">読み込み中...</FluentText>
<FluentStack>
@context.DrawCircle(radius: "32px")
@context.DrawRectangle(width: "100%", height: "32px")
@context.DrawCircle(radius: "32px")
</FluentStack>
</FluentStack>
</FluentSkeleton>
Blazor — CSS クラスで既存コンポーネントにスケルトンスタイルを適用:
既存コンポーネントを Skeleton に「入れ替える」のではなく、既存コンポーネントにスケルトン風のスタイルを重ねるアプローチも提供されています。
<FluentStack Orientation="Orientation.Vertical" VerticalGap="12px">
@* div に直接 CSS クラスを適用する例 *@
<div class="@(IsLoading ? "fluent-skeleton-4" : null)"
style="width: 200px; height: 20px;">
@(IsLoading ? "" : "HTML の div コンテンツ")
</div>
@* FluentLabel に静的メソッドでクラスを適用する例 *@
<FluentLabel Class="@FluentSkeleton.LoadingClass(when: () => IsLoading, size: 4)"
Style="width: 200px; height: 20px;">
@(IsLoading ? "" : "FluentLabel のコンテンツ")
</FluentLabel>
<FluentSwitch @bind-Value="IsLoading" Label="読み込み中トグル" />
</FluentStack>
@code {
bool IsLoading = true;
}
CSS クラス名のルールは fluent-skeleton-{1-8}(矩形)と fluent-skeleton-circular-{1-8}(円形)で、数値は 4px の倍数のサイズを意味します(fluent-skeleton-4 = 16px)。
React vs Blazor の API 比較まとめ
| 項目 | React | Blazor |
|---|---|---|
| ラッパー | <Skeleton> |
<FluentSkeleton> |
| 個別アイテム | <SkeletonItem> |
DrawCircle / DrawRectangle メソッド or CSS クラス |
| 形状指定 |
shape="circle" / shape="rectangle"
|
Circular={true/false} |
| アニメーション種別 |
animation="wave" / "pulse"
|
Shimmer={true/false} |
| 定義済みパターン | なし |
SkeletonPattern.Icon 等 |
| サイズ指定 |
size={40}(数値) or style={{ width, height }}
|
Width="..." / Height="..." (文字列) |
| 既存コンポーネントへの適用 | CSS カスタムで対応 |
LoadingClass() 静的メソッド / fluent-skeleton-{n} クラス |
aria-label |
<Skeleton aria-label="..."> |
親要素に aria-label を付与する |
Blazor の FluentSkeleton は React 版と比べて パターン機能が豊富です。React 版は SkeletonItem を自分で組み合わせる柔軟性がある一方、Blazor 版は SkeletonPattern でよくある形をすぐに使えます。どちらのアプローチを選ぶかはチームやプロジェクトの方針によります。
FluentProgressBar と FluentSpinner(Blazor 5)
参考として、Blazor 5 の ProgressBar と Spinner も簡単に整理します。
FluentProgressBar:
@* 確定(Value あり)*@
<FluentProgressBar Value="60" Min="0" Max="100" />
@* 不確定(Value なし)*@
<FluentProgressBar />
@* 状態付き *@
<FluentProgressBar Value="100" State="ProgressState.Success" />
FluentSpinner:
@* サイズ指定 *@
<FluentSpinner Size="SpinnerSize.Large" />
@* 暗背景対応 *@
<FluentSpinner AppearanceInverted="true" />
Blazor 版も React 版と同様、Skeleton はコンテンツのプレースホルダー、ProgressBar は進捗表示、Spinner は短時間の処理中表示という使い分けが基本です。
まとめ 📝
Skeleton コンポーネントの要点をまとめます。
何を使うか
- Skeleton: コンテンツの形状が予測でき、1〜5 秒程度の読み込みがある場合
- ProgressBar: 処理の進捗を数値で伝えたい場合(確定・不確定どちらも)
- Spinner: ボタン操作後の即時フィードバック、ページ切り替えなどの短時間処理
実装のポイント
-
aria-busy="true"を外側コンテナに付け、読み込み完了後にfalseまたは削除する -
aria-labelはコンテンツの内容を具体的に説明する(「Loading...」ではなく「注文履歴を読み込み中」) - 読み込み完了後は
aria-live="polite"のリージョンで完了をアナウンスする -
prefers-reduced-motion対応を忘れない(Fluent UI 2 は自動対応済み)
React vs Blazor
- React は
Skeleton+SkeletonItemでレイアウトを自由に組む柔軟性 - Blazor は
SkeletonPatternでよくある形をすぐ使えるパターン機能と、LoadingClass()で既存コンポーネントへの適用が容易
Skeleton はコーディング量が少ないコンポーネントですが、アクセシビリティ設計の密度は決して低くありません。aria-busy・aria-label・aria-live の 3 点をしっかり設計することで、視覚に頼れないユーザーにとっても「今何が起きているか」が伝わる UI になります。
シリーズの次回もお楽しみに 🌟