vera logoVera UI
ComponentsData Display

Collapsible

A lightweight wrapper around @helgadigitals/vera-ui collapsible that provides an accessible and flexible way to show and hide content in your application.

Features

  • Accessible — Built on Radix UI with proper ARIA attributes and keyboard navigation
  • Flexible — Works with both controlled and uncontrolled state
  • Composable — Use asChild to render custom trigger elements
  • Customizable — Forward all props including className, styles, and event handlers
  • Lightweight — Minimal wrapper that doesn't add unnecessary complexity

Installation

pnpm add @helgadigitals/vera-ui
npm install @helgadigitals/vera-ui
yarn add @helgadigitals/vera-ui

Usage

Basic Usage

This is the collapsible content area.

components/data-display-examples/collapsible-examples.tsxBasicCollapsibleExample()✓ Auto-extracted
import React, { useState } from "react";import { ChevronDown, ChevronUp, Plus, Minus, Settings } from "lucide-react";import {  Collapsible,  CollapsibleTrigger,  CollapsibleContent,} from "@helgadigitals/vera-ui";function BasicCollapsibleExample() {  const [isOpen, setIsOpen] = useState(true);  return (    <div className="max-w-xl">      <Collapsible        open={isOpen}        onOpenChange={(open) => {          setIsOpen(open);        }}      >        <CollapsibleTrigger asChild>          {/* add an onClick so we can see if the button receives the click */}          <button            onClick={() => {}}            className="w-full flex items-center justify-between px-4 py-3 text-left border rounded  transition-colors"          >            <span className="font-medium">Show details</span>            {isOpen ? (              <ChevronUp className="h-4 w-4" aria-hidden />            ) : (              <ChevronDown className="h-4 w-4" aria-hidden />            )}          </button>        </CollapsibleTrigger>        <CollapsibleContent>          <div className="px-4 py-3 text-sm border border-t-0 rounded-b ">            <p className="mb-2">This is the collapsible content area.</p>          </div>        </CollapsibleContent>      </Collapsible>    </div>  );}

Examples

Open by default

This collapsible is open by default, useful for important content that users should see immediately but can dismiss if needed.

Email notifications
Marketing updates
components/data-display-examples/collapsible-examples.tsxOpenByDefaultExample()✓ Auto-extracted
import React, { useState } from "react";import { ChevronDown, ChevronUp, Plus, Minus, Settings } from "lucide-react";import {  Collapsible,  CollapsibleTrigger,  CollapsibleContent,} from "@helgadigitals/vera-ui";function OpenByDefaultExample() {  const [isOpen, setIsOpen] = useState(true);  return (    <div className="max-w-xl">      <Collapsible open={isOpen} onOpenChange={setIsOpen}>        <CollapsibleTrigger asChild>          <button className="w-full flex items-center justify-between px-4 py-3 text-left border rounded  transition-colors">            <span className="font-medium">User Profile Settings</span>            {isOpen ? (              <Minus className="h-4 w-4" aria-hidden />            ) : (              <Plus className="h-4 w-4" aria-hidden />            )}          </button>        </CollapsibleTrigger>        <CollapsibleContent>          <div className="px-4 py-3 text-sm border border-t-0 rounded-b space-y-3">            <p className="text-gray-600">              This collapsible is open by default, useful for important content              that users should see immediately but can dismiss if needed.            </p>            <div className="space-y-2">              <div className="flex items-center justify-between p-2  rounded border">                <span>Email notifications</span>                <input type="checkbox" defaultChecked />              </div>              <div className="flex items-center justify-between p-2  rounded border">                <span>Marketing updates</span>                <input type="checkbox" />              </div>            </div>          </div>        </CollapsibleContent>      </Collapsible>    </div>  );}

Accordion (only one open)

A Collapsible is a UI component that allows you to show and hide content. It's built as a wrapper around Radix UI's Collapsible primitive, providing a simple way to manage expandable sections in your interface.
components/data-display-examples/collapsible-examples.tsxAccordionExample()✓ Auto-extracted
import React, { useState } from "react";import { ChevronDown, ChevronUp, Plus, Minus, Settings } from "lucide-react";import {  Collapsible,  CollapsibleTrigger,  CollapsibleContent,} from "@helgadigitals/vera-ui";function AccordionExample() {  const items = [    {      id: "q1",      question: "What is a Collapsible component?",      answer:        "A Collapsible is a UI component that allows you to show and hide content. It's built as a wrapper around Radix UI's Collapsible primitive, providing a simple way to manage expandable sections in your interface.",    },    {      id: "q2",      question: "How do I implement accordion behavior?",      answer:        "Use the controlled API with open and onOpenChange props. Maintain a single openId state and only allow one item to be open at a time by setting open={openId === item.id} for each collapsible.",    },    {      id: "q3",      question: "Can I customize the animations?",      answer:        "Yes! The CollapsibleContent component can be styled with CSS transitions. You can add custom animations for height, opacity, or use CSS animation libraries to create smooth expand/collapse effects.",    },  ];  const [openId, setOpenId] = useState<string | null>("q1");  return (    <div className="max-w-2xl space-y-2">      {items.map((item) => {        const isOpen = openId === item.id;        return (          <Collapsible            key={item.id}            open={isOpen}            onOpenChange={(open) => setOpenId(open ? item.id : null)}          >            <CollapsibleTrigger asChild>              <button className="w-full text-left px-4 py-3 border rounded  transition-colors flex items-center justify-between">                <span className="font-medium">{item.question}</span>                <ChevronDown                  className={`h-4 w-4 transition-transform ${                    isOpen ? "rotate-180" : ""                  }`}                  aria-hidden                />              </button>            </CollapsibleTrigger>            <CollapsibleContent>              <div className="px-4 py-3 border border-t-0 rounded-b  text-sm text-gray-700">                {item.answer}              </div>            </CollapsibleContent>          </Collapsible>        );      })}    </div>  );}

Progressive form (advanced settings)

components/data-display-examples/collapsible-examples.tsxProgressiveFormExample()✓ Auto-extracted
import React, { useState } from "react";import { ChevronDown, ChevronUp, Plus, Minus, Settings } from "lucide-react";import {  Collapsible,  CollapsibleTrigger,  CollapsibleContent,} from "@helgadigitals/vera-ui";function ProgressiveFormExample() {  const [isOpen, setIsOpen] = useState(false);  return (    <div className="max-w-xl space-y-4">      <div className="space-y-3">        <div>          <label className="block text-sm font-medium mb-1">Name *</label>          <input            type="text"            className="w-full px-3 py-2 border rounded"            placeholder="Enter your name"          />        </div>        <div>          <label className="block text-sm font-medium mb-1">Email *</label>          <input            type="email"            className="w-full px-3 py-2 border rounded"            placeholder="Enter your email"          />        </div>      </div>      <Collapsible open={isOpen} onOpenChange={setIsOpen}>        <CollapsibleTrigger asChild>          <button className="flex items-center gap-2 px-3 py-2 border rounded  transition-colors text-sm">            <Settings className="h-4 w-4" />            <span>Advanced settings</span>            {isOpen ? (              <ChevronUp className="h-3 w-3" aria-hidden />            ) : (              <ChevronDown className="h-3 w-3" aria-hidden />            )}          </button>        </CollapsibleTrigger>        <CollapsibleContent>          <div className="space-y-3 p-4 border border-t-0 rounded-b ">            <div>              <label className="flex items-center gap-2 text-sm">                <input type="checkbox" />                <span>Enable two-factor authentication</span>              </label>            </div>            <div>              <label className="flex items-center gap-2 text-sm">                <input type="checkbox" />                <span>Subscribe to newsletter</span>              </label>            </div>            <div>              <label className="block text-sm font-medium mb-1">                Preferred language              </label>              <select className="w-full px-3 py-2 border rounded text-sm">                <option>English</option>                <option>Spanish</option>                <option>French</option>              </select>            </div>          </div>        </CollapsibleContent>      </Collapsible>      <button className="w-full px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors">        Submit      </button>    </div>  );}

Table / row details

Project
Status
Date
Project Alpha
Active
2024-11-15
Project Beta
Planning
2024-12-01
components/data-display-examples/collapsible-examples.tsxTableRowExample()✓ Auto-extracted
import React, { useState } from "react";import { ChevronDown, ChevronUp, Plus, Minus, Settings } from "lucide-react";import {  Collapsible,  CollapsibleTrigger,  CollapsibleContent,} from "@helgadigitals/vera-ui";function TableRowExample() {  const rows = [    {      id: 1,      name: "Project Alpha",      status: "Active",      date: "2024-11-15",      details: "Customer-facing dashboard with real-time analytics",      team: "Engineering Team A",      budget: "$45,000",    },    {      id: 2,      name: "Project Beta",      status: "Planning",      date: "2024-12-01",      details: "Internal tool for workflow automation",      team: "Engineering Team B",      budget: "$28,000",    },  ];  // State must be outside the map - manage which row is open  const [openRowId, setOpenRowId] = useState<number | null>(null);  return (    <div className="border rounded overflow-hidden">      <div className="bg-background px-4 py-2 font-medium text-sm border-b">        <div className="grid grid-cols-4 gap-4">          <div>Project</div>          <div>Status</div>          <div>Date</div>          <div></div>        </div>      </div>      {rows.map((row) => {        const isOpen = openRowId === row.id;        return (          <div key={row.id} className="border-b last:border-b-0">            <div className="px-4 py-3 grid grid-cols-4 gap-4 items-center ">              <div className="font-medium">{row.name}</div>              <div>                <span                  className={`inline-block px-2 py-1 text-xs rounded ${                    row.status === "Active"                      ? "bg-green-100 text-green-800"                      : "bg-yellow-100 text-yellow-800"                  }`}                >                  {row.status}                </span>              </div>              <div className="text-sm text-gray-600">{row.date}</div>              <div className="text-right">                <Collapsible                  open={isOpen}                  onOpenChange={(open) => setOpenRowId(open ? row.id : null)}                >                  <CollapsibleTrigger asChild>                    <button className="px-3 py-1 text-sm border rounded  transition-colors">                      {isOpen ? "Hide" : "Details"}                    </button>                  </CollapsibleTrigger>                  <CollapsibleContent>                    <div className="mt-3 p-3  rounded text-sm space-y-2">                      <div>                        <span className="font-medium">Description:</span>{" "}                        {row.details}                      </div>                      <div>                        <span className="font-medium">Team:</span> {row.team}                      </div>                      <div>                        <span className="font-medium">Budget:</span>{" "}                        {row.budget}                      </div>                    </div>                  </CollapsibleContent>                </Collapsible>              </div>            </div>          </div>        );      })}    </div>  );}

Quick usage

import {
  Collapsible,
  CollapsibleTrigger,
  CollapsibleContent,
} from "@helgadigitals/vera-ui";

<Collapsible defaultOpen={false}>
  <CollapsibleTrigger asChild>
    <button>Show details</button>
  </CollapsibleTrigger>

  <CollapsibleContent>
    <div>{/* your content */}</div>
  </CollapsibleContent>
</Collapsible>;

API Reference

Prop

Type

Use Cases

Content display

Use Collapsible to hide optional or verbose content and reduce visual noise. Great for article excerpts, product details, and user bios.

FAQ / Accordion

Render a list of question/answer items. Use the controlled API (open + onOpenChange) to implement accordion behaviour (only one item open at a time).

Progressive disclosure

Hide advanced form controls or secondary settings behind a collapsible to simplify long forms.

Table / list item details

Reveal row-level details or actions without navigating away from the list or table view.

Accessibility & Best Practices

  • Prefer asChild when you need the trigger to be a semantic button or link.
  • Use the controlled API for accordions to coordinate focus and keyboard navigation.
  • Avoid very large content blocks inside a collapsible; paginate or lazy-load if needed.
  • Ensure trigger buttons have descriptive labels for screen readers.
  • Use proper heading hierarchy within collapsible content.
  • Accordion - Purpose-built accordion component
  • Card - Container for grouped content
  • Tabs - Alternative navigation pattern