Dialog、Sheet、Popover:オーバーレイ系コンポーネントのアクセシビリティとフォーカス管理
お客様からこんなメールが届きました。「御社サイトのオーバーレイを開いた後、Tab キーを押すとフォーカスが背景ページへ飛んでしまい、キーボードユーザーがまったく操作できません」。
このメール、なかなか気まずいものでした——というのも、このオーバーレイは先週書いたばかりだったからです。
コードを開いてみると、問題は一目瞭然でした。Dialog を開いてもフォーカスが背景のボタンに残ったままで、ユーザーが Tab キーを押せば当然そのまま背景ページへ抜けていきます。スクリーンリーダーのユーザーはもっと深刻です——フォーカスが移っておらず ARIA 属性も設定されていないため、オーバーレイが開いたことすら分からないのです。
この記事では、Dialog、Sheet、Popover という 3 種類のオーバーレイコンポーネントのアクセシビリティとフォーカス管理について話します。踏んできた落とし穴を、できれば避けてもらえたらと思います。
まず整理:3 種類のコンポーネントの核心的な違い
正直なところ、多くの人は——以前の私も含めて——この 3 つのコンポーネントの違いがかなり曖昧でした。どれもオーバーレイだろう、だいたい同じ、と思いがちです。ですが実際には、その核心的な違いがアクセシビリティの扱い方を決めています。
Dialog(モーダルダイアログ)
Dialog は背景操作を完全にブロックするモーダルオーバーレイです。
たとえば「注文を削除」ボタンを押すと、確認ダイアログが表示されます。このとき背景ページはオーバーレイで覆われ、背景のどの要素もクリックできません——これが Dialog の核心的な特徴、つまり ユーザーに目の前のタスクを強制的に処理させる という性質です。
利用シーン:
- 重要な案内(削除確認、操作警告)
- フォーム入力(ログインフォーム、登録フォーム)
- ユーザーの即時応答が必要な操作
アクセシビリティの要:aria-modal="true" を必ず設定し、フォーカストラップを必ず実装すること。
Sheet(サイドドロワー)
Sheet は画面の端からスライドして出てくるドロワー型のパネルです。本質的には Dialog と同じで、どちらもモーダルオーバーレイ——背景操作をブロックし、フォーカストラップが必要です。唯一の違いは視覚的な位置です。Sheet は側面からスライドし、Dialog は中央に表示されます。
利用シーン:
- ナビゲーションメニュー(モバイルのサイドバー)
- 設定パネル(環境設定、テーマ切り替え)
- 詳細表示(商品詳細、記事プレビュー)
正直、Sheet という言葉は私も最初はかなり馴染みがありませんでした。あとで分かったのですが、これは Drawer の別名なのです——Drawer と呼ぶ UI ライブラリもあれば、Sheet と呼ぶものもあり、Radix UI と shadcn/ui では Sheet を使っています。
アクセシビリティの要:Dialog と同じく、aria-modal="true"、フォーカストラップ、Esc で閉じる。
Popover(ポップオーバー)
Popover は背景操作をブロックしない非モーダルオーバーレイです。
この点がとても重要です——Popover を開いてもユーザーは背景の要素をクリックでき、フォーカスが Popover 内に強制的に閉じ込められることはありません。
たとえば「その他の操作」ボタンを押すと、「編集」「複製」「削除」を含む小さなパネルが出てきます。これが Popover です。背景の別のボタンをクリックでき、すると Popover は自動的に閉じます。
利用シーン:
- ドロップダウンメニュー(操作メニュー、選択肢リスト)
- ツールチップ(リッチテキストのヒント、使い方説明)
- クイック操作(編集、複製、削除)
アクセシビリティの要:aria-modal="false"(または省略)、フォーカスは強制トラップしない、外側のクリックで閉じる。
1 枚の表で違いを把握
正直、この表は自分で書いているときも見比べながらでした——以前はいくつかの概念が本当に曖昧だったのです。
| 特性 | Dialog | Sheet | Popover |
|---|---|---|---|
| 背景のブロック | ✅ 必須 | ✅ 必須 | ❌ しない |
| フォーカストラップ | 必須 | 必須 | 任意(強制しない方が推奨) |
| Esc で閉じる | 必須 | 必須 | 推奨 |
| 外側クリックで閉じる | 任意 | 任意 | デフォルト動作 |
| ARIA ロール | dialog | dialog | popover |
aria-modal | "true" | "true" | "false" または省略 |
| 視覚的な位置 | 中央 | 側面スライド | トリガー要素を基準に配置 |
一言でまとめると、Dialog と Sheet はモーダルオーバーレイ、Popover は非モーダルオーバーレイ です。モーダルオーバーレイはフォーカストラップを必ず実装し、非モーダルオーバーレイは強制しなくてもかまいません。
WCAG アクセシビリティ標準の詳解
正直、WCAG 標準は読み始めはかなり退屈です。英語の専門用語が並び、まるで法律の条文のように感じます。ですが実際のプロジェクトで問題にぶつかると、これらの標準が本当に役立つと分かります——検査をしのぐためではなく、ユーザーが正常に操作できるようにするためです。
ARIA 属性の必須項目
オーバーレイコンポーネントの ARIA 属性のうち、3 つは必須です。
1. role="dialog"
この属性は支援技術(スクリーンリーダー)に「これはダイアログです」と伝えます。
<div role="dialog">
<!-- オーバーレイの内容 -->
</div>
2. aria-labelledby
この属性はオーバーレイのタイトル要素を関連付けます。スクリーンリーダーはオーバーレイを開くと、まずタイトルを読み上げます。
<div role="dialog" aria-labelledby="dialog-title">
<h2 id="dialog-title">削除の確認</h2>
<p>この操作は取り消せません。</p>
</div>
3. aria-modal="true"(モーダルオーバーレイのみ)
この属性はスクリーンリーダーに「背景の内容にはアクセスできません」と伝えます。
<div role="dialog" aria-modal="true">
<!-- モーダルオーバーレイの内容 -->
</div>
正直、私は以前よく aria-labelledby を忘れていました。あとでスクリーンリーダーでテストして初めて気づいたのです——この属性がないと、ユーザーがオーバーレイを開いても聞こえてくるのは空白で、これが何なのか分からないのです。
キーボードナビゲーションの要件
WCAG はオーバーレイのキーボードナビゲーションに明確な要件を定めています。
Tab キー:オーバーレイ内でフォーカスを循環させる
ユーザーが Tab キーを押すと、フォーカスはオーバーレイ内の操作可能な要素の間を循環し、背景ページへ抜けてはいけません。
Shift+Tab:フォーカスを逆方向に循環させる
ユーザーが Shift+Tab を押すと、フォーカスは逆方向に循環します。
Esc キー:オーバーレイを閉じる
ユーザーが Esc キーを押すと、オーバーレイは閉じるべきです。これは必須です——Esc でオーバーレイを閉じる習慣のあるユーザーもおり、対応していなければ中に閉じ込められてしまいます。
Enter/Space:ボタンを実行する
この 2 つのキーはボタンやリンクを実行するのに使います。
正直、Tab 循環は以前に落とし穴を踏みました。オーバーレイを開いた後、フォーカスがオーバーレイ内に制限されておらず、ユーザーが Tab キーを押すとフォーカスが背景ページへ抜けてしまったのです——これが冒頭で触れたお客様の苦情の問題でした。
フォーカス管理の規範
フォーカス管理はオーバーレイのアクセシビリティで最も見落とされやすい部分です。WCAG の要件はとてもシンプルです。
オーバーレイを開いたとき:
フォーカスはオーバーレイ内の最初の操作可能な要素(多くは閉じるボタンか最初の入力欄)へ移すべきです。
オーバーレイを閉じたとき:
フォーカスはトリガー要素(オーバーレイを開いたボタン)へ戻すべきです。
正直、閉じた後にフォーカスを戻すというのは、以前はまったく意識していませんでした。あとでキーボードでテストして初めて気づいたのです——オーバーレイを閉じると、フォーカスがどこへ行ったか分からず、キーボードユーザーは探し直すはめになります。この体験は本当にひどいものでした。
特殊なケース:
オーバーレイに重要な案内(たとえば操作説明)がある場合は、フォーカスをまずコンテナ要素に置き、スクリーンリーダーに案内を先に読み上げさせてから、ユーザーに操作させるべきです。
やり方は、コンテナに tabindex="0" を付けることです。
<div role="dialog" aria-modal="true" tabindex="0">
<h2>操作説明</h2>
<p>以下の内容をよく読んでから操作してください...</p>
<button>確認</button>
</div>
こうすると、オーバーレイを開いたときにフォーカスがまずコンテナに置かれ、スクリーンリーダーが内容全体を先に読み上げてから、ユーザーが Tab でボタンへ移れます。
フォーカストラップの実装原理
正直、フォーカストラップは複雑そうに聞こえますが、原理は実はとてもシンプルです——Tab キーをオーバーレイ内で循環させるだけです。
フォーカストラップとは
フォーカストラップの定義は、ユーザーの Tab ナビゲーションを特定の領域内で循環させること です。
たとえば、オーバーレイを開いた後、ユーザーが Tab キーを押すとフォーカスが「閉じるボタン」から「確認ボタン」へ移り、さらに Tab キーを押すとフォーカスがまた「閉じるボタン」へ戻ります——これがフォーカストラップです。
必要性:ユーザーが背景の内容を誤操作するのを防ぐためです。フォーカスが背景ページへ抜けてしまうと、ユーザーが背景のボタンをうっかり押し、意図しない操作につながりかねません。
JavaScript での実装の考え方
フォーカストラップの中心となるロジックはとてもシンプルです。オーバーレイ内のすべての操作可能な要素を見つけ、Tab キーを監視し、最初と最後の要素の間で循環させます。
function trapFocus(modal) {
// 操作可能な要素をすべて取得
const focusableElements = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
// キーボードイベントを監視
modal.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
// Shift+Tab:最初の要素にいるときは最後へ移動
if (e.shiftKey && document.activeElement === firstElement) {
e.preventDefault();
lastElement.focus();
}
// Tab:最後の要素にいるときは最初へ移動
else if (!e.shiftKey && document.activeElement === lastElement) {
e.preventDefault();
firstElement.focus();
}
}
// Esc でオーバーレイを閉じる
if (e.key === 'Escape') {
closeModal();
}
});
}
正直、このコードは何度も書き直してやっと動きました。主な落とし穴は次の点です。
focusableElementsのセレクターは網羅的にすること。どれかの種類を漏らすとフォーカスが抜けていきますe.preventDefault()は必ず呼ぶこと。そうしないとブラウザのデフォルト動作でフォーカスが外へ移ってしまいます
focus-trap ライブラリの紹介
フォーカストラップを自分で書きたくなければ、既存のライブラリ——focus-trap-react を使えます。
import FocusTrap from 'focus-trap-react';
<FocusTrap>
<div className="modal">
<button>閉じる</button>
<button>確認</button>
</div>
</FocusTrap>
このライブラリは、フォーカス循環、Esc で閉じる、多層オーバーレイなどのケースを自動的に処理してくれます。
正直、私は今ではこのライブラリをほとんど使っていません——shadcn/ui が内部でフォーカス管理をすでに統合しているからです。Radix UI(shadcn/ui の土台)がフォーカストラップのロジックをすべて自動処理するため、追加のライブラリを導入する必要はありません。
shadcn/ui 実践:Dialog の実装
正直、shadcn/ui を使い始めてから、オーバーレイコンポーネントを自分で手書きすることはなくなりました。怠けているからではなく——手書きのオーバーレイはいつもアクセシビリティの問題を抱えるのに対し、shadcn/ui は Radix UI ベースで、アクセシビリティの細部をすべて自動処理してくれるからです。
インストールと基本的な使い方
npx shadcn@latest add dialog
インストールすると components/ui/dialog.tsx ファイルが自動生成されます。
完全なコード例
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
export function DeleteConfirmDialog() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">注文を削除</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>削除の確認</DialogTitle>
<DialogDescription>
この操作は取り消せません。本当にこの注文を削除しますか?
</DialogDescription>
</DialogHeader>
<div className="flex justify-end gap-2 mt-4">
<Button variant="outline">キャンセル</Button>
<Button variant="destructive">削除</Button>
</div>
</DialogContent>
</Dialog>
)
}
正直、このコードは簡単そうに見えますが、裏では Radix UI が多くの細部を自動処理しています。
- オーバーレイを開いたとき、フォーカスが最初のボタン(「キャンセル」)へ移る
- オーバーレイを閉じたとき、フォーカスが「注文を削除」ボタンへ戻る
- Tab キーがオーバーレイ内で循環する
- Esc キーでオーバーレイが閉じる
aria-labelledbyがDialogTitleと自動的に関連付けられるaria-describedbyがDialogDescriptionと自動的に関連付けられる
鍵となるアクセシビリティ特性
1. フォーカスの自動管理
Radix UI の Dialog は、開いたときにフォーカスがオーバーレイ内の最初の操作可能な要素へ自動的に移ります。閉じたときには、フォーカスがトリガー要素へ自動的に戻ります。
2. ARIA 属性の自動関連付け
DialogTitle は aria-labelledby と自動的に関連付けられ、DialogDescription は aria-describedby と自動的に関連付けられます。
<!-- Radix UI が生成する HTML -->
<div role="dialog" aria-modal="true" aria-labelledby="radix-:r1:" aria-describedby="radix-:r2:">
<h2 id="radix-:r1:">削除の確認</h2>
<p id="radix-:r2:">この操作は取り消せません...</p>
</div>
正直、こうした細部は手書きだと簡単に漏れます。shadcn/ui を使えばまったく心配いりません。
3. Esc キーで自動的に閉じる
Esc キーを押すとオーバーレイが自動的に閉じ、フォーカスがトリガー要素へ自動的に戻ります。
4. オーバーレイをクリックして閉じる
オーバーレイの外側(オーバーレイの外のグレーの背景)をクリックしてもオーバーレイを閉じられます。この動作は DialogContent の onInteractOutside 属性で防げます。
<DialogContent onInteractOutside={(e) => e.preventDefault()}>
<!-- 外側をクリックしてもオーバーレイは閉じない -->
</DialogContent>
shadcn/ui 実践:Sheet の実装
Sheet のアクセシビリティ特性は Dialog とまったく同じで、唯一の違いは視覚的な位置——Sheet は側面からスライドします。
インストールと基本的な使い方
npx shadcn@latest add sheet
完全なコード例
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet"
import { Button } from "@/components/ui/button"
export function NavigationSheet() {
return (
<Sheet>
<SheetTrigger asChild>
<Button variant="outline">メニューを開く</Button>
</SheetTrigger>
<SheetContent side="left">
<SheetHeader>
<SheetTitle>ナビゲーションメニュー</SheetTitle>
<SheetDescription>
アクセスしたいページを選んでください
</SheetDescription>
</SheetHeader>
<nav className="flex flex-col gap-4 mt-4">
<a href="/" className="hover:underline">ホーム</a>
<a href="/about" className="hover:underline">概要</a>
<a href="/contact" className="hover:underline">お問い合わせ</a>
</nav>
</SheetContent>
</Sheet>
)
}
Dialog との違い
正直、Sheet と Dialog のコードはほとんど同じで、違うのはコンポーネント名だけです。主な違いは次の点です。
1. 側面スライドのアニメーション
Sheet はデフォルトで右側からスライドし、side 属性で方向を制御できます。
<SheetContent side="left"> <!-- 左からスライド -->
<SheetContent side="right"> <!-- 右からスライド(デフォルト) -->
<SheetContent side="top"> <!-- 上からスライド -->
<SheetContent side="bottom"> <!-- 下からスライド -->
2. アクセシビリティ特性は同じ
Sheet のアクセシビリティ特性は Dialog とまったく同じです。
role="dialog"aria-modal="true"- フォーカストラップ、Esc で閉じる、フォーカスの復帰
正直、Sheet というコンポーネントは主にモバイルのナビゲーションメニューに使っています。側面スライドの視覚効果は、モバイルの操作習慣によりよく合います。
shadcn/ui 実践:Popover の実装
Popover は非モーダルオーバーレイで、Dialog や Sheet との核心的な違いは、背景操作をブロックしない ことです。
インストールと基本的な使い方
npx shadcn@latest add popover
完全なコード例
import {
Popover,
PopoverContent,
PopoverHeader,
PopoverTitle,
PopoverDescription,
PopoverTrigger,
} from "@/components/ui/popover"
import { Button } from "@/components/ui/button"
export function ActionPopover() {
return (
<Popover>
<PopoverTrigger asChild>
<Button variant="outline">その他の操作</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverHeader>
<PopoverTitle>クイック操作</PopoverTitle>
<PopoverDescription>
以下の操作を選んでください
</PopoverDescription>
</PopoverHeader>
<div className="flex flex-col gap-2 mt-2">
<Button size="sm">編集</Button>
<Button size="sm">複製</Button>
<Button size="sm" variant="destructive">削除</Button>
</div>
</PopoverContent>
</Popover>
)
}
鍵となる違い
正直、Popover のコードは Dialog や Sheet とよく似ていますが、裏の動作はまったく異なります。
1. 非モーダル
Popover を開いてもユーザーは背景の要素をクリックできます。フォーカスが Popover 内に強制的に閉じ込められることはありません。
2. フォーカスは強制しない
ユーザーが Tab キーを押すと、フォーカスは Popover から背景の要素へ移れます。この点が Dialog とまったく違います。
3. 外側のクリックで閉じる
Popover の外側のどの要素をクリックしても、Popover は自動的に閉じます。これはデフォルト動作で、onInteractOutside 属性で防げます。
<PopoverContent onInteractOutside={(e) => e.preventDefault()}>
<!-- 外側をクリックしても閉じない -->
</PopoverContent>
4. 柔軟な配置
Popover は align 属性で水平方向の揃え方を制御します。
<PopoverContent align="start"> <!-- 左揃え -->
<PopoverContent align="center"> <!-- 中央揃え(デフォルト) -->
<PopoverContent align="end"> <!-- 右揃え -->
正直、Popover は主に操作メニューに使っています——ボタンを押すとクイック操作の選択肢がいくつか出てくる形です。このようなシーンでは背景操作をブロックする必要がなく、Popover がちょうど合っています。
上級テクニックとよくある落とし穴
正直、オーバーレイコンポーネントの落とし穴はたくさん踏んできました。ここでは特によくあるものをいくつか話します。
フォーカス復帰の落とし穴:トリガー要素が削除された
シーン:オーバーレイを開いた後、トリガー要素(ボタン)が削除された。オーバーレイを閉じるとき、フォーカスの戻り先がない。
解決策:
- トリガー要素を削除せず、隠すだけにする
- または、フォーカスを戻す対象の要素を記録しておく
const [triggerElement, setTriggerElement] = useState<HTMLElement | null>(null);
// オーバーレイを開くときにトリガー要素を記録
const handleOpen = (e: React.MouseEvent<HTMLButtonElement>) => {
setTriggerElement(e.currentTarget);
setOpen(true);
};
// オーバーレイを閉じるときにフォーカスを戻す
const handleClose = () => {
setOpen(false);
triggerElement?.focus();
};
正直、この落とし穴は私も踏みました。ユーザーがレコードを 1 件削除した後、オーバーレイが閉じても、フォーカスがどこへ行ったか分からなかったのです。あとでフォーカスをリストの 1 つ前のレコードへ戻すようにして、ようやく解決しました。
スクリーンリーダーの落とし穴:オーバーレイの内容が読み上げられない
シーン:オーバーレイを開いた後、スクリーンリーダーが内容を読み上げず、ユーザーはオーバーレイの中身が分からない。
原因:
aria-labelledby属性が欠けている- フォーカスがオーバーレイ内へ移っていない
解決策:
DialogTitle と DialogDescription の両方を設定していることを確認してください。shadcn/ui が ARIA 属性を自動的に関連付けます。
<DialogContent>
<DialogHeader>
<DialogTitle>削除の確認</DialogTitle> <!-- 必須 -->
<DialogDescription>この操作は取り消せません</DialogDescription> <!-- 必須 -->
</DialogHeader>
</DialogContent>
正直、私は以前よく DialogDescription を漏らしていました。あとで NVDA(スクリーンリーダー)でテストして初めて気づいたのです——説明がないと、ユーザーはオーバーレイのタイトルだけ分かって、具体的な内容が分かりません。
多層オーバーレイの落とし穴:フォーカス管理が混乱する
シーン:オーバーレイ A がオーバーレイ B を開き、B を閉じた後にフォーカスがどこへ行ったか分からない。
解決策:
Radix UI の Dialog と Sheet は入れ子に対応しています。内側のオーバーレイを閉じると、フォーカスは内側のトリガー要素(外側のオーバーレイ内のボタンかもしれません)へ戻ります。
<Dialog>
<DialogTrigger>オーバーレイ A を開く</DialogTrigger>
<DialogContent>
<DialogTitle>オーバーレイ A</DialogTitle>
<!-- オーバーレイ A の中でオーバーレイ B を開く -->
<Dialog>
<DialogTrigger>オーバーレイ B を開く</DialogTrigger>
<DialogContent>
<DialogTitle>オーバーレイ B</DialogTitle>
</DialogContent>
</Dialog>
</DialogContent>
</Dialog>
正直、多層オーバーレイの状況はできるだけ避けています。どうしても必要なときは、Radix UI の入れ子サポートを使って、フォーカスを自動的に処理させます。
アニメーション遅延の落とし穴:フォーカスがオーバーレイ内にない
シーン:オーバーレイにアニメーション(たとえばフェードイン)があり、アニメーション中はフォーカスがオーバーレイ内にない。
原因:
アニメーションの開始時点ではオーバーレイがまだ完全に表示されておらず、フォーカス設定が失敗します。
解決策:
Radix UI はこの問題を自動的に処理します。アニメーションが完了してからフォーカスを設定します。
自分で実装する場合は、アニメーションの完了を待つ必要があります。
modal.addEventListener('animationend', () => {
const firstFocusable = modal.querySelector('button, [href], input');
firstFocusable?.focus();
});
正直、この落とし穴も踏みました。自分で書いたオーバーレイは、開いたときにフォーカスがまだ背景のボタンに残っていたのです。アニメーションが完了する前にフォーカスを設定しようとしたためでした。あとで animationend イベントのリスナーを追加して、ようやく解決しました。
まとめ
いろいろ話してきましたが、核心は実は次の 3 点です。
1. 3 種類のコンポーネントの核心的な違い
Dialog と Sheet はモーダルオーバーレイ——背景操作をブロックし、フォーカストラップが必須。
Popover は非モーダルオーバーレイ——背景操作をブロックせず、フォーカスは強制しない。
2. WCAG アクセシビリティの三大要件
ARIA 属性(role="dialog"、aria-labelledby、aria-modal="true")
キーボードナビゲーション(Tab 循環、Shift+Tab で逆方向、Esc で閉じる)
フォーカス管理(開いたときにフォーカス、閉じたときに復帰)
3. shadcn/ui が細部をすべて自動処理する
Radix UI がフォーカストラップ、ARIA 属性、キーボードナビゲーションを自動処理します。shadcn/ui を使えば、アクセシビリティの問題はほとんど心配いりません。
正直、これだけ多くのオーバーレイコンポーネントを書いてきて、今の私の原則はとてもシンプルです。本番環境では shadcn/ui を優先する。オーバーレイコンポーネントを手書きすると、アクセシビリティの問題が次々と出てくるのに対し、shadcn/ui は Radix UI ベースで、こうした細部をきちんと処理してくれます。
唯一注意すべきは、原理を理解することです。Radix UI が裏で何をしているかを知っていれば、問題にぶつかったときにすばやく切り分けられます。
参考資料
- WAI-ARIA dialog role - MDN
- Radix UI Accessibility
- WCAG 2.1 Quick Reference
- Mastering Accessible Modals
- focus-trap-react
FAQ
Dialog、Sheet、Popover の 3 つのコンポーネントは何が違うのですか?
オーバーレイコンポーネントが実装すべきアクセシビリティ要件は何ですか?
フォーカストラップとは何ですか?なぜモーダルオーバーレイには必須なのですか?
shadcn/ui の Dialog コンポーネントが自動処理するアクセシビリティの細部は何ですか?
• オーバーレイを開いたとき、フォーカスが最初の操作可能な要素へ自動的に移る
• オーバーレイを閉じたとき、フォーカスがトリガー要素へ自動的に戻る
• Tab キーがオーバーレイ内で循環する
• Esc キーでオーバーレイが自動的に閉じる
• aria-labelledby が DialogTitle と自動的に関連付けられる
• aria-describedby が DialogDescription と自動的に関連付けられる
オーバーレイを閉じた後、フォーカスはどこへ戻すべきですか?
オーバーレイにアニメーションがあるとき、フォーカス設定が失敗したらどうすればよいですか?
スクリーンリーダーにオーバーレイの内容を読み上げさせるにはどうすればよいですか?
6分で読めます · 公開日: 2026年3月29日 · 更新日: 2026年6月8日
Tailwind と shadcn/ui 実践ガイド
検索からこのページに来た場合は、前後の記事もあわせて読むと同じテーマの理解がかなり早く深まります。
前の記事
shadcn/ui コンポーネント組み合わせパターン:実践ベストプラクティス
shadcn/ui コンポーネント組み合わせのベストプラクティスを学ぶ。Dialog+Form、DataTable+DropdownMenu パターン、Context 状態管理、パフォーマンス最適化を実践例とともに解説
第 6 / 11 記事
次の記事
Tailwind パフォーマンス最適化:JIT・content設定・本番バンドルサイズ管理
Tailwind CSSのJITモードの仕組み、content設定のベストプラクティス、本番バンドルサイズを抑える4層最適化戦略を、実践事例とTailwind v4の新機能分析とともに詳しく解説します。
第 8 / 11 記事
関連記事
Tailwind v4 + Vite:5 分で完成する設定テンプレートとディレクトリ構成
Tailwind v4 + Vite:5 分で完成する設定テンプレートとディレクトリ構成
shadcn/ui のインストールとテーマカスタマイズ完全ガイド(CSS 変数つき)
shadcn/ui のインストールとテーマカスタマイズ完全ガイド(CSS 変数つき)
shadcn/ui で管理画面の骨組みを構築:Sidebar + Layout ベストプラクティス
コメント
GitHubアカウントでログインしてコメントできます