Overlay (Dialog / Popover)
willink DS の overlay は Dialog と Popover の 2 系統で、どちらも Radix Primitives の上に構築されています。フォーカス管理・Esc での dismiss・ARIA ロールは Radix が担い、DS は trigger に紐づくスタイルとモーション(motion のロールトークン)を上乗せします。すべて @willink-labs/react から import します。
Dialog vs Popover の使い分け
どちらも trigger から開く浮遊パネルですが、モーダル性が決定的に異なります。
| コンポーネント | 性質 | 用途 |
|---|---|---|
Dialog | モーダル。フォーカストラップで背面を遮断します。 | 確認・破壊的操作の警告・フォーム送信。 |
Popover | 非モーダル。trigger にアンカーされた浮遊パネルで、背面は操作可能なままです。 | 設定・ピッカー・コンボボックス。 |
読み取り専用の hover / focus 補足には Tooltip を使い、操作可能な要素(ボタン・リンク・入力)は入れないでください。Tooltip はインタラクティブなコンテンツの容れ物ではありません。
Dialog
モーダルダイアログです。DialogTrigger に asChild を付けて任意の要素(通常は Button)を trigger にし、DialogHeader / DialogTitle / DialogDescription / DialogFooter で構造化します。
import {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
DialogClose,
Button,
} from '@willink-labs/react';
<Dialog>
<DialogTrigger asChild>
<Button>開く</Button>
</DialogTrigger>
<DialogContent size="md">
<DialogHeader>
<DialogTitle>確認</DialogTitle>
<DialogDescription>この操作は取り消せません。</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">キャンセル</Button>
</DialogClose>
<Button>実行</Button>
</DialogFooter>
</DialogContent>
</Dialog>size
DialogContent は cva の size prop で最大幅を選びます。 既定は md です。
| size | 最大幅 | 用途 |
|---|---|---|
sm | max-w-sm | 確認ダイアログ。 |
md(既定) | max-w-md | 標準的なフォーム。 |
lg | max-w-lg | 項目数の多いフォーム。 |
xl | max-w-2xl | リッチなコンテンツ。 |
full | max-w-[95vw] | ほぼ全画面のワークスペース。 |
その他の props
DialogContent は次の prop も受け取ります(型は DialogContentProps)。
closeButton?: ReactNode | false— 既定の✕を差し替える、またはfalseで非表示にします。既定のボタンにはaria-label="Close"が付与済みです。portal?: boolean—DialogPortalで body 直下に描画するか(既定true)。overlay?: boolean— 背面のDialogOverlayを描画するか(既定true)。
{/* 既定の ✕ をカスタム要素に差し替える */}
<DialogContent closeButton={<XIcon aria-hidden />}>…</DialogContent>
{/* ✕ を非表示にする(自前の閉じる導線を用意する場合のみ) */}
<DialogContent closeButton={false}>…</DialogContent>アクセシビリティ(Dialog)
Radix が フォーカストラップと Esc での dismiss を自動で提供し、role="dialog" を管理します。DialogTitle がアクセシブルネームになるため、必ず含めてください。開閉アニメーションは data-[state=open]:animate-dialog-in / data-[state=closed]:animate-dialog-out で、motion-reduce:animate-none により reduced-motion 環境では停止します。
Popover
非モーダルの浮遊パネルです。PopoverTrigger にアンカーされ、背面は操作可能なまま残ります。cva の variant は持たず、配置のみを prop で調整します。
import {
Popover,
PopoverTrigger,
PopoverContent,
Button,
} from '@willink-labs/react';
<Popover>
<PopoverTrigger asChild>
<Button>設定</Button>
</PopoverTrigger>
{/* aria-label は必須(下記 a11y を参照) */}
<PopoverContent aria-label="設定" align="end" sideOffset={8}>
…
</PopoverContent>
</Popover>PopoverContent の主な props:
align?: "start" | "center" | "end"— trigger に対する整列(既定center)。sideOffset?: number— trigger との距離(既定4)。
aria-label は必須
重要: PopoverContent は role="dialog" として描画されるため、アクセシブルネームが必須です。aria-label を渡すか、内部の見出しを指す aria-labelledby を渡してください。これを省くとスクリーンリーダーは「unnamed dialog」と読み上げ、axe の aria-dialog-name ルールが失敗します。
<PopoverContent aria-labelledby="filters-heading">
<h3 id="filters-heading">絞り込み</h3>
…
</PopoverContent>Radix が フォーカス管理と、Esc / 外側クリックでの dismiss を提供します。フォーカス可能な要素を含めない単純な情報表示なら、 モーダルではなく Tooltip の採用を検討してください。
Storybook で挙動を確認できます — Dialog / Popover のストーリー。次は アクセシビリティ で overlay を含む a11y 契約全体を確認する。