はじめに 🌟
Fluent UI 2 の Dialog は、単なる小さなウィンドウではありません。
今の作業をいったん止めてもらってでも、必要な判断や入力をしてもらうための補助面です。
この記事では、Fluent UI 2 の Dialog を入り口にして、Fluent UI Blazor 5 の Dialog とどう違うのかを整理します。
特に、使い方の違い、モーダル / 非モーダルの使い分け、閉じるボタンの配置、アクセシビリティ、コンテンツの書き方を重点的に見ます。
参照した公式情報はこちらです。
Fluent UI 2 とは
Fluent UI 2 は、Microsoft の Fluent 2 デザインに沿った UI の考え方とコンポーネント群です。
見た目をそろえるだけではなく、情報の優先順位、操作の流れ、アクセシビリティまで含めて設計されています。
つまり Fluent UI 2 は、「きれいな部品集」ではありません。
利用者が迷わず操作できるように、何を見せるか、何を止めるか、どう行動してもらうかを決めるための土台です。
Dialog もその一部で、通知のように流してよい情報ではなく、今のタスクに関係する判断を促すときに使います。
この前提を押さえると、React 版と Blazor 版の違いも読みやすくなります。
今回のゴール ✅
- Fluent UI 2 の Dialog が何を担うか分かる
- Fluent UI 2 と Fluent UI Blazor 5 の使い方の違いが分かる
- modal / non-modal / alert の使い分けが分かる
- 閉じるボタン、スクロール、レイアウトの考え方が分かる
- アクセシビリティとコンテンツ説明の要点を押さえられる
Fluent UI 2 と Fluent UI Blazor 5 の使い方の違い
まず結論から言うと、React の Fluent UI 2 はDialog という体験を部品で組む感覚で、Fluent UI Blazor 5 はDialog の枠をサービス経由で開く感覚です。
どちらも Fluent の見た目を持ちますが、API の入口と組み立て方がかなり違います。
| 観点 | Fluent UI 2 (React) | Fluent UI Blazor 5 |
|---|---|---|
| 🧭 入口 |
Dialog / DialogSurface / DialogBody / DialogTrigger などを組み合わせる |
IDialogService.ShowDialogAsync<T>() で開くことが多い |
| 🧱 状態 | コンポーネント側で open state を持つ |
DialogInstance と DialogResult でやり取りする |
| 🪟 形の作り方 | JSX の構造そのものが Dialog の構造になる |
FluentDialogBody と FluentDialogInstance を中心に組む |
| 🔒 モーダル |
modal / non-modal / alert を選べる |
Modal = true/false で切り替える。既定は true
|
| ✖ 閉じ方 |
Esc、オーバーレイ、footer の action など |
footer ボタン、CancelAsync、CloseAsync など |
| 📐 レイアウト |
DialogSurface の中で sections を組む |
FluentDialogBody で Header / Body / Footer をまとめる |
| ♿ アクセシビリティ | role や focus の考え方を Dialog 側で担う | 開いたコンポーネント側でフォーカスと終了導線を整える |
React では、利用者にどう見えるかを先に考えて、そこに必要な部品を並べます。
Blazor では、まず「どのコンポーネントを Dialog として開くか」を決めて、そこに内容とボタンを載せます。
non-modal は背景を見ながら使えますが、だからといって外側クリックで雑に閉じる前提ではありません。
補助的に使うほど、閉じる導線は明示しておいたほうが安全です。
React のイメージ
import * as React from "react";
import {
Button,
Dialog,
DialogActions,
DialogBody,
DialogContent,
DialogSurface,
DialogTitle,
} from "@fluentui/react-components";
export function DiscardDraftDialog() {
const [open, setOpen] = React.useState(false);
return (
<>
<Button onClick={() => setOpen(true)}>Open dialog</Button>
<Dialog open={open} onOpenChange={(_, data) => setOpen(data.open)}>
<DialogSurface>
<DialogBody>
<DialogTitle>Discard draft?</DialogTitle>
<DialogContent>
The last saved draft will be moved to Deleted items.
</DialogContent>
<DialogActions>
<Button appearance="primary">Discard</Button>
<Button appearance="secondary">Cancel</Button>
</DialogActions>
</DialogBody>
</DialogSurface>
</Dialog>
</>
);
}
React 版は、Dialog の構造がそのまま JSX に表れます。
このため、タイトル、本文、ボタンの意味をコード上でも追いやすいです。
Blazor のイメージ
@inject IDialogService DialogService
<FluentButton OnClick="OpenDialogAsync">Open dialog</FluentButton>
@code {
private async Task OpenDialogAsync()
{
await DialogService.ShowDialogAsync<DiscardDraftDialog>(options =>
{
options.Modal = true;
options.Header.Title = "Discard draft?";
});
}
}
@inherits FluentDialogInstance
<FluentDialogBody FixedHeaderFooter="true">
<p>The last saved draft will be moved to Deleted items.</p>
</FluentDialogBody>
@code {
protected override Task OnActionClickedAsync(bool primary)
=> primary
? DialogInstance.CloseAsync("Discard")
: DialogInstance.CancelAsync();
}
Blazor 版は、ShowDialogAsync<T>() で開く流れが分かりやすいです。
Dialog の見た目を作る場所と、Dialog を呼び出す場所が分かれるので、再利用しやすいのも利点です。
Dialog の種類と使い分け 🧭
Fluent UI 2 では、Dialog を大きく 3 つで考えると整理しやすいです。
Blazor 5 でも、基本の考え方は同じです。
| 種類 | 使いどころ | 代表例 |
|---|---|---|
| 🟦 Modal | 背景の操作を止めて、まず判断してもらいたいとき | サインイン、削除確認、保存前の確認 |
| 🟨 Non-modal | 背景を見ながら補助的に使いたいとき | Find and replace、補助入力 |
| 🟥 Alert | 失う可能性がある、または強い決断が必要なとき | 未保存変更、破壊的操作の確認 |
React の公式ガイドでは、何かを更新しただけで利用者の操作が不要なら toast を、現在の作業に影響しない補足なら popover を勧めています。
Dialog はあくまで「止める価値があるとき」に使うのが基本です。
Blazor でも、Modal = false にすれば背景操作を許す補助的な Dialog にできます。
一方で、既定は Modal = true なので、何も指定しないと modal として振る舞います。
閉じるボタンの配置 ✖
閉じる導線は、見た目の好みではなく責務の重複を避ける観点で決めます。
特に modal では、footer に Cancel があるのに右上の × まで追加すると、選択肢が増えすぎます。
| 種類 | 閉じるボタン | 考え方 |
|---|---|---|
| 🟦 Modal | 右上の × は必須ではない | footer の Cancel で閉じられるなら、× はなくてもよい |
| 🟨 Non-modal | 右上の × が基本 | 背景と並行して使うので、すぐ閉じられる導線が自然 |
| 🟥 Alert | footer のボタンで閉じる | 選択を明示させるため、× だけで逃げられない方がよい |
React の公式ガイドでも、modal に Close button を置くのは Cancel がない場合 の補助として案内されています。
Blazor でも同じで、CloseAction や footer のアクションを増やしすぎるより、まずは必要最小限に絞るほうが安全です。
決定を迫るダイアログかどうか
Dialog を使うべきか迷ったら、まず「これは決定を迫るのか」を考えます。
必要なのは、単なる通知ではなく、今の作業を止める理由があるかです。
- ✅ 使う: 削除、未保存の破棄、重要な設定の確定
- ✅ 使う: 入力が終わるまで先に進めないサインインや初期設定
- ❌ 使わない: ただの進捗報告
- ❌ 使わない: 追加の背景説明だけ
この判断があいまいなら、toast や popover の方が自然です。
Dialog は強い介入なので、使うなら理由が必要です。
破壊的操作の確認や未保存内容の破棄のような場面では、Dialog を軽く扱わないほうがよいです。
「閉じられれば何でもよい」ではなく、選択の意味が伝わることを優先します。
スクロール
Dialog の中身が長くなると、利用者は「何を見ればよいか」を見失いやすくなります。
そのため、長い内容は body だけをスクロールさせ、header と footer は見える位置に残すのが基本です。
React の公式ガイドでは、header と footer はスクロールしても固定される前提です。
Blazor でも <FluentDialogBody FixedHeaderFooter="true"> を使うと、長いフォームや一覧でも Header / Footer を固定しやすくなります。
ポイントは、長い内容を無理に 1 枚に詰め込まないことです。
フォームが長すぎるなら、別画面に分けるか、段階を分けたほうが安全です。
レイアウト
Dialog は、レイアウトとコンテンツの対応をそのまま Header / Body / Footer でそろえると整理しやすいです。
対応は Header = タイトル、Body = 本文、Footer = ボタン です。
- Header
- Body
- Footer
それぞれの役割を、必須と任意で分けると次のようになります。
| 項目 | 必須 | オプション | 補足 |
|---|---|---|---|
| 🧭 Header | タイトル | 閉じるボタン、追加アクション | タイトルは必須。閉じるボタンは Dialog の種類で扱いが変わる |
| 📝 Body | 本文がある場合の内容 | 省略可 | ない Dialog もある。ある場合は判断材料や入力を置く |
| 🔘 Footer | 行動ボタン | 省略可 | ある場合は最大 3 個程度に抑える |
Header にはタイトルが必須で、Body と Footer は必要に応じて置きます。
React なら DialogTitle / DialogContent / DialogActions、Blazor なら TitleTemplate / ChildContent / ActionsTemplate の考え方です。
React でも Blazor でも、レイアウトの主役はHeader、Body、Footer です。
見た目を整えることより、どの情報がどこにあるかを一目で分かるようにすることが大切です。
Footer のボタンは、通常 3 個以内に抑えるのがよいです。
候補が増えすぎると、利用者は「どれを押せばよいか」で迷います。
Blazor 側では、options.Header.Title、options.Header.CloseAction、options.Footer などで見出しやボタンを調整できます。
ただし、調整できるからといって増やしすぎないことが大切です。
アクセシビリティ ♿
Dialog のアクセシビリティで重要なのは、見た目よりフォーカスです。
開いたら最初の操作対象にフォーカスが移り、閉じたら呼び出し元に戻る必要があります。
ネストはしない
Dialog の中からさらに Dialog を開くのは避けます。
モーダルが重なるほど、元の文脈が見えにくくなり、キーボード操作や拡大表示でも扱いにくくなります。
React の公式ガイドでも、Dialog はネストしない前提です。
Blazor 側でも、FluentDialog の上に別の FluentDialog を重ねる設計は避けるべきです。
フォーカス
- 開いたら最初のインタラクティブ要素へフォーカスを移す
- modal / alert はフォーカスを閉じ込める
- 少なくとも 1 つは、閉じるためのインタラクティブ要素を持たせる
- 閉じたら起点の要素へフォーカスを戻す
Blazor では、PreventDismissOnEscape で ESC の扱いを調整できます。
ただし、ESC を止めるなら、別の分かりやすい閉じ方を必ず用意したほうがよいです。
コンテンツの説明 ✍️
Dialog のコンテンツは、レイアウト名と合わせて Header / Body / Footer で考えます。
Dialog は、説明文をたくさん詰める場所ではありません。
Header で主メッセージを伝え、Body で補足し、Footer で答えを返すのが基本です。
タイトル
- 目的を一文で言い切る
- 短く、具体的にする
- 英語 UI なら sentence style capitalization にする
- 末尾の句点は、質問文でない限り付けない
「Error」のような曖昧なタイトルより、「Your session ended」のように何が起きたかが分かる文がよいです。
日本語でも同じで、何を判断してほしいのかがすぐ分かる表現にします。
本文
Body は Header の繰り返しを書く場所ではありません。
Header で主メッセージを伝えたら、Body では追加情報、前提条件、結果への影響などを補います。
- タイトルの言い換えを繰り返さない
- 補足情報や手順を入れる
- 重要な結果や不利益があるなら先に書く
本文は「長くてもよい説明」ではなく、判断に必要な説明です。
必要なら 1 行目に影響、2 行目に補足、3 行目に詳細という順で置くと読みやすくなります。
ボタン
- タイトルに対する返答として書く
- 1〜2 語で短くする
- 何をするボタンか分かる語を使う
たとえば Delete this file? なら、Delete と Cancel が自然です。
Proceed のような曖昧なラベルより、利用者が結果を想像できるラベルのほうがよいです。
タイトル・本文・ボタンの役割分担
| 項目 | 役割 | 書き方のコツ |
|---|---|---|
| 🧭 タイトル | 何が起きるかを伝える | 具体的な一文にする |
| 📝 本文 | 追加情報を補う | タイトルを繰り返さない |
| 🔘 ボタン | 行動の返答を示す | 1〜2 語に絞る |
タイトル、本文、ボタンを分けて考えると、Dialog は一気に読みやすくなります。
ここを崩さないことが、アクセシビリティにも直結します。
まとめ
Fluent UI 2 の Dialog は、情報を見せるための枠ではなく、行動を促すための補助面です。
React では部品を組んで Dialog を作り、Blazor ではサービスと Dialog コンポーネントで開く、という違いがあります。
迷ったら、次の順で考えると整理しやすいです。
- これは本当に Dialog が必要か
- modal / non-modal / alert のどれか
- 閉じる導線は重複していないか
- 長い内容を詰め込みすぎていないか
- タイトル、本文、ボタンの役割が分かれているか
- フォーカスとネストの問題はないか
Dialog は小さく見えて、実は情報設計の差がそのまま出ます。
だからこそ、見た目だけでなく、どう使うかまで丁寧に見る価値があります。




