Collapsible

Reveal or hide supporting content in place.

Keep supporting details out of the way until they are needed. Pair a trigger with content, and nest panels for tree-like layouts.

Registry push #2841

tsx
import { Card, CardContent } from "@/components/ui/card"import {  Collapsible,  CollapsibleContent,  CollapsibleTrigger,} from "@/components/ui/collapsible"function ChevronDownIcon(props: React.SVGProps<SVGSVGElement>) {  return (    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>      <path d="m6 9 6 6 6-6" />    </svg>  )}export function Example() {  return (    <Card className="w-full py-0">      <CardContent className="py-4">        <Collapsible className="flex w-full flex-col">          <div className="flex items-center justify-between gap-3">            <h4 className="text-sm font-medium tracking-wide">              Registry push #2841            </h4>            <CollapsibleTrigger className="group gap-1.5 rounded-md px-2.5 py-1.5 hover:bg-accent hover:text-accent-foreground">              Details              <ChevronDownIcon className="size-4 transition-transform duration-200 ease-out group-data-[state=open]:rotate-180" />            </CollapsibleTrigger>          </div>          <CollapsibleContent className="flex flex-col gap-2 pt-3 text-sm tracking-wide text-muted-foreground">            <div className="flex items-center justify-between gap-3 rounded-md border border-border px-3 py-2">              <span>Status</span>              <span className="text-foreground">Shipped</span>            </div>            <div className="flex items-center justify-between gap-3 rounded-md border border-border px-3 py-2">              <span>Namespace</span>              <span className="text-foreground">@arctis-sh/ui</span>            </div>          </CollapsibleContent>        </Collapsible>      </CardContent>    </Card>  )}

Installation

npx @arctis-sh/@arctis-sh/ui@latest add collapsible

Usage

import {  Collapsible,  CollapsibleContent,  CollapsibleTrigger,} from "@/components/ui/collapsible"
<Collapsible>  <CollapsibleTrigger>What’s included?</CollapsibleTrigger>  <CollapsibleContent>    You get the source. Install the component, then adapt it to your app.  </CollapsibleContent></Collapsible>

Composition

Collapsible
├── CollapsibleTrigger
└── CollapsibleContent

Basic

A card header with a trigger that reveals status rows beneath the title.

Registry push #2841

tsx
import { Card, CardContent } from "@/components/ui/card"import {  Collapsible,  CollapsibleContent,  CollapsibleTrigger,} from "@/components/ui/collapsible"function ChevronDownIcon(props: React.SVGProps<SVGSVGElement>) {  return (    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>      <path d="m6 9 6 6 6-6" />    </svg>  )}export function Example() {  return (    <Card className="w-full py-0">      <CardContent className="py-4">        <Collapsible className="flex w-full flex-col">          <div className="flex items-center justify-between gap-3">            <h4 className="text-sm font-medium tracking-wide">              Registry push #2841            </h4>            <CollapsibleTrigger className="group gap-1.5 rounded-md px-2.5 py-1.5 hover:bg-accent hover:text-accent-foreground">              Details              <ChevronDownIcon className="size-4 transition-transform duration-200 ease-out group-data-[state=open]:rotate-180" />            </CollapsibleTrigger>          </div>          <CollapsibleContent className="flex flex-col gap-2 pt-3 text-sm tracking-wide text-muted-foreground">            <div className="flex items-center justify-between gap-3 rounded-md border border-border px-3 py-2">              <span>Status</span>              <span className="text-foreground">Shipped</span>            </div>            <div className="flex items-center justify-between gap-3 rounded-md border border-border px-3 py-2">              <span>Namespace</span>              <span className="text-foreground">@arctis-sh/ui</span>            </div>          </CollapsibleContent>        </Collapsible>      </CardContent>    </Card>  )}

Controlled

Pass open and onOpenChange when the parent owns the state. Use defaultOpen to set an initial uncontrolled state.

tsx
import * as React from "react"import { Card, CardContent } from "@/components/ui/card"import {  Collapsible,  CollapsibleContent,  CollapsibleTrigger,} from "@/components/ui/collapsible"function ChevronDownIcon(props: React.SVGProps<SVGSVGElement>) {  return (    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>      <path d="m6 9 6 6 6-6" />    </svg>  )}export function Example() {  const [open, setOpen] = React.useState(false)  return (    <Card className="w-full bg-transparent py-0">      <CardContent className="py-4">        <Collapsible          open={open}          onOpenChange={setOpen}          className="flex w-full flex-col"        >          <CollapsibleTrigger className="group w-full justify-between text-left">            Theme tokens {open ? "(open)" : "(closed)"}            <ChevronDownIcon className="size-4 transition-transform duration-200 ease-out group-data-[state=open]:rotate-180" />          </CollapsibleTrigger>          <CollapsibleContent className="pt-3 text-sm tracking-wide text-muted-foreground">            CSS variables under globals.css drive light and dark surfaces for            every component page.          </CollapsibleContent>        </Collapsible>      </CardContent>    </Card>  )}

Disabled

Set disabled on the root to lock the trigger. Dim the row as well so it reads as inactive.

tsx
import { Card, CardContent } from "@/components/ui/card"import {  Collapsible,  CollapsibleContent,  CollapsibleTrigger,} from "@/components/ui/collapsible"function ChevronDownIcon(props: React.SVGProps<SVGSVGElement>) {  return (    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>      <path d="m6 9 6 6 6-6" />    </svg>  )}export function Example() {  return (    <Card className="w-full bg-transparent py-0">      <CardContent className="py-4">        <Collapsible disabled className="flex w-full flex-col">          <CollapsibleTrigger className="group w-full justify-between text-left">            Publish package — unlock after review            <ChevronDownIcon className="size-4" />          </CollapsibleTrigger>          <CollapsibleContent className="pt-3 text-sm tracking-wide text-muted-foreground">            Shipping is locked until the changelog entry is approved.          </CollapsibleContent>        </Collapsible>      </CardContent>    </Card>  )}

Settings panel

Place a compact trigger beside a label, then reveal the related controls below it.

Radius

Set the corner radius of the element.

tsx
import * as React from "react"import { Card, CardContent } from "@/components/ui/card"import {  Collapsible,  CollapsibleContent,  CollapsibleTrigger,} from "@/components/ui/collapsible"function CornersIcon(props: React.SVGProps<SVGSVGElement>) {  return (    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>      <path d="M8 3H5a2 2 0 0 0-2 2v3" />      <path d="M16 3h3a2 2 0 0 1 2 2v3" />      <path d="M8 21H5a2 2 0 0 1-2-2v-3" />      <path d="M16 21h3a2 2 0 0 0 2-2v-3" />    </svg>  )}function ShrinkCornersIcon(props: React.SVGProps<SVGSVGElement>) {  return (    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>      <g transform="rotate(180 5.5 5.5)">        <path d="M8 3H5a2 2 0 0 0-2 2v3" />      </g>      <g transform="rotate(180 18.5 5.5)">        <path d="M16 3h3a2 2 0 0 1 2 2v3" />      </g>      <g transform="rotate(180 5.5 18.5)">        <path d="M8 21H5a2 2 0 0 1-2-2v-3" />      </g>      <g transform="rotate(180 18.5 18.5)">        <path d="M16 21h3a2 2 0 0 0 2-2v-3" />      </g>    </svg>  )}export function Example() {  const [open, setOpen] = React.useState(false)  return (    <Card className="w-full bg-transparent py-0">      <CardContent className="py-4">        <Collapsible          open={open}          onOpenChange={setOpen}          className="flex w-full flex-col gap-3"        >          <div className="grid gap-1">            <p className="text-sm font-medium tracking-wide">Radius</p>            <p className="text-sm text-muted-foreground">              Set the corner radius of the element.            </p>          </div>          <div className="flex items-start gap-2">            <div className="min-w-0 flex-1">              <div className="grid grid-cols-2 gap-2">                <input defaultValue="0" className="h-9 w-full rounded-md border border-border bg-transparent px-3 text-sm tracking-wide" />                <input defaultValue="0" className="h-9 w-full rounded-md border border-border bg-transparent px-3 text-sm tracking-wide" />              </div>              <CollapsibleContent className="pt-2">                <div className="grid grid-cols-2 gap-2">                  <input defaultValue="0" className="h-9 w-full rounded-md border border-border bg-transparent px-3 text-sm tracking-wide" />                  <input defaultValue="0" className="h-9 w-full rounded-md border border-border bg-transparent px-3 text-sm tracking-wide" />                </div>              </CollapsibleContent>            </div>            <CollapsibleTrigger className="inline-flex size-9 shrink-0 items-center justify-center rounded-md border border-border hover:bg-accent hover:text-accent-foreground">              {open ? (                <ShrinkCornersIcon className="size-4" />              ) : (                <CornersIcon className="size-4" />              )}              <span className="sr-only">Toggle radius settings</span>            </CollapsibleTrigger>          </div>        </Collapsible>      </CardContent>    </Card>  )}

File tree

Nest collapsibles to build a folder tree. The chevron rotates as each branch opens.

Explorer

button.tsx
card.tsx
collapsible.tsx
logo.tsx
globals.css
package.json
tsx
import type { ReactNode } from "react"import {  Collapsible,  CollapsibleContent,  CollapsibleTrigger,} from "@/components/ui/collapsible"function ChevronRightIcon(props: React.SVGProps<SVGSVGElement>) {  return (    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>      <path d="m9 18 6-6-6-6" />    </svg>  )}function FolderIcon(props: React.SVGProps<SVGSVGElement>) {  return (    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>      <path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.64 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" />    </svg>  )}function FileIcon(props: React.SVGProps<SVGSVGElement>) {  return (    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>      <path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />      <path d="M14 2v4a2 2 0 0 0 2 2h4" />    </svg>  )}function TreeFolder({  name,  defaultOpen = false,  children,}: {  name: string  defaultOpen?: boolean  children: ReactNode}) {  return (    <Collapsible defaultOpen={defaultOpen} className="flex flex-col">      <CollapsibleTrigger className="group w-fit justify-start gap-1.5 rounded-md px-1.5 py-1 text-left text-sm tracking-wide hover:bg-accent hover:text-accent-foreground">        <ChevronRightIcon className="size-3.5 shrink-0 transition-transform duration-200 ease-out group-data-[state=open]:rotate-90" />        <FolderIcon className="size-3.5 shrink-0 text-muted-foreground" />        {name}      </CollapsibleTrigger>      <CollapsibleContent className="ms-3 flex flex-col gap-1 border-l border-border ps-2 pt-1">        {children}      </CollapsibleContent>    </Collapsible>  )}function TreeFile({ name }: { name: string }) {  return (    <div className="inline-flex w-fit items-center gap-1.5 rounded-md px-1.5 py-1 text-sm tracking-wide text-muted-foreground hover:bg-accent hover:text-accent-foreground">      <span className="size-3.5 shrink-0" />      <FileIcon className="size-3.5 shrink-0" />      {name}    </div>  )}export function Example() {  return (    <div className="w-full rounded-md border border-border p-3">      <p className="mb-2 px-1.5 text-xs tracking-wide text-muted-foreground">        Explorer      </p>      <TreeFolder name="components" defaultOpen>        <TreeFolder name="ui" defaultOpen>          <TreeFile name="button.tsx" />          <TreeFile name="card.tsx" />          <TreeFile name="collapsible.tsx" />        </TreeFolder>        <TreeFile name="logo.tsx" />      </TreeFolder>      <TreeFolder name="lib">        <TreeFile name="utils.ts" />      </TreeFolder>      <TreeFile name="globals.css" />      <TreeFile name="package.json" />    </div>  )}

API Reference

Collapsible

PropTypeDefault
openboolean
defaultOpenbooleanfalse
onOpenChange(open: boolean) => void
disabledbooleanfalse
classNamestring
<Collapsible>  <CollapsibleTrigger>Toggle</CollapsibleTrigger>  <CollapsibleContent>Hidden content</CollapsibleContent></Collapsible>

CollapsibleTrigger

PropTypeDefault
disabledbooleanfalse
classNamestring
<CollapsibleTrigger>Toggle</CollapsibleTrigger>

CollapsibleContent

PropTypeDefault
unmountOnExitbooleanfalse
classNamestring
<CollapsibleContent>Hidden content</CollapsibleContent>