Collapsible
freeCollapsible keeps optional content reachable without expanding the page by default.
import Collapsible from '@/components/ui/Collapsible';
export default function UsageDemo() {
return (
<div className="w-full max-w-md">
<Collapsible className="rounded-lg border bg-card">
<Collapsible.Trigger>Shipping details</Collapsible.Trigger>
<Collapsible.Content className="px-4 pb-4">
<p className="text-muted-foreground">
Orders are processed within one business day and ship with tracked
delivery. You will receive a confirmation email once the parcel
leaves the warehouse.
</p>
</Collapsible.Content>
</Collapsible>
</div>
);
}Installation
Add this component with the NateUI CLI.
npx nateui@latest add CollapsibleExamples
Basic
Collapsible starts closed. Set defaultOpen to render it expanded on first paint while keeping it uncontrolled.
This region is expanded on first render and can still be collapsed.
import Collapsible from '@/components/ui/Collapsible';
export default function BasicDemo() {
return (
<div className="grid w-full gap-4">
<Collapsible className="rounded-lg border bg-card">
<Collapsible.Trigger>Closed by default</Collapsible.Trigger>
<Collapsible.Content className="px-4 pb-4">
<p className="text-muted-foreground">
This region is hidden until the trigger is activated.
</p>
</Collapsible.Content>
</Collapsible>
<Collapsible
defaultOpen
className="rounded-lg border bg-card"
>
<Collapsible.Trigger>Open by default</Collapsible.Trigger>
<Collapsible.Content className="px-4 pb-4">
<p className="text-muted-foreground">
This region is expanded on first render and can still be collapsed.
</p>
</Collapsible.Content>
</Collapsible>
</div>
);
}Controlled
Pass open and onOpenChange to drive collapsible state from outside. Here, two external buttons step through a list of sections while a label reflects which one is currently expanded.
Expanded: setup-guide
Install the CLI, scaffold a starter, and connect your first component to a live project.
import { useState } from 'react';
import Button from '@/components/ui/Button';
import Collapsible from '@/components/ui/Collapsible';
import { ChevronUp, ChevronDown } from '@/components/ui/Icons';
const sections = [
{
key: 'setup-guide',
title: 'Setup guide',
description:
'Install the CLI, scaffold a starter, and connect your first component to a live project.',
},
{
key: 'api-reference',
title: 'API reference',
description:
'Every prop, type, and default for each component, generated from the source you install.',
},
{
key: 'troubleshooting',
title: 'Troubleshooting',
description:
'Fixes for the errors that come up most often during install, build, and updates.',
},
];
export default function ControlledDemo() {
const [activeIndex, setActiveIndex] = useState(0);
const active = sections[activeIndex];
return (
<div className="w-full max-w-md">
<div className="mb-2 flex items-center justify-between">
<p className="text-muted-foreground ps-4">
Expanded: <span className="font-medium text-foreground">{active.key}</span>
</p>
<div className="flex items-center gap-2">
<Button
size="sm"
variant="subtle"
disabled={activeIndex === 0}
onClick={() => setActiveIndex((index) => index - 1)}
icon={<ChevronUp />}
aria-label="Expand previous section"
/>
<Button
size="sm"
variant="subtle"
disabled={activeIndex === sections.length - 1}
onClick={() => setActiveIndex((index) => index + 1)}
icon={<ChevronDown />}
aria-label="Expand next section"
/>
</div>
</div>
<div className="divide-y divide-border">
{sections.map((section, index) => (
<Collapsible
key={section.key}
open={index === activeIndex}
onOpenChange={(next) => next && setActiveIndex(index)}
>
<Collapsible.Trigger>{section.title}</Collapsible.Trigger>
<Collapsible.Content className="px-4 pb-4">
<p className="text-muted-foreground">
{section.description}
</p>
</Collapsible.Content>
</Collapsible>
))}
</div>
</div>
);
}Accordion
Drive several collapsibles from one piece of state so only a single panel is open at a time — the common FAQ pattern.
Run the add command from the Installation section. The CLI copies the source into your project and installs any required dependencies.
import { useState } from 'react';
import Collapsible from '@/components/ui/Collapsible';
const faqs = [
{
question: 'How do I install a component?',
answer:
'Run the add command from the Installation section. The CLI copies the source into your project and installs any required dependencies.',
},
{
question: 'Can I use it without a provider?',
answer:
'Yes. Every component falls back to sensible defaults, so it renders standalone. Mount a provider only when you want to theme across the whole app.',
},
{
question: 'Is the source mine to edit?',
answer:
'The files land in your codebase, so you can change the markup, styles, and behavior to fit your product.',
},
];
export default function AccordionDemo() {
const [openIndex, setOpenIndex] = useState<number | null>(0);
return (
<div className="grid w-full max-w-md gap-2">
{faqs.map((faq, index) => (
<Collapsible
key={faq.question}
open={openIndex === index}
onOpenChange={(next) => setOpenIndex(next ? index : null)}
className="rounded-lg border bg-card"
>
<Collapsible.Trigger>{faq.question}</Collapsible.Trigger>
<Collapsible.Content className="px-4 pb-4">
<p className="text-muted-foreground">{faq.answer}</p>
</Collapsible.Content>
</Collapsible>
))}
</div>
);
}Custom trigger
Pass a function to Collapsible.Trigger to render a fully custom trigger. It receives isOpen and toggle, so you can build your own control instead of the default button.
import Collapsible from '@/components/ui/Collapsible';
import Button from '@/components/ui/Button';
import { Plus, Minus } from '@/components/ui/Icons';
export default function CustomTriggerDemo() {
return (
<div className="w-full max-w-md">
<Collapsible>
<Collapsible.Trigger>
{({ isOpen, toggle }) => (
<button
className="py-2 px-4 rounded-lg bg-secondary hover:bg-secondary-active w-full text-start"
onClick={toggle}
>
<span className="inline-flex items-center justify-between gap-2">
{
isOpen ? (
<Minus />
) : (
<Plus />
)
}
<span>Advanced settings</span>
</span>
</button>
)}
</Collapsible.Trigger>
<Collapsible.Content className="py-4">
<p className="text-muted-foreground">
The render-prop trigger gives you full control over the markup while
Collapsible keeps managing the open state and the content animation.
</p>
</Collapsible.Content>
</Collapsible>
</div>
);
}API
Collapsible is a compound component: a root Collapsible that owns the open state, Collapsible.Trigger for the toggle, and Collapsible.Content for the animated region.
Collapsible
Collapsible accepts the props below plus native props for the root div.
| Prop | Description | Type | Default |
|---|---|---|---|
children | Trigger and content parts rendered inside the collapsible. | ReactNode | - |
className | Class names applied to the root div. | string | - |
defaultOpen | Initial open state for uncontrolled usage. | boolean | false |
onOpenChange | Called with the next open state whenever it changes. | (open: boolean) => void | - |
open | Controls the open state. Provide with onOpenChange for controlled usage. | boolean | - |
...nativeDivProps | Native props for the root div. | ComponentProps<'div'> | - |
Collapsible.Trigger
Renders the default toggle button, or a custom trigger when children is a function.
| Prop | Description | Type | Default |
|---|---|---|---|
children | Trigger label, or a render function that receives isOpen and toggle for a custom trigger. | ReactNode | CollapsibleTriggerRender | - |
className | Class names applied to the default trigger button. Ignored when children is a function. | string | - |
Collapsible.Content
Wraps the region that expands and collapses. Accepts the props below plus native props for the div.
| Prop | Description | Type | Default |
|---|---|---|---|
children | Content revealed when the collapsible is open. | ReactNode | - |
className | Class names applied to the animated content wrapper. | string | - |
defaultOverflowHidden | Applies overflow-hidden so content is clipped during the height animation. | boolean | true |
...nativeDivProps | Native props for the content div. | ComponentProps<'div'> | - |
Types
type CollapsibleTriggerRender = (props: {
isOpen: boolean;
toggle: () => void;
}) => ReactNode;