Skip to content
goodtabs UI
BrowseBrowse components

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

PropTypeDefaultDescription
titleReactNodeThe header text. Clicking it toggles the body.
descriptionReactNodeOne line under the title.
metaReactNodeSomething small on the right of the header, outside the button.
defaultOpenbooleanfalseStart open when uncontrolled.
openbooleanControlled open state.
onOpenChange(open: boolean) => voidCalled when the header is toggled.
childrenReactNodeThe 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