Diwa Design System

Accordion

An animated, accessible panel that reveals or hides content when the user clicks its heading. Follows the controlled component pattern — the consumer manages open state.

Keyboard interaction

KeyAction
TabMove focus to the accordion header button
Shift + TabMove focus to the previous focusable element
EnterToggle the accordion panel open or closed
SpaceToggle the accordion panel open or closed

Screen reader behaviour

The following ARIA attributes are managed internally by the component:

AttributeDescription
aria-expanded="true|false"Set on the toggle button. Reflects the current open prop so screen readers announce the collapsed/expanded state.
aria-controls="[panel-id]"Set on the toggle button. Points to the collapsible panel div, establishing the controlling relationship.
role="region"Set on the collapsible panel. Marks it as a landmark region for screen reader navigation.
aria-labelledby="[button-id]"Set on the collapsible panel. References the toggle button, giving the region an accessible name.
visibility: hidden (CSS)Applied to the collapsed panel. Removes interactive child content from the accessibility tree and tab order when the panel is closed.

WCAG 2.2 compliance

1.4.3 Contrast (Minimum) — AAPass

Header text and chevron icon use --diwa-text-primary; meets the 4.5:1 minimum contrast ratio against panel background in both themes.

1.4.11 Non-text Contrast — AAPass

The accordion border (--diwa-border) is validated to have ≥ 3:1 contrast against adjacent surface colours.

2.1.1 Keyboard — APass

The toggle button is reachable by Tab and activatable with Enter or Space. Slotted content inside an open panel is in the natural tab order.

2.4.7 Focus Visible — AAPass

A tokenized focus outline (var(--diwa-focus-ring-width) solid --diwa-border-focus) is rendered on the inner button via :focus-visible when navigating by keyboard.

4.1.2 Name, Role, Value — APass

aria-expanded, aria-controls, role="region", and aria-labelledby are all managed automatically by the component.

Best practices

  • Heading hierarchy — always set headingTag to fit the outline of the page. Defaulting to h2 implicitly makes every accordion a top-level section — only appropriate when the accordion IS the first heading level on the page. Incorrect hierarchy confuses screen reader users navigating by heading.
  • Focus management — the component uses shadow: { delegatesFocus: true }. Calling .focus() on the host element correctly forwards focus to the inner <button>. The :focus-visible ring is rendered on the inner button and visible to all pointer and keyboard users.
  • Controlled pattern — the component emits update but never mutates open itself. The consumer must always respond to the event. Failing to do so means the accordion will appear frozen after the first click.
  • Slot content — interactive elements (links, buttons, inputs) slotted inside become unreachable when the panel is closed because visibility: hidden removes them from the accessibility tree. There is no need for additional tabindex="-1" management.
  • Reduced motion — all CSS transitions inside the shadow DOM respond to prefers-reduced-motion: reduce and are suppressed to an instant switch.