Skip to content

BbAccordion

A panel that opens when you click its header, with the accessibility already wired.

import { BbAccordion } from 'bitboss-ui';

On this page

Use it for

Reach for BbAccordion when the trigger is a title sitting over the body: an FAQ entry, a settings section, a row in a list. You get the button and the accessibility for free.

Use something else when

  • BbCollapsible, if the trigger cannot be a header: a switch in a settings row, a link inside a paragraph
  • BbSmoothHeight, if you hide nothing and only want a height change to animate
  • BbTabs, if the sections are peer views of one subject and only one stays visible

Passthrough

Hover or tap a part to outline it. The toggles turn on loading, errors and warnings where the component has them; only parts get a row.

Refunds go back to the original payment method within five days.

Default

One BbAccordion is one panel, and one panel is one boolean.

Orders placed before 15:00 CET ship the same day. Delivery inside the EU takes 2 to 4 working days and is tracked from the moment it leaves the warehouse.

title gives the header its look: the text, a chevron that turns as the panel opens, padding, and a rule under the panel. The title slot replaces only the text and keeps the rest, as One panel at a time does with a price beside each name. The header slot replaces the whole trigger, chevron included, for a header of your own.

Each of them lands inside a real <button>, already carrying aria-expanded and aria-controls. The body is the region that button labels. You add none of it, and the keyboard comes with the button.

Never put a <button> or an <a> inside the header. Actions belong in the body.

A list of panels

There is no BbAccordionGroup. Render several panels, one boolean each.

Yes. New seats are billed pro rata for the days left in the current cycle, and the full amount from the next invoice.

The header spans the panel and is the only click target. Whatever you slot into it becomes click-transparent, so a badge, an icon or a <span> never swallows the toggle. A :hover on those children never fires, though: hang it on the header, for example pt:header="hover:underline".

Panels nest with no extra wiring. Pad the nested body, or the hierarchy disappears.

When the headers are section titles, such as an FAQ or a settings page, pass heading-level so a screen reader user can jump between them. The button then sits inside an <h1> to <h6>. Match the page outline: under an <h2>, the panels take 3.

vue
<h2>Billing questions</h2>
<BbAccordion v-model="open" :heading-level="3" title="Can I change plans?">
    Yes, at any time.
</BbAccordion>

Leave it out inside a form, a card or a side panel, where the headers are controls and not titles.

One panel at a time

Track one active key and derive every panel from it.

  • Unlimited workspaces
  • SAML and OIDC
  • Priority support

Clicking the open header emits false. Set the key to null in that branch, or the open panel refuses to close.

Use a stable key rather than an index when the list can reorder or filter. And do not write a wrapper that forces siblings closed.

A close action in the body

header and the body receive the same two things, open and toggle.

Lumen Sit-Stand Desk 160× 1
Northlight 27" 4K Display× 1

open says whether the panel is open. Use it to rotate a chevron or swap the header copy. Assigning to it does nothing.

toggle() earns its place in the body, as a "Show less" at the end of a long panel. In the header it is redundant, since the button already toggles.

Listen to update:modelValue only for side effects: analytics, a fetch on first open. The open or close has already happened.

Coming from v2The slot scope says open, not value

In v2 both slots received { value, toggle }. The flag is now open, the word BbPopover, BbDropdown and the pickers use for the same thing. With vue-tsc checking templates the old name is a compile error; without it, value reads undefined.

diff
- <template #header="{ value }">
+ <template #header="{ open }">

When the body renders

The body does not exist until the first open, and it stays mounted from then on.

Add eager when the content has to exist while the panel is shut. A field joins its BbForm only once it has rendered. Without eager the form submits as though the field were not there, and it starts blocking submission the first time somebody opens the panel. The same goes for text a prerendered page has to carry.

If a collapsed panel can hold an invalid field, open it from the form's invalid event, as the demo does.

Closed content is aria-hidden and inert, eager or not: out of the tab order, unclickable, invisible to screen readers. The mechanism is documented on BbCollapsible.

Pass id when the markup has to be predictable. The body takes your id and the header takes <id>_header, already cross-wired.

Spacing and styling

Pad the body with pt:panel (or .bb-accordion__panel) or on what you slot in: the panel sits inside the collapsing region, so its padding collapses with it. With title the header and the body pad themselves; the header slot gets a button with no padding of its own, so pad the <span> you put in it.

transition-duration (milliseconds, 250 by default) drives the height and the fade together. Under prefers-reduced-motion: reduce both drop to near-instant on their own.

Three classes are yours: .bb-accordion on the panel, .bb-accordion__header on the button, .bb-accordion__panel on the body, plus .bb-accordion__title and .bb-accordion__icon (the chevron) under title. The same parts take pt, with an open state: pt:header:open="font-semibold", pt:icon="text-(--bb-primary)". If you repaint the header, keep a visible focus ring: the browser supplies none.

Under title every panel draws its own rule underneath, so a list needs no divider. With the header slot the borders and dividers belong to the container that renders the panels, which keeps a collapsed panel from leaving a stray line.

Coming from v2.bb-accordion__content → .bb-accordion__panel

v2's .bb-accordion__content sat on the collapsible's root. It now matches nothing; npx bitboss-ui check reports it in your stylesheets. The body is wrapped in a new div, .bb-accordion__panel (pt:panel), inside the collapsing region, so padding, borders and backgrounds on it collapse with the body instead of holding a closed panel open.

diff
- .faq .bb-accordion__content { color: var(--bb-text-muted); }
+ .faq .bb-accordion__panel { color: var(--bb-text-muted); }

Find every use with rg -n -- 'bb-accordion__content'.