Components
Navigation
Collapsible

Collapsible

Collapsible is a convenience wrapper for the Radix Collapsible primitive. It keeps trigger and content composition concise while still supporting controlled or uncontrolled state.

Installation

1. Install dependencies:

npm install @radix-ui/react-collapsible

2. Install the Collapsible primitive via shadcn:

npx shadcn@latest add collapsible

3. Copy the wrapper source:

4. Update import paths to match your project structure.


Usage

import { CollapsibleWrapper as Collapsible } from "@hoag/ui/collapsible-wrapper";
import { Button } from "@hoag/ui/components/button";
 
export default function Demo() {
  return (
    <Collapsible trigger={<Button variant="outline">Toggle Details</Button>}>
      <div>Hidden content goes here.</div>
    </Collapsible>
  );
}

Default

Added improved wrappers for overlays, docs navigation updates, and UX polishing.

<Collapsible
  trigger={<Button variant="outline">Toggle release notes</Button>}
  defaultOpen
  contentContainerClassName="pt-2"
>
  <div className="rounded-md border p-3 text-sm text-muted-foreground">
    Added improved wrappers for overlays, docs navigation updates, and UX
    polishing.
  </div>
</Collapsible>

Trigger Modes

triggerAsChild (default)

Use this mode when your trigger is already a button-like component.

<Collapsible trigger={<Button variant="secondary">Show changelog</Button>}>
  <div>Version 2.1 introduced keyboard navigation improvements.</div>
</Collapsible>

Native trigger button (triggerAsChild={false})

Use this mode when you want the wrapper to render its own trigger button.

<Collapsible
  trigger="Toggle details"
  triggerAsChild={false}
  triggerClassName="rounded-md border px-3 py-2 text-sm"
>
  <div>The trigger is rendered by CollapsibleTrigger.</div>
</Collapsible>

Disabled

<Collapsible trigger={<Button variant="outline">Cannot open</Button>} disabled>
  <div>This content cannot be expanded.</div>
</Collapsible>

Controlled Mode

Use controlled mode when open state depends on parent state or app logic.

import { useState } from "react";
 
export default function ControlledExample() {
  const [open, setOpen] = useState(false);
 
  return (
    <Collapsible
      open={open}
      onOpenChange={setOpen}
      trigger={
        <Button variant="secondary">{open ? "Hide" : "Show"} details</Button>
      }
    >
      <div className="rounded-md border p-3">
        This content is controlled by parent state.
      </div>
    </Collapsible>
  );
}

Tips

  • Keep triggerAsChild enabled when using Button, Link, or other custom trigger components.
  • Use contentContainerClassName for animating/spacing the collapsible region.
  • Use contentClassName for styling the inner content wrapper.
  • Use controlled mode (open + onOpenChange) when syncing UI state with URL/query/form logic.

API Reference

Props

PropTypeDefaultDescription
classNamestringClass for root Collapsible
triggerReactNodeTrigger element
triggerAsChildbooleantrueRender trigger as child
triggerClassNamestringTrigger class override
contentContainerClassNamestringClass for CollapsibleContent
contentClassNamestringClass for inner content wrapper
openbooleanControlled open state
onOpenChange(open: boolean) => voidOpen state callback
defaultOpenbooleanInitial open state
disabledbooleanfalseDisable interactions
childrenReactNodeContent rendered inside collapsible area

MIT 2026 © @hoag/ui