Collapsible

A single section that can be expanded or collapsed. For multiple independent sections use Accordion with type='multiple'.

Show-more list (chevron rotates on open)

@peduarte starred 3 repositories

@radix-ui/primitives

Advanced options (outline trigger)

Installation

pnpm
pnpm dlx @hex-core/cli add collapsible

Expandable table row detail

Drill into a row without leaving the table. Each Collapsible owns its own tbody (multiple tbody elements per table is valid HTML and gives Radix Slot a real element to clone via asChild). Chevron rotates via the trigger's data-state attribute.

tsx
import { useState } from "react";
import { ChevronRight } from "lucide-react";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import { TableCell, TableRow } from "@/components/ui/table";

interface Order {
  id: string;
  customer: string;
  total: string;
  items: { sku: string; qty: number }[];
}

export function OrderRow({ order }: { order: Order }) {
  const [open, setOpen] = useState(false);
  return (
    <Collapsible asChild open={open} onOpenChange={setOpen}>
      <tbody>
        <TableRow>
          <TableCell className="w-8">
            <CollapsibleTrigger className="group inline-flex h-6 w-6 items-center justify-center rounded hover:bg-muted" aria-label="Toggle line items">
              <ChevronRight className="h-4 w-4 transition-transform group-data-[state=open]:rotate-90" />
            </CollapsibleTrigger>
          </TableCell>
          <TableCell className="font-mono text-xs">{order.id}</TableCell>
          <TableCell>{order.customer}</TableCell>
          <TableCell className="text-right">{order.total}</TableCell>
        </TableRow>
        <CollapsibleContent asChild>
          <TableRow className="bg-muted/40">
            <TableCell />
            <TableCell colSpan={3} className="py-3">
              <ul className="space-y-1 text-sm">
                {order.items.map((line) => (
                  <li key={line.sku} className="flex justify-between">
                    <span className="font-mono text-xs">{line.sku}</span>
                    <span>x{line.qty}</span>
                  </li>
                ))}
              </ul>
            </TableCell>
          </TableRow>
        </CollapsibleContent>
      </tbody>
    </Collapsible>
  );
}

Nested advanced settings

Hide power-user options behind a labelled trigger inside a Card section so the default form stays focused.

tsx
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { ChevronDown } from "lucide-react";

export function DeploySettings() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Deploy</CardTitle>
      </CardHeader>
      <CardContent className="space-y-4">
        <div className="space-y-2">
          <Label htmlFor="branch">Branch</Label>
          <Input id="branch" defaultValue="main" />
        </div>
        <Collapsible className="rounded-md border">
          <CollapsibleTrigger className="group flex w-full items-center justify-between px-4 py-3 text-sm font-medium">
            Advanced
            <ChevronDown className="h-4 w-4 transition-transform group-data-[state=open]:rotate-180" />
          </CollapsibleTrigger>
          <CollapsibleContent className="space-y-3 border-t px-4 py-3">
            <div className="space-y-2">
              <Label htmlFor="build-cmd">Build command</Label>
              <Input id="build-cmd" defaultValue="pnpm build" />
            </div>
            <div className="space-y-2">
              <Label htmlFor="node">Node version</Label>
              <Input id="node" defaultValue="20.x" />
            </div>
            <div className="flex items-center justify-between">
              <Label htmlFor="telemetry" className="font-normal">Send build telemetry</Label>
              <Switch id="telemetry" defaultChecked />
            </div>
          </CollapsibleContent>
        </Collapsible>
      </CardContent>
    </Card>
  );
}

API Reference

PropTypeDefaultDescription
open
booleanControlled open state
defaultOpen
booleanfalseDefault open state
onOpenChange
functionCallback on open change: (open: boolean) => void
disabled
booleanfalseDisable toggling

AI Guidance

When to use

Use for a single show-more/show-less section: 'View full details', 'Advanced settings', preview cards with expandable notes.

When not to use

Don't use for multiple related sections (use Accordion). Don't use for overlays (use Dialog/Popover). Don't use for navigation (use DropdownMenu).

Common mistakes

  • Using Collapsible for multiple sections instead of Accordion
  • Missing asChild when passing a Button as trigger
  • Not animating content height (Radix exposes --radix-collapsible-content-height for CSS keyframes)

Accessibility

Radix sets aria-expanded on the trigger and aria-controls → content id. Trigger is keyboard-operable (Enter/Space).

Related components

Token budget: 250