APG Patterns
English GitHub
English GitHub

Disclosure

コンテンツセクションの表示を制御するボタン。

🤖 AI Implementation Guide

デモ

基本的な Disclosure

クリックするとコンテンツの表示を切り替えるシンプルな開閉ボタン。

初期展開

defaultExpanded プロパティを使用して、初期レンダリング時にコンテンツを表示します。

このコンテンツは、defaultExpandedtrue に設定されているため、ページ読み込み時に表示されます。

無効化状態

disabled プロパティを使用して、Disclosure との対話を防ぎます。

デモのみ表示 →

ネイティブ 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>要素の動作をキーボードインタラクションに使用します。追加のキーボードハンドラーは必要ありません。

ソースコード

Disclosure.svelte
<script lang="ts">
  import type { Snippet } from 'svelte';

  /**
   * APG Disclosure Pattern - Svelte Implementation
   *
   * A button that controls the visibility of a section of content.
   *
   * @see https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/
   */

  /**
   * Props for the Disclosure component
   */
  interface DisclosureProps {
    /** Unique identifier (recommended for SSR-safe aria-controls) */
    id?: string;
    /** Content displayed in the disclosure trigger button */
    trigger: string;
    /** Content displayed in the collapsible panel */
    children?: Snippet;
    /** When true, the panel is expanded on initial render */
    defaultExpanded?: boolean;
    /** When true, the disclosure cannot be expanded/collapsed */
    disabled?: boolean;
    /** Additional CSS class */
    className?: string;
    /** Callback fired when the expanded state changes */
    onExpandedChange?: (expanded: boolean) => void;
  }

  let {
    id,
    trigger,
    children,
    defaultExpanded = false,
    disabled = false,
    className = '',
    onExpandedChange = () => {},
  }: DisclosureProps = $props();

  // Generate fallback ID if not provided (client-side only, may cause SSR mismatch)
  const instanceId = id ?? crypto.randomUUID();

  let expanded = $state(defaultExpanded);

  const panelId = `${instanceId}-panel`;

  function handleToggle() {
    if (disabled) return;

    expanded = !expanded;
    onExpandedChange(expanded);
  }
</script>

<div class="apg-disclosure {className}">
  <button
    type="button"
    aria-expanded={expanded}
    aria-controls={panelId}
    {disabled}
    class="apg-disclosure-trigger"
    onclick={handleToggle}
  >
    <span class="apg-disclosure-icon" aria-hidden="true">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
        <polyline points="9 6 15 12 9 18" />
      </svg>
    </span>
    <span class="apg-disclosure-trigger-content">{trigger}</span>
  </button>
  <div
    id={panelId}
    class="apg-disclosure-panel"
    aria-hidden={!expanded}
    inert={!expanded ? true : undefined}
  >
    <div class="apg-disclosure-panel-content">
      {@render children?.()}
    </div>
  </div>
</div>

使い方

使用例
<script>
  import Disclosure from './Disclosure.svelte';
</script>

<Disclosure
  id="my-disclosure"
  trigger="Show details"
  defaultExpanded={false}
  onExpandedChange={(expanded) => console.log('Expanded:', expanded)}
>
  <p>Hidden content that can be revealed</p>
</Disclosure>

API

プロパティ

プロパティ デフォルト 説明
id string 自動生成 aria-controls用の一意の識別子(SSRでは推奨)
trigger string 必須 トリガーボタンに表示されるコンテンツ
children Snippet - パネルに表示されるコンテンツ
defaultExpanded boolean false 初期展開状態
disabled boolean false Disclosure を無効化
className string "" 追加のCSSクラス
onExpandedChange (expanded: boolean) => void - 展開状態変更時のコールバック

テスト

テストは、キーボード操作、ARIA属性、アクセシビリティ要件の観点からAPG準拠を検証します。Disclosureコンポーネントは4つのフレームワーク全体でE2Eテストを使用しています。

テスト戦略

E2Eテスト (Playwright)

4つのフレームワーク全体で実際のブラウザ環境でのコンポーネント動作を検証します。フルブラウザコンテキストが必要なインタラクションをカバーします。

  • キーボード操作(Space、Enter)
  • aria-expanded状態の切り替え
  • aria-controlsによるパネル関連付け
  • パネル表示の同期
  • 無効状態の動作
  • フォーカス管理とTabナビゲーション
  • クロスフレームワーク一貫性

テストカテゴリ

高優先度: APG ARIA構造(E2E)

テスト 説明
button要素 トリガーがセマンティックな<button>要素である
aria-expanded ボタンがaria-expanded属性を持つ
aria-controls ボタンがaria-controlsでパネルIDを参照
アクセシブル名 ボタンがコンテンツまたはaria-labelからアクセシブルな名前を持つ

高優先度: APGキーボード操作(E2E)

テスト 説明
Spaceキーでトグル Spaceキーを押すとDisclosureの状態が切り替わる
Enterキーでトグル Enterキーを押すとDisclosureの状態が切り替わる
Tabナビゲーション Tabキーでフォーカスをボタンに移動
無効時のTabスキップ 無効化されたDisclosureはTabの順序でスキップされる

高優先度: 状態の同期(E2E)

テスト 説明
aria-expandedトグル クリックでaria-expanded値が変わる
パネル表示 パネルの表示がaria-expanded状態と一致
折りたたみ時は非表示 折りたたみ時にパネルコンテンツが非表示
展開時は表示 展開時にパネルコンテンツが表示

高優先度: 無効状態(E2E)

テスト 説明
disabled属性 無効なDisclosureがdisabled属性を持つ
クリック無効 無効なDisclosureはクリックでトグルしない
キーボード無効 無効なDisclosureはキーボードでトグルしない

中優先度: アクセシビリティ(E2E)

テスト 説明
axe(折りたたみ時) 折りたたみ状態でWCAG 2.1 AA違反なし
axe(展開時) 展開状態でWCAG 2.1 AA違反なし

テストツール

詳細は testing-strategy.md (opens in new tab) を参照してください。

リソース