Collapsible card
A card whose header toggles its body, with a turning chevron and a meta slot.
npx shadcn@latest add https://ui.goodtabs.co/r/collapsible-card.json
With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/collapsible-card
Pro
- Plan
- Pro, yearly
- Next invoice
- 12 Nov 2026
- Card
- Visa 4242
A settings card with a description, a meta label and a key value body.
Closed by default
Starts collapsed. The header is one button with aria-expanded.
A stack of three
Independent cards in a column, the FAQ pattern without an accordion.
Usage
import { CollapsibleCard } from "@/components/ui/collapsible-card"
<CollapsibleCard title="Billing" description="Plan and invoices" meta="Pro" defaultOpen>
<p>Pro, yearly. Next invoice 12 Nov.</p>
</CollapsibleCard>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| title | ReactNode | The header text. Clicking it toggles the body. | |
| description | ReactNode | One line under the title. | |
| meta | ReactNode | Something small on the right of the header, outside the button. | |
| defaultOpen | boolean | false | Start open when uncontrolled. |
| open | boolean | Controlled open state. | |
| onOpenChange | (open: boolean) => void | Called when the header is toggled. | |
| children | ReactNode | The body, hidden while closed. |
Source
collapsible-card/collapsible-card.tsx
"use client";
import * as React from "react";
import { ChevronRightIcon } from "lucide-react";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import { cn } from "@/lib/utils";
type CollapsibleCardProps = Omit<React.ComponentProps<typeof Collapsible>, "title"> & {
/** The header text. Clicking it toggles the body. */
title: React.ReactNode;
/** One line under the title. */
description?: React.ReactNode;
/** Something small on the right of the header: a count, a badge, a date. */
meta?: React.ReactNode;
};
/**
* A card whose header toggles its body. The header is one real button with aria-expanded;
* the meta slot sits beside it so it can hold its own controls.
*/
export function CollapsibleCard({
title,
description,
meta,
defaultOpen,
open,
onOpenChange,
className,
children,
...props
}: CollapsibleCardProps) {
return (
<Collapsible
data-slot="collapsible-card"
defaultOpen={defaultOpen}
open={open}
onOpenChange={onOpenChange}
className={cn("surface-shine rounded-lg border bg-card text-card-foreground", className)}
{...props}
>
<div className="flex items-center gap-3 pr-4">
<CollapsibleTrigger
data-slot="collapsible-card-trigger"
className="group/trigger flex min-w-0 flex-1 items-center gap-2.5 rounded-lg px-4 py-3 text-left transition-colors duration-100 ease-out outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50"
>
<ChevronRightIcon
aria-hidden="true"
className="size-4 shrink-0 text-muted-foreground transition-transform duration-150 ease-out group-data-open/trigger:rotate-90"
/>
<span className="flex min-w-0 flex-col gap-0.5">
<span className="text-sm font-medium text-foreground">{title}</span>
{description && <span className="text-sm text-muted-foreground">{description}</span>}
</span>
</CollapsibleTrigger>
{meta !== undefined && meta !== null && (
<div
data-slot="collapsible-card-meta"
className="flex shrink-0 items-center font-mono text-xs text-muted-foreground"
>
{meta}
</div>
)}
</div>
<CollapsibleContent
data-slot="collapsible-card-content"
className="border-t px-4 py-3 text-sm text-muted-foreground"
>
{children}
</CollapsibleContent>
</Collapsible>
);
}
For AI agents
Hand your agent this page, the markdown at /components/collapsible-card/prompt.md, or the Copy AI prompt button above.
When to use
- Settings sections that most people never open
- FAQ lists where each answer opens on its own
- Long detail under a short summary, such as logs or a diff
When not to use
- When only one section should be open at a time, use an accordion
- For content people need at once; hiding it costs a click
- As a page navigation device
Agent notes
- Client component on radix Collapsible: defaultOpen, open and onOpenChange work as in shadcn; pass onOpenChange only from a client component
- The header is one button with aria-expanded; meta renders beside it, not inside it, so it may hold a link or button
- The body is text-sm text-muted-foreground on a hairline; style children to override
- Each card is independent; for one open at a time, control open yourself