APG Patterns
日本語 GitHub
日本語 GitHub

Disclosure

A button that controls the visibility of a section of content.

🤖 AI Implementation Guide

Demo

Basic Disclosure

A simple disclosure that toggles the visibility of content when clicked.

Initially Expanded

Use the defaultExpanded prop to show the content on initial render.

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

Disabled State

Use the disabled prop to prevent interaction with the disclosure.

Open demo only →

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

Accessibility Features

WAI-ARIA Roles

Role Target Element Description
button Trigger element Interactive element that toggles panel visibility (use native <button>)

WAI-ARIA Disclosure Pattern (opens in new tab)

WAI-ARIA Properties

Attribute Target Values Required Description
aria-controls button ID reference to panel Yes Associates the button with the panel it controls
aria-hidden panel true | false No Hides panel from assistive technology when collapsed

WAI-ARIA States

aria-expanded

Indicates whether the disclosure panel is expanded or collapsed.

Target button element
Values true | false
Required Yes
Change Trigger Click, Enter, Space
Reference aria-expanded (opens in new tab)

Keyboard Support

Key Action
Tab Move focus to the disclosure button
Space / Enter Toggle the visibility of the disclosure panel

Disclosure uses native <button> element behavior for keyboard interaction. No additional keyboard handlers are required.

Source Code

Disclosure.vue
<template>
  <div :class="['apg-disclosure', className]">
    <button
      type="button"
      :aria-expanded="expanded"
      :aria-controls="panelId"
      :disabled="disabled"
      class="apg-disclosure-trigger"
      @click="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">
        <slot name="trigger">{{ trigger }}</slot>
      </span>
    </button>
    <div
      :id="panelId"
      class="apg-disclosure-panel"
      :aria-hidden="!expanded"
      :inert="!expanded || undefined"
    >
      <div class="apg-disclosure-panel-content">
        <slot />
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
/**
 * APG Disclosure Pattern - Vue Implementation
 *
 * A button that controls the visibility of a section of content.
 *
 * @see https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/
 */
import { ref, useId } from 'vue';

/**
 * Props for the Disclosure component
 *
 * @example
 * ```vue
 * <Disclosure trigger="Show details">
 *   <p>Hidden content that can be revealed</p>
 * </Disclosure>
 * ```
 */
export interface DisclosureProps {
  /** Content displayed in the disclosure trigger button (can also use #trigger slot) */
  trigger?: string;
  /** When true, the panel is expanded on initial render @default false */
  defaultExpanded?: boolean;
  /** When true, the disclosure cannot be expanded/collapsed @default false */
  disabled?: boolean;
  /** Additional CSS class @default "" */
  className?: string;
}

const props = withDefaults(defineProps<DisclosureProps>(), {
  trigger: '',
  defaultExpanded: false,
  disabled: false,
  className: '',
});

const emit = defineEmits<{
  expandedChange: [expanded: boolean];
}>();

const instanceId = useId();
const panelId = `${instanceId}-panel`;

const expanded = ref(props.defaultExpanded);
const { className } = props;

const handleToggle = () => {
  if (props.disabled) return;

  expanded.value = !expanded.value;
  emit('expandedChange', expanded.value);
};
</script>

Usage

Example
<script setup>
import Disclosure from './Disclosure.vue';
</script>

<template>
  <Disclosure
    trigger="Show details"
    :default-expanded="false"
    @expanded-change="(expanded) => console.log('Expanded:', expanded)"
  >
    <p>Hidden content that can be revealed</p>
  </Disclosure>
</template>

API

Props

Prop Type Default Description
trigger string "" Content displayed in the trigger button (or use #trigger slot)
defaultExpanded boolean false Initially expanded state
disabled boolean false Disable the disclosure
className string "" Additional CSS class

Events

Event Payload Description
expandedChange boolean Emitted when expanded state changes

Slots

Slot Description
default Content displayed in the panel
#trigger Custom trigger content (alternative to trigger prop)

Testing

Tests verify APG compliance across keyboard interaction, ARIA attributes, and accessibility requirements. The Disclosure component uses E2E tests across all four frameworks.

Testing Strategy

E2E Tests (Playwright)

Verify component behavior in a real browser environment across all four frameworks. These tests cover interactions that require full browser context.

  • Keyboard interactions (Space, Enter)
  • aria-expanded state toggling
  • aria-controls panel association
  • Panel visibility synchronization
  • Disabled state behavior
  • Focus management and Tab navigation
  • Cross-framework consistency

Test Categories

High Priority: APG ARIA Structure

Test Description
button element Trigger is a semantic <button> element
aria-expanded Button has aria-expanded attribute
aria-controls Button references panel ID via aria-controls
accessible name Button has an accessible name from content or aria-label

High Priority: APG Keyboard Interaction

Test Description
Space key toggles Pressing Space toggles the disclosure state
Enter key toggles Pressing Enter toggles the disclosure state
Tab navigation Tab key moves focus to disclosure button
Disabled Tab skip Disabled disclosures are skipped in Tab order

High Priority: State Synchronization

Test Description
aria-expanded toggle Click changes aria-expanded value
panel visibility Panel visibility matches aria-expanded state
collapsed hidden Panel content is hidden when collapsed
expanded visible Panel content is visible when expanded

High Priority: Disabled State

Test Description
disabled attribute Disabled disclosure has disabled attribute
click blocked Disabled disclosure doesn't toggle on click
keyboard blocked Disabled disclosure doesn't toggle on keyboard

Medium Priority: Accessibility

Test Description
axe (collapsed) No WCAG 2.1 AA violations in collapsed state
axe (expanded) No WCAG 2.1 AA violations in expanded state

Testing Tools

See testing-strategy.md (opens in new tab) for full documentation.

Resources