

<Preview name="BasicCollapsibleExample" />

## Overview [#overview]

The **Collapsible** component provides an interactive way to show and hide content with smooth CSS transitions. Built on Base UI primitives, it supports both controlled and uncontrolled states, making it perfect for creating expandable sections, details panels, and progressive disclosure interfaces.

## Usage [#usage]

```tsx
import {
  Collapsible,
  CollapsiblePanel,
  CollapsibleTrigger,
} from "@tilt-legal/cubitt-components/collapsible";
```

```tsx
<Collapsible>
  <CollapsiblePanel>Your collapsible content goes here</CollapsiblePanel>
  <CollapsibleTrigger />
</Collapsible>
```

## Examples [#examples]

### Card with Collapsible [#card-with-collapsible]

Combine collapsible with cards to create expandable information panels.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="CardCollapsibleExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    "use client";

    import { cn } from "@tilt-legal/cubitt-components/utilities";
    import { Badge } from "@tilt-legal/cubitt-components/badge";
    import { Button } from "@tilt-legal/cubitt-components/button";
    import {
      Card,
      CardContent,
      CardHeader,
      CardHeading,
      CardToolbar,
    } from "@tilt-legal/cubitt-components/card";
    import {
      Collapsible,
      CollapsiblePanel,
      CollapsibleTrigger,
    } from "@tilt-legal/cubitt-components/collapsible";
    import {
      ArrowDown,
      ArrowUp,
      ChevronDown,
      ChevronUp,
    } from "@tilt-legal/cubitt-icons/ui/outline";

    export default function Component() {
      const [isOpen, setIsOpen] = React.useState(false);

      interface IStatItem {
        label: string;
        value: number;
        change: string;
        changeType: "increase" | "decrease";
      }

      const stats: IStatItem[] = [
        {
          label: "Added to Cart",
          value: 3842,
          change: "+1.8%",
          changeType: "increase",
        },
        {
          label: "Reached Checkout",
          value: 1256,
          change: "-1.2%",
          changeType: "decrease",
        },
        {
          label: "Purchased",
          value: 649,
          change: "+2.4%",
          changeType: "increase",
        },
      ];

      return (
        <Card className="w-full max-w-[350px]">
          <Collapsible open={isOpen} onOpenChange={setIsOpen}>
            <CardHeader
              className={cn(
                "h-auto py-4 transition-[border-radius,margin]",
                isOpen ? "rounded-b-none!" : "mb-0! rounded-b-xl!"
              )}
            >
              <CardHeading>
                <div className="font-medium text-fg-2 text-sm">
                  Conversion Rate
                </div>
                <div className="flex items-center gap-1.5">
                  <span className="font-semibold text-fg-1 text-xl">
                    29.9%
                  </span>
                  <Badge variant="brand" size="sm">
                    +4.5%
                  </Badge>
                </div>
              </CardHeading>
              <CardToolbar>
                <CollapsibleTrigger
                  render={
                    <Button variant="secondary">
                      Details
                      {isOpen ? <ChevronUp /> : <ChevronDown />}
                    </Button>
                  }
                />
              </CardToolbar>
            </CardHeader>
            <CollapsiblePanel>
              <CardContent className="space-y-3 rounded-t-none! text-sm">
                {stats.map((stat) => (
                  <div key={stat.label} className="flex items-center justify-between">
                    <span className="text-fg-2">{stat.label}</span>
                    <div className="flex items-center space-x-2">
                      <span className="font-semibold text-fg-1">
                        ${stat.value.toLocaleString()}
                      </span>
                      <span
                        className={cn(
                          "flex min-w-20 items-center justify-end font-medium text-sm",
                          stat.changeType === "increase"
                            ? "text-success"
                            : "text-destructive"
                        )}
                      >
                        {stat.changeType === "increase" ? (
                          <ArrowUp className="size-3.5" />
                        ) : (
                          <ArrowDown className="size-3.5" />
                        )}
                        {stat.change}
                      </span>
                    </div>
                  </div>
                ))}
              </CardContent>
            </CollapsiblePanel>
          </Collapsible>
        </Card>
      );
    }
    ```
  </Tab>
</Tabs>

### Default Open [#default-open]

Start the collapsible in an open state by default.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="DefaultOpenExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { Button } from "@tilt-legal/cubitt-components/button";
    import {
      Card,
      CardContent,
      CardFooter,
    } from "@tilt-legal/cubitt-components/card";
    import {
      Collapsible,
      CollapsiblePanel,
      CollapsibleTrigger,
    } from "@tilt-legal/cubitt-components/collapsible";

    export default function Component() {
      return (
        <Card className="w-full max-w-[500px] text-sm">
          <Collapsible defaultOpen>
            <CardContent className="gap-0">
              <div className="font-medium">Open by Default</div>
              <CollapsiblePanel>
                <div className="mt-3 text-fg-2">
                  This collapsible starts in the open state by default. Click the
                  trigger below to collapse it.
                </div>
              </CollapsiblePanel>
            </CardContent>

            <CardFooter className="justify-end">
              <CollapsibleTrigger
                render={
                  <Button size="sm" variant="link">
                    Toggle
                  </Button>
                }
              />
            </CardFooter>
          </Collapsible>
        </Card>
      );
    }
    ```
  </Tab>
</Tabs>

### Disabled [#disabled]

Prevent the collapsible from being toggled.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="DisabledCollapsibleExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { Button } from "@tilt-legal/cubitt-components/button";
    import {
      Card,
      CardContent,
      CardFooter,
    } from "@tilt-legal/cubitt-components/card";
    import {
      Collapsible,
      CollapsiblePanel,
      CollapsibleTrigger,
    } from "@tilt-legal/cubitt-components/collapsible";

    export default function Component() {
      return (
        <Card className="w-full max-w-[500px] text-sm opacity-60">
          <Collapsible disabled>
            <CardContent className="gap-0">
              <div className="font-medium">Disabled Collapsible</div>
              <CollapsiblePanel>
                <div className="mt-3 text-fg-2">
                  This content cannot be toggled because the collapsible is
                  disabled.
                </div>
              </CollapsiblePanel>
            </CardContent>

            <CardFooter className="justify-end">
              <CollapsibleTrigger
                render={
                  <Button disabled size="sm" variant="link">
                    Cannot toggle
                  </Button>
                }
              />
            </CardFooter>
          </Collapsible>
        </Card>
      );
    }
    ```
  </Tab>
</Tabs>

### Nested [#nested]

Create hierarchical collapsible sections by nesting multiple collapsibles.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="NestedCollapsibleExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    "use client";

    import { Button } from "@tilt-legal/cubitt-components/button";
    import { Card, CardContent } from "@tilt-legal/cubitt-components/card";
    import {
      Collapsible,
      CollapsiblePanel,
      CollapsibleTrigger,
    } from "@tilt-legal/cubitt-components/collapsible";
    import { ChevronDown, ChevronUp } from "@tilt-legal/cubitt-icons/ui/outline";

    export default function Component() {
      const [parentOpen, setParentOpen] = React.useState(false);
      const [child1Open, setChild1Open] = React.useState(false);
      const [child2Open, setChild2Open] = React.useState(false);

      return (
        <Card className="w-full max-w-[500px] text-sm">
          <Collapsible open={parentOpen} onOpenChange={setParentOpen}>
            <CardContent className="gap-0">
              <div className="flex items-center justify-between">
                <div className="font-medium">Parent Section</div>
                <CollapsibleTrigger
                  render={
                    <Button mode="icon" variant="ghost">
                      {parentOpen ? <ChevronUp /> : <ChevronDown />}
                    </Button>
                  }
                />
              </div>

              <CollapsiblePanel>
                <div className="mt-3 space-y-3 pl-4">
                  <Collapsible open={child1Open} onOpenChange={setChild1Open}>
                    <div className="flex items-center justify-between">
                      <div className="font-medium text-sm">Child Section 1</div>
                      <CollapsibleTrigger
                        render={
                          <Button mode="icon" variant="ghost">
                            {child1Open ? <ChevronUp /> : <ChevronDown />}
                          </Button>
                        }
                      />
                    </div>
                    <CollapsiblePanel>
                      <div className="mt-2 text-fg-2 text-sm">
                        Nested collapsible content for section 1.
                      </div>
                    </CollapsiblePanel>
                  </Collapsible>

                  <Collapsible open={child2Open} onOpenChange={setChild2Open}>
                    <div className="flex items-center justify-between">
                      <div className="font-medium text-sm">Child Section 2</div>
                      <CollapsibleTrigger
                        render={
                          <Button mode="icon" variant="ghost">
                            {child2Open ? <ChevronUp /> : <ChevronDown />}
                          </Button>
                        }
                      />
                    </div>
                    <CollapsiblePanel>
                      <div className="mt-2 text-fg-2 text-sm">
                        Nested collapsible content for section 2.
                      </div>
                    </CollapsiblePanel>
                  </Collapsible>
                </div>
              </CollapsiblePanel>
            </CardContent>
          </Collapsible>
        </Card>
      );
    }
    ```
  </Tab>
</Tabs>

***

## API Reference [#api-reference]

### Collapsible [#collapsible]

The root component that manages the collapsible state.

| Prop           | Type                             | Default | Description                                      |
| -------------- | -------------------------------- | ------- | ------------------------------------------------ |
| `open`         | `boolean`                        | -       | The controlled open state of the collapsible.    |
| `defaultOpen`  | `boolean`                        | `false` | The default open state when uncontrolled.        |
| `onOpenChange` | `(open: boolean) => void`        | -       | Callback fired when the open state changes.      |
| `disabled`     | `boolean`                        | `false` | Whether the collapsible is disabled.             |
| `className`    | `string`                         | -       | Additional CSS classes for the collapsible root. |
| `render`       | `React.ReactElement \| function` | -       | Allows replacing the component's HTML element.   |

### CollapsiblePanel [#collapsiblepanel]

The panel component that contains the collapsible content with animated height transitions.

| Prop        | Type                             | Default | Description                                    |
| ----------- | -------------------------------- | ------- | ---------------------------------------------- |
| `className` | `string`                         | -       | Additional CSS classes for the panel.          |
| `render`    | `React.ReactElement \| function` | -       | Allows replacing the component's HTML element. |

The panel includes built-in CSS transitions for smooth expand/collapse animations with automatic height calculation.

### CollapsibleTrigger [#collapsibletrigger]

The trigger component that toggles the collapsible state when clicked.

| Prop        | Type                             | Default | Description                                                                                        |
| ----------- | -------------------------------- | ------- | -------------------------------------------------------------------------------------------------- |
| `render`    | `React.ReactElement \| function` | -       | Allows replacing the component's HTML element. Use this to render as a custom element like Button. |
| `className` | `string`                         | -       | Additional CSS classes for the trigger.                                                            |

When using the `render` prop, the trigger will forward all collapsible state to the rendered element.
