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-uinpm install @helgadigitals/vera-uiyarn add @helgadigitals/vera-uiUsage
Basic Usage
This is the collapsible content area.
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.
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)
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)
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
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
asChildwhen 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.
Related Components
Card
A versatile container component for grouping related content and actions. Cards provide a structured way to display information with clear boundaries and hierarchy.
DataTable
A feature-rich data table component that provides advanced functionality for displaying and interacting with tabular data. Includes built-in support for sorting, filtering, pagination, row selection, custom actions, and responsive design.