Recent chats
ASKGEEZ Q&A GUIDETechnology & devices

How can I build a show-and-hide disclosure button that stays in sync?

Quick answer

Use a button to control a specific content section, and keep the button’s aria-expanded value matched to whether that section is visible: true when open, false when closed. Make one function update both the section’s visibility and the button state, and use it for every open, close, or reset action. Test toggling with Enter and Space, then verify the state again after other code changes the disclosure.

LET’S WORK THROUGH IT

The question, the details, the next step.

Question

What does it mean for a show-and-hide disclosure button to stay in sync? I can make the content disappear, but I’m not sure what else needs to change.

Geez

Treat the button and the content it controls as two parts of one disclosure. The button’s aria-expanded value should be true when the content is visible and false when it is hidden. The WAI-ARIA Authoring Practices Guide describes the pattern as a button controlling a section, and says to keep the reported state consistent with actual visibility whenever visibility changes. aria-controls can identify the controlled section. [S1]

A practical rule is: do not update only the visual content or only the accessibility state. Route every change through the same small update function so both are set together.

Follow-up question

Could you show a small example that keeps those values together?

Geez

Here is a plain HTML-and-JavaScript pattern. The example assumes this disclosure starts closed; adjust the initial values together if your page should start open.

<button type="button" id="details-button"
        aria-expanded="false" aria-controls="details-panel">
  Show details
</button>
<div id="details-panel" hidden>
  Extra information goes here.
</div>

<script>
  const button = document.querySelector('#details-button');
  const panel = document.querySelector('#details-panel');

  function setOpen(open) {
    panel.hidden = !open;
    button.setAttribute('aria-expanded', String(open));
    button.textContent = open ? 'Hide details' : 'Show details';
  }

  button.addEventListener('click', () => {
    setOpen(button.getAttribute('aria-expanded') !== 'true');
  });
</script>

The hidden property controls whether the panel is shown in this example; the same setOpen function updates aria-expanded and the button label. The label is useful visual wording, but it is not a replacement for the expanded state. The pattern guidance establishes the expected relationship and interaction, not a required framework or this particular code structure. [S1]

Follow-up question

What if another part of my page changes the content or resets the panel? That is where I think it gets out of step.

Geez

Changing the text inside an already-open panel does not by itself require changing whether the panel is expanded. The important case is when other code changes its visibility. If a reset closes the panel, have the reset call setOpen(false); if another action opens it, call setOpen(true). Avoid separately changing panel.hidden or aria-expanded in those other code paths.

For a larger component, keep one state value such as isOpen and have one render or update function derive both the visible panel and the button’s expanded state from it. This makes the state relationship explicit. The source guidance says the reported state should remain consistent whenever visibility changes, but it does not prescribe a particular state-management system. [S1]

Follow-up question

How should I test the finished disclosure, including keyboard use?

Geez

Test the behavior, not just the appearance. Click the button to open and close the panel, and after each action check that the panel’s actual visibility agrees with aria-expanded. Then activate the button with Enter and Space; both are expected to toggle a disclosure button. [S1]

Also test any other path that can change visibility, such as a reset or a parent component update. If one path changes the panel without updating the button state, fix that path to use the shared update function. The guide describes the expected interaction and semantics; it does not establish that a particular sample is correct for every framework or page, so test your own implementation.

A practical checklist

  • Use a button for the control and identify the section it controls.

  • Set aria-expanded to true when the section is visible and false when hidden.

  • Use one update function or render path to change visibility and expanded state together.

  • Route resets and other visibility changes through that same update path.

  • Test open and close with both a pointer and the Enter and Space keys.

A few more useful answers

Is `aria-controls` required for the disclosure to work?

The pattern guide says aria-controls may identify the controlled content; it does not describe it as mandatory. The essential state relationship is that aria-expanded matches whether the content is visible. [S1]

Should I change `aria-expanded` when only the panel’s text changes?

Not if the panel remains open or remains closed. Update the expanded state when visibility changes, not merely because the content within the panel is edited.

Sources

References used for this guide.

  1. [S1] W3C ARIA APG: Disclosure (Show/Hide) Pattern
    w3.org · Checked

YOUR QUESTION. YOUR DETAILS.

Let’s work through your situation.

Get a tailored answer or help with your next task. Starts at $1, with a quote before you pay.

Ask your own question
← Back to Geez at Work