Disclosure
コンテンツセクションの表示/非表示を制御するボタン。
デモ
基本的なディスクロージャー
クリックでコンテンツの表示/非表示を切り替えるシンプルなディスクロージャー。
初期状態で展開
defaultExpanded プロパティを使用すると、初期レンダリング時にコンテンツを表示できます。
This content is visible when the page loads because defaultExpanded is set to
true.
無効化状態
disabled プロパティを使用すると、ディスクロージャーの操作を無効化できます。
Native HTML
ネイティブ HTML を優先
このカスタムコンポーネントを使用する前に、ネイティブの <details> と <summary> 要素の使用を検討してください。 ネイティブ要素は組み込みのアクセシビリティを提供し、JavaScript なしで動作し、ARIA 属性を必要としません。
<details>
<summary>Show details</summary>
<p>Hidden content here...</p>
</details> カスタム実装は、滑らかな高さアニメーション、外部状態制御、またはネイティブ要素では提供できないスタイリングが必要な場合にのみ使用してください。
| ユースケース | ネイティブ HTML | カスタム実装 |
|---|---|---|
| シンプルなトグルコンテンツ | 推奨 | 不要 |
| JavaScript 無効時のサポート | ネイティブで動作 | フォールバックが必要 |
| 滑らかなアニメーション | 限定的なサポート | 完全に制御可能 |
| 外部状態制御 | 制限あり | 完全に制御可能 |
| カスタムスタイリング | ブラウザ依存 | 完全に制御可能 |
アクセシビリティ
WAI-ARIA ロール
| ロール | 対象要素 | 説明 |
|---|---|---|
button | トリガー要素 | パネルの表示を切り替えるインタラクティブな要素(ネイティブの<button>を使用) |
WAI-ARIA Disclosure Pattern (opens in new tab)
WAI-ARIA プロパティ
| 属性 | 対象 | 値 | 必須 | 説明 |
|---|---|---|---|---|
aria-controls | button | パネルへのID参照 | はい | ボタンと制御対象のパネルを関連付けます |
aria-hidden | panel | true | false | いいえ | 折りたたまれた際にパネルを支援技術から隠します |
WAI-ARIA ステート
aria-expanded
| 対象 | button 要素 |
| 値 | true | false |
| 必須 | はい |
| 変更トリガー | Click、Enter、Space |
| リファレンス | aria-expanded (opens in new tab) |
キーボードサポート
| キー | アクション |
|---|---|
| Tab | ディスクロージャーボタンにフォーカスを移動します |
| Space / Enter | ディスクロージャーパネルの表示を切り替えます |
ディスクロージャーはネイティブの<button>要素の動作をキーボードインタラクションに使用します。追加のキーボードハンドラーは必要ありません。
ソースコード
import { useId, useState, useCallback } from 'react';
import { cn } from '@/lib/utils';
/**
* Props for the Disclosure component
*
* @see https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/
*
* @example
* ```tsx
* <Disclosure trigger="Show details">
* <p>Hidden content that can be revealed</p>
* </Disclosure>
* ```
*/
export interface DisclosureProps {
/**
* Content displayed in the disclosure trigger button
*/
trigger: React.ReactNode;
/**
* Content displayed in the collapsible panel
*/
children: React.ReactNode;
/**
* When true, the panel is expanded on initial render
* @default false
*/
defaultExpanded?: boolean;
/**
* Callback fired when the expanded state changes
* @param expanded - The new expanded state
*/
onExpandedChange?: (expanded: boolean) => void;
/**
* When true, the disclosure cannot be expanded/collapsed
* @default false
*/
disabled?: boolean;
/**
* Additional CSS class to apply to the disclosure container
* @default ""
*/
className?: string;
}
export function Disclosure({
trigger,
children,
defaultExpanded = false,
onExpandedChange,
disabled = false,
className = '',
}: DisclosureProps): React.ReactElement {
const instanceId = useId();
const panelId = `${instanceId}-panel`;
const [expanded, setExpanded] = useState(defaultExpanded);
const handleToggle = useCallback(() => {
if (disabled) return;
const newExpanded = !expanded;
setExpanded(newExpanded);
onExpandedChange?.(newExpanded);
}, [expanded, disabled, onExpandedChange]);
return (
<div className={cn('apg-disclosure', className)}>
<button
type="button"
aria-expanded={expanded}
aria-controls={panelId}
disabled={disabled}
className="apg-disclosure-trigger"
onClick={handleToggle}
>
<span className="apg-disclosure-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<polyline points="9 6 15 12 9 18" />
</svg>
</span>
<span className="apg-disclosure-trigger-content">{trigger}</span>
</button>
<div
id={panelId}
className="apg-disclosure-panel"
aria-hidden={!expanded}
inert={!expanded ? true : undefined}
>
<div className="apg-disclosure-panel-content">{children}</div>
</div>
</div>
);
}
export default Disclosure; 使い方
import { Disclosure } from './Disclosure';
function App() {
return (
<Disclosure
trigger="Show details"
defaultExpanded={false}
onExpandedChange={(expanded) => console.log('Expanded:', expanded)}
>
<p>Hidden content that can be revealed</p>
</Disclosure>
);
} API
DisclosureProps
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
trigger | ReactNode | 必須 | トリガーボタンに表示されるコンテンツ |
children | ReactNode | 必須 | パネルに表示されるコンテンツ |
defaultExpanded | boolean | false | 初期展開状態 |
onExpandedChange | (expanded: boolean) => void | - | 展開状態変更時のコールバック |
disabled | boolean | false | ディスクロージャーを無効化 |
className | string | "" | 追加の CSS クラス |
テスト
テストは、キーボード操作、ARIA属性、アクセシビリティ要件の観点からAPG準拠を検証します。Disclosureコンポーネントは4つのフレームワーク全体でE2Eテストを使用しています。
テスト戦略
E2Eテスト(Playwright)
4つのフレームワーク全体で実際のブラウザ環境でのコンポーネント動作を検証します。フルブラウザコンテキストが必要なインタラクションをカバーします。
- キーボード操作(Space、Enter)
- aria-expanded状態の切り替え
- aria-controlsによるパネル関連付け
- パネル表示の同期
- 無効状態の動作
- フォーカス管理とTabナビゲーション
- クロスフレームワーク一貫性
テストカテゴリ
高優先度 : APG ARIA構造(E2E)
| テスト | 説明 |
|---|---|
button element | トリガーがセマンティックな<button>要素である |
aria-expanded | ボタンがaria-expanded属性を持つ |
aria-controls | ボタンがaria-controls経由でパネルIDを参照 |
accessible name | ボタンがコンテンツまたはaria-labelからアクセシブルな名前を持つ |
高優先度 : APGキーボード操作(E2E)
| テスト | 説明 |
|---|---|
Space key toggles | Spaceキーを押すとDisclosureの状態が切り替わる |
Enter key toggles | Enterキーを押すとDisclosureの状態が切り替わる |
Tab navigation | Tabキーでフォーカスをボタンに移動 |
Disabled Tab skip | 無効化されたDisclosureはTabの順序でスキップされる |
高優先度 : 状態の同期(E2E)
| テスト | 説明 |
|---|---|
aria-expanded toggle | クリックでaria-expanded値が変わる |
panel visibility | パネルの表示がaria-expanded状態と一致 |
collapsed hidden | 折りたたみ時にパネルコンテンツが非表示 |
expanded visible | 展開時にパネルコンテンツが表示 |
高優先度 : 無効状態(E2E)
| テスト | 説明 |
|---|---|
disabled attribute | 無効なDisclosureがdisabled属性を持つ |
click blocked | 無効なDisclosureはクリックでトグルしない |
keyboard blocked | 無効なDisclosureはキーボードでトグルしない |
中優先度 : アクセシビリティ(E2E)
| テスト | 説明 |
|---|---|
axe (collapsed) | 折りたたみ状態でWCAG 2.1 AA違反なし |
axe (expanded) | 展開状態でWCAG 2.1 AA違反なし |
テストの実行
# DisclosureのE2Eテストを実行(全フレームワーク)
npm run test:e2e:pattern --pattern=disclosure テストツール
- Vitest (opens in new tab) - ユニットテストランナー
- Testing Library (opens in new tab) - フレームワーク別テストユーティリティ(React、Vue、Svelte)
- Playwright (opens in new tab) - E2Eテスト用ブラウザ自動化
- axe-core/playwright (opens in new tab) - E2Eでの自動アクセシビリティテスト
詳細は testing-strategy.md (opens in new tab) を参照してください。
リソース
- WAI-ARIA APG: Disclosure パターン (opens in new tab)
- MDN: <details> element (opens in new tab)
- AI Implementation Guide (llm.md) (opens in new tab) - ARIA specs, keyboard support, test checklist