Collapsible
A wrapper for Base UI Collapsible — a single trigger that expands and collapses a panel with smooth animation.
Basic
Right indicator (default)
Left indicator
Open by default
This panel starts expanded. User can collapse it.
Disabled
Controlled
External open state + built-in trigger
Panel
Keep mounted — DOM preserved when closed
Panel DOM stays mounted when closed.
Triggers
Custom element trigger (div)
Installation
npx shadcn@latest add @glrk-ui/collapsibleIf you haven't set up the prerequisites yet, check out Prerequest section.
Copy and paste the following code into your project.
'use client'
import * as React from 'react'
import { Collapsible as CollapsiblePrimitive } from '@base-ui/react/collapsible'
import { ChevronDownIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
function Collapsible(props: CollapsiblePrimitive.Root.Props) {
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
}
function CollapsibleTrigger({
className,
children,
indicatorAt = 'right',
...props
}: CollapsiblePrimitive.Trigger.Props & { indicatorAt?: indicatorAtT }) {
const icon = (
<ChevronDownIcon
data-slot="collapsible-trigger-icon"
className={cn(
'pointer-events-none shrink-0 transition-transform duration-200 group-data-[panel-open]/collapsible-trigger:rotate-180',
indicatorAt === 'right' && 'ml-auto',
)}
/>
)
return (
<CollapsiblePrimitive.Trigger
data-slot="collapsible-trigger"
className={cn(
'group/collapsible-trigger flex w-full items-center gap-2 text-sm font-medium outline-none focus-visible:ring-2 focus-visible:ring-ring cursor-pointer disabled:pointer-events-none disabled:opacity-50 **:data-[slot=collapsible-trigger-icon]:size-4 **:data-[slot=collapsible-trigger-icon]:text-muted-foreground',
className,
)}
{...props}
>
{indicatorAt === 'left' && icon}
{children}
{indicatorAt === 'right' && icon}
</CollapsiblePrimitive.Trigger>
)
}
function CollapsibleContent({ className, ...props }: CollapsiblePrimitive.Panel.Props) {
return (
<CollapsiblePrimitive.Panel
data-slot="collapsible-content"
className={cn(
'overflow-hidden text-sm h-(--collapsible-panel-height) transition-[height] ease-out data-ending-style:h-0 data-starting-style:h-0',
className,
)}
{...props}
/>
)
}
type CollapsibleWrapperProps = {
trigger: React.ReactNode
children: React.ReactNode
triggerCls?: string
contentCls?: string
indicatorAt?: indicatorAtT
triggerProps?: Omit<CollapsiblePrimitive.Trigger.Props, 'children' | 'className'>
contentProps?: Omit<CollapsiblePrimitive.Panel.Props, 'children' | 'className'>
} & CollapsiblePrimitive.Root.Props
function CollapsibleWrapper({
trigger,
children,
triggerCls,
contentCls,
indicatorAt = 'right',
triggerProps,
contentProps,
...props
}: CollapsibleWrapperProps) {
return (
<Collapsible {...props}>
<CollapsibleTrigger className={cn(triggerCls)} indicatorAt={indicatorAt} {...triggerProps}>
{trigger}
</CollapsibleTrigger>
<CollapsibleContent className={cn(contentCls)} {...contentProps}>
{children}
</CollapsibleContent>
</Collapsible>
)
}
export { Collapsible, CollapsibleTrigger, CollapsibleContent, CollapsibleWrapper }
Usage
Basic
import { CollapsibleWrapper } from "@/components/ui/collapsible"
import { ChevronDown } from "lucide-react"
<CollapsibleWrapper
trigger={<><ChevronDown className="size-4" /> Details</>}
className="w-72 rounded-lg border p-3"
>
<p className="mt-2 text-sm text-muted-foreground">Panel content here.</p>
</CollapsibleWrapper>Default open
<CollapsibleWrapper defaultOpen trigger="Details">
Content visible on mount.
</CollapsibleWrapper>Disabled
<CollapsibleWrapper disabled trigger="Details">
Unreachable content.
</CollapsibleWrapper>Controlled
const [open, setOpen] = useState(false)
<CollapsibleWrapper
open={open}
onOpenChange={setOpen}
trigger="Details"
>
Controlled from outside.
</CollapsibleWrapper>Keep mounted
Panel DOM stays in tree when closed — preserves state, avoids remount:
<CollapsibleWrapper
trigger="Details"
contentProps={{ keepMounted: true }}
>
State here persists when collapsed.
</CollapsibleWrapper>Hidden until found
Closed panel content findable by browser Ctrl+F:
<CollapsibleWrapper
trigger="Details"
contentProps={{ hiddenUntilFound: true }}
>
This text is findable even when collapsed.
</CollapsibleWrapper>Custom element trigger
<CollapsibleWrapper
trigger="Settings"
triggerProps={{
render: <div role="button" tabIndex={0} />,
nativeButton: false,
}}
triggerCls="flex cursor-pointer items-center gap-2 rounded-md border px-3 py-1.5 text-sm"
>
Content here.
</CollapsibleWrapper>Using primitives
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "@/components/ui/collapsible"
<Collapsible>
<CollapsibleTrigger className="w-full justify-between">
Title
<ChevronDown className="size-4" />
</CollapsibleTrigger>
<CollapsibleContent keepMounted>
Content
</CollapsibleContent>
</Collapsible>Reference
CollapsibleWrapper
Prop
Type
Related Components
- Base UI Collapsible
- Shadcn Collapsible
- Accordion — multiple collapsible panels grouped together