Collapsible
@cloudflare/kumo

Kumo is Cloudflare's new design system.

import { Collapsible, Text } from "@cloudflare/kumo";
import { useState } from "react";

/**
 * Hero demo using DefaultTrigger and DefaultPanel for classic Kumo styling.
 */
export function CollapsibleHeroDemo() {
  const [isOpen, setIsOpen] = useState(true);
  return (
    <div className="w-full">
      <Collapsible.Root open={isOpen} onOpenChange={setIsOpen}>
        <Collapsible.DefaultTrigger>What is Kumo?</Collapsible.DefaultTrigger>
        <Collapsible.DefaultPanel>
          <Text>Kumo is Cloudflare's new design system.</Text>
        </Collapsible.DefaultPanel>
      </Collapsible.Root>
    </div>
  );
}

Installation

Barrel

import { Collapsible } from "@cloudflare/kumo";

Granular

import { Collapsible } from "@cloudflare/kumo/components/collapsible";

Usage

Collapsible uses a compound component pattern for full composition control.

With Default Styling

Use DefaultTrigger and DefaultPanel for the classic Kumo style:

import { Collapsible } from "@cloudflare/kumo";

export default function Example() {
  const [open, setOpen] = useState(false);

  return (
    <Collapsible.Root open={open} onOpenChange={setOpen}>
      <Collapsible.DefaultTrigger>Show details</Collapsible.DefaultTrigger>
      <Collapsible.DefaultPanel>
        Content with border-left accent styling.
      </Collapsible.DefaultPanel>
    </Collapsible.Root>
  );
}

Custom Trigger

Use the render prop on Trigger for full control over the trigger element:

<Collapsible.Root open={open} onOpenChange={setOpen}>
  <Collapsible.Trigger render={<Button variant="ghost" />}>
    {open ? "Hide" : "Show"} details
  </Collapsible.Trigger>
  <Collapsible.Panel className="mt-2 p-4 bg-kumo-tint rounded-lg">
    Custom styled panel content.
  </Collapsible.Panel>
</Collapsible.Root>

Examples

Basic

import { Collapsible, Text } from "@cloudflare/kumo";
import { useState } from "react";

/**
 * Basic usage with default styling components.
 */
export function CollapsibleBasicDemo() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <div className="w-full">
      <Collapsible.Root open={isOpen} onOpenChange={setIsOpen}>
        <Collapsible.DefaultTrigger>What is Kumo?</Collapsible.DefaultTrigger>
        <Collapsible.DefaultPanel>
          <Text>Kumo is Cloudflare's new design system.</Text>
        </Collapsible.DefaultPanel>
      </Collapsible.Root>
    </div>
  );
}

Multiple Items

import { Collapsible, Text } from "@cloudflare/kumo";
import { useState } from "react";

/**
 * Multiple independent collapsibles.
 */
export function CollapsibleMultipleDemo() {
  const [open1, setOpen1] = useState(false);
  const [open2, setOpen2] = useState(false);
  const [open3, setOpen3] = useState(false);

  return (
    <div className="w-full space-y-2">
      <Collapsible.Root open={open1} onOpenChange={setOpen1}>
        <Collapsible.DefaultTrigger>What is Kumo?</Collapsible.DefaultTrigger>
        <Collapsible.DefaultPanel>
          <Text>Kumo is Cloudflare's new design system.</Text>
        </Collapsible.DefaultPanel>
      </Collapsible.Root>
      <Collapsible.Root open={open2} onOpenChange={setOpen2}>
        <Collapsible.DefaultTrigger>How do I use it?</Collapsible.DefaultTrigger>
        <Collapsible.DefaultPanel>
          <Text>Install the components and import them into your project.</Text>
        </Collapsible.DefaultPanel>
      </Collapsible.Root>
      <Collapsible.Root open={open3} onOpenChange={setOpen3}>
        <Collapsible.DefaultTrigger>Is it open source?</Collapsible.DefaultTrigger>
        <Collapsible.DefaultPanel>
          <Text>Check the repository for license information.</Text>
        </Collapsible.DefaultPanel>
      </Collapsible.Root>
    </div>
  );
}

Custom Trigger

Use Collapsible.Trigger with the render prop for full control:

import { Button, Collapsible, Text } from "@cloudflare/kumo";
import { useState } from "react";

/**
 * Custom trigger using the render prop for full control.
 */
export function CollapsibleCustomTriggerDemo() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <div className="w-full">
      <Collapsible.Root open={isOpen} onOpenChange={setIsOpen}>
        <Collapsible.Trigger
          render={<Button variant="secondary" size="sm" />}
        >
          {isOpen ? "Hide details" : "Show details"}
        </Collapsible.Trigger>
        <Collapsible.Panel className="mt-3 rounded-lg bg-kumo-tint p-4">
          <Text>
            This panel uses custom styling instead of the default border-left accent.
          </Text>
        </Collapsible.Panel>
      </Collapsible.Root>
    </div>
  );
}

Accordion Pattern

Control which item is open to create an accordion where only one item can be expanded at a time:

Kumo is Cloudflare's new design system built on Base UI and Tailwind CSS v4.

import { Collapsible, Text } from "@cloudflare/kumo";
import { useState } from "react";

/**
 * Accordion pattern where only one item can be open at a time.
 */
export function CollapsibleAccordionDemo() {
  const [activeIndex, setActiveIndex] = useState<number | null>(0);

  const items = [
    {
      title: "What is Kumo?",
      content: "Kumo is Cloudflare's new design system built on Base UI and Tailwind CSS v4.",
    },
    {
      title: "How do I install it?",
      content: "Run `npm install @cloudflare/kumo` and import the components you need.",
    },
    {
      title: "Is it accessible?",
      content: "Yes! Kumo is built on Base UI which provides excellent accessibility out of the box.",
    },
  ];

  return (
    <div className="w-full space-y-2">
      {items.map((item, i) => (
        <Collapsible.Root
          key={i}
          open={activeIndex === i}
          onOpenChange={(open) => setActiveIndex(open ? i : null)}
        >
          <Collapsible.DefaultTrigger>{item.title}</Collapsible.DefaultTrigger>
          <Collapsible.DefaultPanel>
            <Text>{item.content}</Text>
          </Collapsible.DefaultPanel>
        </Collapsible.Root>
      ))}
    </div>
  );
}

Sub-components

ComponentDescription
Collapsible.RootManages open state. Pass open and onOpenChange for controlled mode.
Collapsible.TriggerButton that toggles visibility. Use render prop for custom elements.
Collapsible.PanelContainer for collapsible content.
Collapsible.DefaultTriggerPre-styled trigger with text label and animated caret icon.
Collapsible.DefaultPanelPre-styled panel with border-left accent and standard spacing.

API Reference

Collapsible.Root

PropTypeDefaultDescription
openbooleanWhether the panel is visible (controlled).
defaultOpenbooleanfalseInitial open state (uncontrolled).
onOpenChange(open: boolean) => voidCallback when open state changes.
disabledbooleanfalseWhether the collapsible is disabled.

Collapsible.Trigger

PropTypeDefaultDescription
renderReactElementCustom element to render as trigger.
classNamestringAdditional CSS classes.

Collapsible.Panel

PropTypeDefaultDescription
classNamestringAdditional CSS classes.
keepMountedbooleanfalseWhether to keep the panel in the DOM when closed.

Collapsible.DefaultTrigger

PropTypeDefaultDescription
childrenReactNodeLabel text displayed in the trigger.
classNamestringAdditional CSS classes.

Collapsible.DefaultPanel

PropTypeDefaultDescription
childrenReactNodePanel content.
classNamestringAdditional CSS classes.