Choose the interaction before writing code

An accordion groups content under headings that people can expand or collapse. Before implementing one, decide whether several sections may stay open at once. This example uses a multi-open accordion: opening one section does not change any other section. That rule is easy to explain, and it prevents a person from unexpectedly losing access to another open panel.

The example assumes a browser with standard DOM APIs and an HTML page whose script runs after the accordion markup has been parsed, such as a script placed at the end of the body. It needs no framework or build step. Each section has a heading containing a button, followed by a content panel. Give every button a unique id, every panel a unique id, and set each button’s aria-controls value to the id of its panel. Start with aria-expanded set to false on each button and the corresponding panel’s hidden attribute present.

Use a real button rather than a clickable div. A button can receive keyboard focus and already responds to Enter and Space. Keeping the button inside a heading also lets people navigate the page by headings while leaving the interactive control clearly identifiable.

Keep the button and panel in sync

For a small page, give each button the class accordion-trigger. The following script finds those buttons, looks up each associated panel through aria-controls, and updates the two pieces of state together. It assumes the ids in the markup are valid and unique.

document.querySelectorAll('.accordion-trigger').forEach((button) => { const panel = document.getElementById(button.getAttribute('aria-controls')); if (!panel) return; button.addEventListener('click', () => { const willOpen = panel.hidden; panel.hidden = !willOpen; button.setAttribute('aria-expanded', String(willOpen)); }); });

The click event covers pointer activation and the Enter and Space behavior supplied by a native button. There is no separate keydown handler for those keys; adding one could toggle the panel twice. The hidden property removes collapsed content from normal display and focus navigation. The aria-expanded value communicates the button’s current state to assistive technology. Both values change in the same handler, so they do not drift apart during ordinary use.

If a panel should start open, omit its hidden attribute and set its button’s aria-expanded value to true. Make that initial pair match before the script runs. Avoid using CSS alone to conceal a collapsed panel: visually hidden links or form fields may otherwise remain reachable by keyboard.

Make keyboard behavior easy to predict

In this version, Tab moves through the accordion buttons in document order, and Shift+Tab moves backward. Enter or Space activates the focused button. Focus stays on that button after a panel opens or closes. A person can therefore toggle the same section again without searching for the control. If an open panel contains links or form fields, those controls join the normal tab order; when the panel is closed, they are no longer available through Tab.

Arrow-key navigation and a one-open-at-a-time rule are not part of this example. Those can be useful design choices, but they need additional code and clear expectations. Do not intercept arrow keys merely because the component looks like an accordion: visitors may expect those keys to scroll the page. Likewise, there is no need to move focus into the panel when it opens. Leaving focus on the button preserves a stable starting point for the next action.

Avoid closing a panel automatically while focus is inside it. If another feature must do that, decide where focus should go before hiding the focused control. Otherwise, the next keyboard action can feel unpredictable.

Test states, not just clicks

Create at least two sections to check the multi-open rule. Start with both collapsed, open the first with Enter, then use Tab to reach the second and open it with Space. Both panels should remain visible. Close the first and confirm that the second stays open. Repeat with a pointer and check that the same states result.

Inspect the DOM after each toggle. An open section should have aria-expanded set to true and a panel without the hidden attribute; a closed section should have aria-expanded set to false and a hidden panel. Also test a panel containing a link: it should be reachable by Tab only while that panel is open. If focus jumps, content remains tabbable while closed, or the announced state disagrees with what is visible, fix that behavior before adding animations.

Animation is optional. If you add it later, keep the final hidden and aria-expanded states accurate, and avoid letting a transition leave collapsed content focusable. A dependable accordion is defined less by how it moves than by whether its controls, content and keyboard behavior consistently agree.

Back to category ↗