Skip to content

BbCollapsible

A region that animates open and closed from a boolean you own, with your own trigger.

import { BbCollapsible } from 'bitboss-ui';

On this page

Use it for

Reach for BbCollapsible when the trigger cannot be a header sitting over the body. A switch in a settings row, a "Show more" at the end of a paragraph, a button in a toolbar.

Use something else when

  • BbAccordion, if the trigger is a title over the body: it writes the button and the ARIA for you
  • BbSmoothHeight, if you hide nothing and only want a height change to animate
  • BbTabs, if they are peer views of one subject and only one stays visible

BbAccordion is this component plus a wired header button. Everything on this page is true there too.

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.

Ships from Turin in two working days.

Default

Bind a boolean and supply the control that flips it.

The component cannot open or close itself. It renders model-value and never writes it back, and it emits nothing. v-model compiles and works, but only as a prop binding, so do not wait for an update:modelValue. Put side effects on the trigger.

model-value is optional and defaults to false: a missing or undefined binding renders closed. Initialise the ref with ref(false).

The root is a div, for block content (fields, paragraphs, lists), as every demo on this page holds. Pass tag="span" for a collapsible that sits inside a paragraph.

You own the trigger

The component manages the region. The semantics of the control that opens it are yours.

If the trigger is a button, give it aria-expanded and aria-controls pointing at the collapsible's id. Without them a screen-reader user gets a button whose effect is announced nowhere. That is what the Default demo above wires. bitboss-ui check reports a click handler that flips the ref with no aria-expanded on the trigger.

When the trigger is already a control with a state, do not add a second one:

A switch or a checkbox bound to the same ref already says "on / off", and the fields that appear are the consequence of the setting. Adding aria-expanded there announces the same fact twice.

If you would rather not wire this, step up to BbAccordion: it writes the header button for you.

Closed content is out of reach

Hiding something visually is not hiding it. Here the component does that work.

No refund requested yet.

Closed content is aria-hidden and inert: unfocusable, unclickable, out of the tab order and out of the accessibility tree. Do not wrap it in a v-if: that would take the animation and the mounted state with it.

Nothing inside the slot renders until the first open, and it stays mounted from then on. eager renders it immediately. Reach for it when the content has to exist before anyone opens the region. A form field that must register and validate, text a prerendered page has to carry, anything measured on mount. The demo above is eager, which is why its text sits in this page's static HTML.

One region at a time

Several collapsibles become an exclusive group when you derive each state from one key.

Order ORD-2026-0418

€338.80

One ref holds the open key, each region compares against it, and each trigger assigns it. No group wrapper, and no watcher forcing siblings shut. Make the key nullable if clicking the open trigger should close everything.

When the triggers are stacked headers, you are describing an accordion: see One panel at a time. When one region must always show, the buttons are a tab strip: use BbTabs.

Spacing and timing

Do not pad the collapsible itself: padding on its root stays when it closes, so a closed region keeps that height.

Its inner .bb-collapsible__content (pt:content) loses its vertical padding, border and gap only while closed. Space the element you slot in, as every demo here does. Both parts take an open state, pt:root:open and pt:content:open, for styling that should apply only while the region shows.

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

Coming from v2tag narrowed

tag takes only 'div' (the default) or 'span'. A span root keeps an inline "Show more" valid inside a paragraph. If you passed tag="section", wrap the component instead.

diff
- <BbCollapsible v-model="open" tag="section">…</BbCollapsible>
+ <section>
+   <BbCollapsible v-model="open">…</BbCollapsible>
+ </section>

Three classes are yours: .bb-collapsible on the root, with .bb-collapsible--open / .bb-collapsible--closed tracking the state, and .bb-collapsible__content on the wrapper around your slot. Style the state modifiers, not the animation: the height is a grid-template-rows transition between 0fr and 1fr, and overriding it is how you lose the collapse.