Collapse
Overview
The Collapse component provides an expandable/collapsible content panel.
It's perfect for accordions, FAQs, settings panels, and any content that should be hidden by default. Supports both controlled and uncontrolled modes, with smooth animations and full accessibility support. To group several into an accordion, wrap them in Collapses.
Import
import { Collapse } from '@allxsmith/bestax-bulma';
Usage
Basic Collapse
A simple uncontrolled collapse.
function example() { return ( <Collapse trigger={ <Block p="3" cursor="pointer"> <Strong>Click to expand</Strong> </Block> } > <Block p="3"> <Paragraph> This is the collapsible content. It can contain any React elements. </Paragraph> <Paragraph>Click the header again to collapse.</Paragraph> </Block> </Collapse> ); }
Default Open
Collapse that starts open by default.
function example() { return ( <Collapse defaultOpen trigger={ <Block p="3" cursor="pointer"> <Strong>This starts open</Strong> </Block> } > <Block p="3"> <Paragraph>This content is visible by default.</Paragraph> </Block> </Collapse> ); }
Without Animation
Collapse that toggles instantly without animation.
function example() { return ( <Collapse animation={false} trigger={ <Block p="3" cursor="pointer"> <Strong>No animation</Strong> </Block> } > <Block p="3"> <Paragraph>This collapse has animation disabled.</Paragraph> </Block> </Collapse> ); }
Controlled Mode
Collapse with external state management. The trigger reports through onOpenChange with the state
it asks for, so wiring onOpenChange to the same setter lets the trigger and the button both
change it. Without onOpenChange, a controlled trigger changes nothing.
function example() { const [isOpen, setIsOpen] = useState(false); return ( <Block> <Block mb="4"> <Button color="primary" onClick={() => setIsOpen(!isOpen)}> {isOpen ? 'Close' : 'Open'} Collapse </Button> <Span ml="4">State: {isOpen ? 'Open' : 'Closed'}</Span> </Block> <Collapse open={isOpen} onOpenChange={setIsOpen} trigger={ <Block p="3"> Controlled collapse (click here or the button above) </Block> } > <Block p="3"> <Paragraph>This collapse is controlled by external state.</Paragraph> </Block> </Collapse> </Block> ); }
Accordion
Wrap collapses in Collapses to group them into an accordion: it keeps one
item open at a time and addresses items by index, the way Tabs addresses tabs. Here the group
is controlled so each trigger can show its item's state.
function example() { const [openIndex, setOpenIndex] = useState<number | null>(0); const items = [ { title: 'Section 1', content: 'Content for section 1.' }, { title: 'Section 2', content: 'Content for section 2.' }, { title: 'Section 3', content: 'Content for section 3.' }, ]; return ( <Collapses value={openIndex} onChange={setOpenIndex}> {items.map((item, index) => ( <Collapse key={item.title} bordered trigger={ <Block p="4" display="flex" justifyContent="space-between" alignItems="center" > <Strong>{item.title}</Strong> <Span aria-hidden="true">{openIndex === index ? '−' : '+'}</Span> </Block> } > <Paragraph p="4">{item.content}</Paragraph> </Collapse> ))} </Collapses> ); }
FAQ Example
A common FAQ pattern, where answers open independently: Collapses with multiple holds the
open items as an array of indexes.
function example() { const faqs = [ { q: 'What is Bestax?', a: 'Bestax is a React component library built on Bulma.', }, { q: 'How do I install it?', a: 'pnpm add @allxsmith/bestax-bulma' }, { q: 'Is it free?', a: 'Yes, Bestax is open source and free to use.' }, ]; const [open, setOpen] = useState<number[]>([]); return ( <Block> <Title size="5" mb="4"> FAQ </Title> <Collapses multiple value={open} onChange={setOpen}> {faqs.map((faq, index) => ( <Collapse key={faq.q} bordered trigger={ <Block p="4" display="flex" justifyContent="space-between"> <Strong>{faq.q}</Strong> <Span aria-hidden="true"> {open.includes(index) ? '▲' : '▼'} </Span> </Block> } > <Paragraph p="4" textColor="grey-dark"> {faq.a} </Paragraph> </Collapse> ))} </Collapses> </Block> ); }
CSS Classes
The Collapse component supports these additional CSS classes:
| Class | Description |
|---|---|
is-active | Applied when the collapse is open |
is-bordered | Bordered style with rounded corners |
Accessibility
- Trigger has
role="button"andtabIndex="0"for keyboard access - Trigger has
aria-expandedto indicate open/closed state - Trigger has
aria-controlspointing to the content element - Content has
aria-hiddenmatching the collapsed state, and isinertwhile closed, so its links and fields leave the tab order until it opens (underfadeandslidea closed panel is only squeezed to height 0) - Supports keyboard navigation (Enter and Space to toggle)
Related
- Collapses - Groups collapses into an accordion
- Tabs - Tabbed content panels
- Panel - Bulma panel component
Props
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | — | Controlled open state. If provided, component is controlled, and the trigger only reports through onOpenChange. Inside a Collapses group, a Collapse that sets open stays yours: the group neither overrides it nor closes it when another item opens. |
defaultOpen | boolean | false | Initial open state for uncontrolled usage. Ignored inside a Collapses group, whose defaultValue decides. |
onOpen | () => void | — | Called when the trigger opens the Collapse. Fires only while the Collapse keeps its own state: not when open is set, and not when a Collapses group manages it. onOpenChange reports in every mode. |
onClose | () => void | — | Called when the trigger closes the Collapse. Fires only while the Collapse keeps its own state: not when open is set, and not when a Collapses group manages it. onOpenChange reports in every mode. |
onOpenChange | (open: boolean) => void | — | Called with the state the trigger asks for (true to open) each time it is clicked or activated with Enter or Space, in every mode. Pair it with open to let the trigger drive a controlled Collapse. |
trigger | React.ReactNode | — | The clickable trigger element (header/button). |
animation | 'fade' | 'slide' | false | 'fade' | Animation style, or false to disable. |
position | 'top' | 'bottom' | 'top' | Position of the trigger relative to content. |
ariaId | string | auto | Custom aria id for accessibility. |
bordered | boolean | false | Adds a border around the collapse. |
triggerClassName | string | — | Additional classes for the trigger wrapper. |
contentClassName | string | — | Additional classes for the content wrapper. |
children | React.ReactNode | — | The collapsible content. |
className | string | — | Additional CSS classes. |
... | All standard <div> attributes and Bulma helper props | — | See Helper Props |
CSS & Sass Variables
Collapse registers these variables on its own .collapse element. Override them there (or via className) — a value set on an ancestor is only inherited, and loses to the component-level declaration. See Theme.
| CSS Variable | Sass Variable | Default |
|---|---|---|
--bulma-collapse-margin-bottom | $collapse-margin-bottom | 0.5rem |
--bulma-collapse-border-color | $collapse-border-color | var(--bulma-border) |
--bulma-collapse-radius | $collapse-radius | var(--bulma-radius) |
--bulma-collapse-trigger-icon-size | $collapse-trigger-icon-size | 1.5em |
--bulma-collapse-trigger-icon-margin | $collapse-trigger-icon-margin | 0.5em |
--bulma-collapse-header-weight | $collapse-header-weight | var(--bulma-weight-semibold) |
--bulma-collapse-group-gap | $collapse-group-gap | 0.5rem |
--bulma-collapse-group-gap is the exception: it spaces the items of a
Collapses group, which reads it without declaring it, so set it on
the group or on any ancestor of the group rather than on a Collapse.