Skip to main content

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"
          bgColor="white-ter"
          cursor="pointer"
          style={{ borderRadius: '4px' }}
        >
          <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"
          bgColor="white-ter"
          cursor="pointer"
          style={{ borderRadius: '4px' }}
        >
          <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"
          bgColor="white-ter"
          cursor="pointer"
          style={{ borderRadius: '4px' }}
        >
          <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" bgColor="white-ter" style={{ borderRadius: '4px' }}>
            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:

ClassDescription
is-activeApplied when the collapse is open
is-borderedBordered style with rounded corners

Accessibility​

  • Trigger has role="button" and tabIndex="0" for keyboard access
  • Trigger has aria-expanded to indicate open/closed state
  • Trigger has aria-controls pointing to the content element
  • Content has aria-hidden matching the collapsed state, and is inert while closed, so its links and fields leave the tab order until it opens (under fade and slide a closed panel is only squeezed to height 0)
  • Supports keyboard navigation (Enter and Space to toggle)

  • Collapses - Groups collapses into an accordion
  • Tabs - Tabbed content panels
  • Panel - Bulma panel component

Props​

PropTypeDefaultDescription
openboolean—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.
defaultOpenbooleanfalseInitial 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.
triggerReact.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.
ariaIdstringautoCustom aria id for accessibility.
borderedbooleanfalseAdds a border around the collapse.
triggerClassNamestring—Additional classes for the trigger wrapper.
contentClassNamestring—Additional classes for the content wrapper.
childrenReact.ReactNode—The collapsible content.
classNamestring—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 VariableSass VariableDefault
--bulma-collapse-margin-bottom$collapse-margin-bottom0.5rem
--bulma-collapse-border-color$collapse-border-colorvar(--bulma-border)
--bulma-collapse-radius$collapse-radiusvar(--bulma-radius)
--bulma-collapse-trigger-icon-size$collapse-trigger-icon-size1.5em
--bulma-collapse-trigger-icon-margin$collapse-trigger-icon-margin0.5em
--bulma-collapse-header-weight$collapse-header-weightvar(--bulma-weight-semibold)
--bulma-collapse-group-gap$collapse-group-gap0.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.