APG Patterns
English
English

Disclosure

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

デモ

基本ディスクロージャー

クリックでコンテンツの表示・非表示を切り替えるシンプルなディスクロージャー。

初期展開状態

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

This content is visible when the page loads because defaultExpanded is set to true.

無効状態

disabled プロパティを使用して、ディスクロージャーの操作を無効にします。

デモのみ表示 →

ネイティブ 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 プロパティ

aria-controls

ボタンと制御対象のパネルを関連付けます

パネルへのID参照
必須
はい

aria-hidden

折りたたまれた際にパネルを支援技術から隠します

true | false
必須
いいえ

WAI-ARIA ステート

aria-expanded

対象要素
button 要素
true | false
必須
はい
変更トリガー
Click、Enter、Space

キーボードサポート

キー アクション
Tab ディスクロージャーボタンにフォーカスを移動します
Space / Enter ディスクロージャーパネルの表示を切り替えます
  • ディスクロージャーはネイティブの<button>要素の動作をキーボードインタラクションに使用します。追加のキーボードハンドラーは必要ありません。

参考資料

ソースコード

Disclosure.svelte
<script lang="ts">
  import type { Snippet } from 'svelte';
  import { untrack } 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 = untrack(() => id) ?? crypto.randomUUID();

  let expanded = $state(untrack(() => 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>

使い方

Example
<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

プロパティデフォルト説明
idstringauto-generatedaria-controls用の一意の識別子(SSRでは推奨)
triggerstringrequiredトリガーボタンに表示されるコンテンツ
childrenSnippet-パネルに表示されるコンテンツ
defaultExpandedbooleanfalse初期展開状態
disabledbooleanfalseDisclosure を無効化
classNamestring""追加のCSSクラス
onExpandedChange(expanded: boolean) => void-展開状態変更時のコールバック

テスト

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

テスト戦略

E2Eテスト(Playwright)

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

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

テストカテゴリ

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

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

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

テスト説明
Space key togglesSpaceキーを押すとDisclosureの状態が切り替わる
Enter key togglesEnterキーを押すとDisclosureの状態が切り替わる
Tab navigationTabキーでフォーカスをボタンに移動
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

テストツール

詳細はテスト戦略ガイドを参照してください。

リソース