Disclosure
コンテンツセクションの表示/非表示を制御するボタン。
🤖 AI 実装ガイドデモ
基本的なディスクロージャー
クリックでコンテンツの表示/非表示を切り替えるシンプルなディスクロージャー。
初期状態で展開
defaultExpanded プロパティを使用すると、初期レンダリング時にコンテンツを表示できます。
This content is visible when the page loads because defaultExpanded is set to
true.
無効化状態
disabled プロパティを使用すると、ディスクロージャーの操作を無効化できます。
Native HTML
Use Native HTML First
Before using this custom component, consider using native <details> and <summary> elements.
They provide built-in accessibility, work without JavaScript, and require no ARIA attributes.
<details>
<summary>Show details</summary>
<p>Hidden content here...</p>
</details> Use custom implementations only when you need smooth height animations, external state control, or styling that native elements cannot provide.
| Use Case | Native HTML | Custom Implementation |
|---|---|---|
| Simple toggle content | Recommended | Not needed |
| JavaScript disabled support | Works natively | Requires fallback |
| Smooth animations | Limited support | Full control |
| External state control | Limited | Full control |
| Custom styling | Browser-dependent | Full control |
アクセシビリティ
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構造
| テスト | 説明 |
|---|---|
button要素 | トリガーがセマンティックな<button>要素である |
aria-expanded | ボタンがaria-expanded属性を持つ |
aria-controls | ボタンがaria-controlsでパネルIDを参照 |
アクセシブル名 | ボタンがコンテンツまたはaria-labelからアクセシブルな名前を持つ |
高優先度: APGキーボード操作
| テスト | 説明 |
|---|---|
Spaceキーでトグル | Spaceキーを押すとDisclosureの状態が切り替わる |
Enterキーでトグル | Enterキーを押すとDisclosureの状態が切り替わる |
Tabナビゲーション | Tabキーでフォーカスをボタンに移動 |
無効時のTabスキップ | 無効化されたDisclosureはTabの順序でスキップされる |
高優先度: 状態の同期
| テスト | 説明 |
|---|---|
aria-expandedトグル | クリックでaria-expanded値が変わる |
パネル表示 | パネルの表示がaria-expanded状態と一致 |
折りたたみ時は非表示 | 折りたたみ時にパネルコンテンツが非表示 |
展開時は表示 | 展開時にパネルコンテンツが表示 |
高優先度: 無効状態
| テスト | 説明 |
|---|---|
disabled属性 | 無効なDisclosureがdisabled属性を持つ |
クリック無効 | 無効なDisclosureはクリックでトグルしない |
キーボード無効 | 無効なDisclosureはキーボードでトグルしない |
中優先度: アクセシビリティ
| テスト | 説明 |
|---|---|
axe(折りたたみ時) | 折りたたみ状態でWCAG 2.1 AA違反なし |
axe(展開時) | 展開状態でWCAG 2.1 AA違反なし |
テストツール
- 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