v1.0.0

<bmx-accordion>

A set of bmx-accordion-items that know about each other: one open at a time, or several; collapsible or always showing something; arrow keys moving between the headings.

5 properties · 1 events · 4 methods · 1 parts

Example

Two to three working days to a UK address, and next-day if the order is placed before noon.

Anything unworn, within thirty days, in the packaging it arrived in.

To the EU and to the United States. Duties are shown at checkout rather than on arrival.

Not available yet.

One section at a time. Arrow keys move between the headings; Tab moves through them.

Several at once, in the separated appearance:

Card, PayPal, or an invoice on an approved trade account.

Cardboard and paper tape. No plastic anywhere in the chain.

And one on its own, which is a disclosure rather than an accordion:

Show markup
<div class="row">
  <bmx-accordion id="ex-acc" style="max-inline-size: 40rem">
    <bmx-accordion-item heading="How long does delivery take?">
      <p style="margin: 0">Two to three working days to a UK address, and next-day if the order is placed before noon.</p>
    </bmx-accordion-item>

    <bmx-accordion-item heading="Can I return something?" badge="30 days">
      <p style="margin: 0">Anything unworn, within thirty days, in the packaging it arrived in.</p>
    </bmx-accordion-item>

    <bmx-accordion-item heading="Do you ship outside the UK?">
      <p style="margin: 0">To the EU and to the United States. Duties are shown at checkout rather than on arrival.</p>
    </bmx-accordion-item>

    <bmx-accordion-item heading="Trade accounts" disabled>
      <p style="margin: 0">Not available yet.</p>
    </bmx-accordion-item>
  </bmx-accordion>
</div>

<div class="row">
  <span class="note" id="ex-acc-out">One section at a time. Arrow keys move between the headings; Tab moves through them.</span>
</div>

<div class="row">
  <p style="margin: 0">Several at once, in the separated appearance:</p>
</div>

<div class="row">
  <bmx-accordion multiple appearance="separated" style="max-inline-size: 40rem">
    <bmx-accordion-item heading="Payment" open>
      <p style="margin: 0">Card, PayPal, or an invoice on an approved trade account.</p>
    </bmx-accordion-item>
    <bmx-accordion-item heading="Packaging" open>
      <p style="margin: 0">Cardboard and paper tape. No plastic anywhere in the chain.</p>
    </bmx-accordion-item>
  </bmx-accordion>
</div>

<div class="row">
  <p style="margin: 0">And one on its own, which is a disclosure rather than an accordion:</p>
</div>

<div class="row">
  <bmx-accordion-item heading="Advanced options" style="max-inline-size: 40rem; border: 1px solid var(--bmx-border); border-radius: var(--bmx-radius-lg)">
    <bmx-switch label="Send anonymous usage statistics"></bmx-switch>
  </bmx-accordion-item>
</div>

<script type="module">
  await customElements.whenDefined('bmx-accordion');

  const accordion = document.getElementById('ex-acc');
  const out = document.getElementById('ex-acc-out');

  // One event per press, carrying the whole open set - not one from the item
  // that opened and another from the one that closed.
  accordion.addEventListener('bmxChange', event => {
    const { value, open, expanded } = event.detail;

    out.textContent = `"${value}" ${open ? 'opened' : 'closed'} — showing: ${expanded.length > 0 ? expanded.join(', ') : 'nothing'}`;
  });
</script>

WHAT IT OWNS, AND WHAT THE ITEMS OWN

Each item draws its own heading and its own panel, and keeps both ends of its ARIA references inside its own shadow root - so this component never reaches across a boundary to wire anything, which is the problem that shaped bmx-tabs. What is left for the group is the part an item cannot know: that opening one may have to close another.

So an item reports its press, this component decides what the open set becomes, and it writes open back onto every item. That is also why the item's own bmxToggle is stopped at this boundary: the items are light-DOM children, so their events pass through this element on the way to whoever is listening, and a consumer would otherwise see a toggle for the item they pressed, a second for the one that closed, and no single event saying what the accordion now shows. One bmxChange says all of it.

KEYBOARD

Every heading is a Tab stop, which is the part libraries most often get wrong: an accordion is a list of ordinary buttons, and a user who has reached one expects Tab to take them to the next thing rather than out of the accordion entirely. Up, Down, Home and End are a shortcut on top of that, not a replacement for it.

Properties

PropertyAttributeTypeDefaultDescription
appearance appearance BmxAccordionAppearance 'bordered' How the accordion is drawn.
collapsible collapsible boolean true Whether the open panel may be closed again, leaving none. Only asked when one panel may be open. False is the right shape for a wizard, where something must always be showing; true is the right shape for a list of questions, where closing the answer you have read is the obvious thing to try.
expanded property only string[] [] Which panels are open, by value. Mutable, so pressing a heading updates it. Seeded from whichever items were written with open when the accordion first reads its children, and made legal on the way in: values naming nothing are dropped, and several open items in a single-open accordion keep the first in document order. Accepts a comma-separated or JSON string as well, because an attribute is the only channel some templates have. See src/core/markup.ts.
headingLevel heading-level number 3 The aria-level every item's heading is given.
multiple multiple boolean false Whether more than one panel may be open. The default is one at a time, which is what makes it an accordion rather than a list of disclosures - and what a long page of sections usually wants, because it keeps the headings within reach of each other.

Events

EventDetailDescription
bmxChange BmxAccordionChangeDetail Fired when a panel opens or closes.

Methods

MethodSignatureDescription
closeAll closeAll() => Promise<void> Close every panel. Ignored when one must always be showing.
closePanel closePanel(value: string) => Promise<void> Close a panel by value.
openAll openAll() => Promise<void> Open every panel. Ignored unless several may be open.
openPanel openPanel(value: string) => Promise<void> Open a panel by value, closing another if only one may be open.

Slots

SlotDescription
(default) bmx-accordion-item elements.

CSS shadow parts

PartDescription
base The container.

CSS custom properties

PropertyDescription
--bmx-accordion-background Background behind the sections.
--bmx-accordion-border-color Colour of the rules between sections.
--bmx-accordion-gap Space between sections in the separated appearance.
--bmx-accordion-radius Corner radius of the container, or of each section when separated.