Collapsible

A collapsible panel controlled by a button.

Order #4189

StatusShipped
"use client"

import * as React from "react"
import { Collapsible as CollapsiblePrimitive } from "@animate-ui/react/primitives/base-ui/collapsible"

import { UnfoldMoreIcon } from "@hugeicons/core-free-icons"
import { HugeiconsIcon } from "@hugeicons/react"

import { Button } from "@/components/ui/button"

function Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) {
  return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
}

function CollapsibleTrigger({ ...props }: CollapsiblePrimitive.Trigger.Props) {
  return (
    <CollapsiblePrimitive.Trigger data-slot="collapsible-trigger" {...props} />
  )
}

function CollapsibleContent({ ...props }: CollapsiblePrimitive.Panel.Props) {
  return (
    <CollapsiblePrimitive.Panel data-slot="collapsible-content" {...props} />
  )
}

export { Collapsible, CollapsibleTrigger, CollapsibleContent }

export default function CollapsibleDemo() {
  const [isOpen, setIsOpen] = React.useState(false)

  return (
    <Collapsible
      open={isOpen}
      onOpenChange={setIsOpen}
      className="flex w-[350px] flex-col gap-2"
    >
      <div className="flex items-center justify-between gap-4 px-4">
        <h4 className="text-sm font-semibold">Order #4189</h4>
        <CollapsibleTrigger
          render={<Button variant="ghost" size="icon" className="size-8" />}
        >
          <HugeiconsIcon icon={UnfoldMoreIcon} strokeWidth={2} />
          <span className="sr-only">Toggle details</span>
        </CollapsibleTrigger>
      </div>
      <div className="flex items-center justify-between rounded-md border px-4 py-2 text-sm">
        <span className="text-muted-foreground">Status</span>
        <span className="font-medium">Shipped</span>
      </div>
      <CollapsibleContent className="flex flex-col gap-2">
        <div className="rounded-md border px-4 py-2 text-sm">
          <p className="font-medium">Shipping address</p>
          <p className="text-muted-foreground">100 Market St, San Francisco</p>
        </div>
        <div className="rounded-md border px-4 py-2 text-sm">
          <p className="font-medium">Items</p>
          <p className="text-muted-foreground">2x Studio Headphones</p>
        </div>
      </CollapsibleContent>
    </Collapsible>
  )
}

Installation

Install the packages

npm install @animate-ui/react motion
pnpm add @animate-ui/react motion
yarn add @animate-ui/react motion
bun add @animate-ui/react motion

Add the shadcn/ui component

npx shadcn@latest add collapsible
pnpm dlx shadcn@latest add collapsible
yarn dlx shadcn@latest add collapsible
bun x shadcn@latest add collapsible

Animate it

The CLI finds your shadcn/ui components through components.json, swaps their primitive imports for Animate UI's and removes the CSS animation classes Motion replaces.

npx @animate-ui/cli@latest animate collapsible
pnpm dlx @animate-ui/cli@latest animate collapsible
yarn dlx @animate-ui/cli@latest animate collapsible
bun x @animate-ui/cli@latest animate collapsible

Usage

Import the primitive from Animate UI instead of Base UI. Everything else stays the same.

import { Collapsible } from '@animate-ui/react/primitives/base-ui/collapsible';

export function Changelog() {
  return (
    <Collapsible.Root>
      <Collapsible.Trigger>Show all changes</Collapsible.Trigger>
      <Collapsible.Panel>
        Drop-in primitives for Radix UI, Base UI and React Aria.
      </Collapsible.Panel>
    </Collapsible.Root>
  );
}

Using the @base-ui/react umbrella import instead? The barrel mirrors it: import { Collapsible } from '@animate-ui/react/primitives/base-ui'.

Animation

  • Panel unfolds like the accordions: its height grows from 0 while the content fades in from a 2px blur, over 350ms on a soft ease-out. While it moves, its bottom edge fades (over 16px, or over the part still hidden): the content never meets what follows it with a hard cut. It folds back the same way, in reverse, then unmounts.
  • Toggling it while it moves retargets from where it is.
  • Open by default (defaultOpen), it animates open on mount (page load), as in v1.
  • Motion animates the panel itself: you don't need Base UI's h-(--collapsible-panel-height) and data-starting-style/data-ending-style CSS (the CLI removes it). --collapsible-panel-height and --collapsible-panel-width read auto.
  • With keepMounted, the closed panel stays in the DOM: it collapses, then gets Base UI's hidden attribute.
  • With hiddenUntilFound, it collapses, then gets hidden="until-found" so find-in-page can reveal it. A panel revealed by find-in-page opens at once, as in Base UI.
  • An onOpenChange that calls eventDetails.cancel() still cancels the change.
  • <AnimateUIProvider tempo> paces both transitions.
  • Under reduced motion, the height changes at once; the fade and the blur still play.

API Reference

Every part takes exactly the props of Base UI's Collapsible. Animate UI adds the animation props below.

Root, Trigger

Base UI props only. They are not animated.

Panel

Base UI props, plus:

PropTypeDefault
transition?
Transition{ duration: 0.35, ease: [0.215, 0.61, 0.355, 1] }
exitTransition?
Transitiontransition

A render element (or function) of your own is made animatable with motion.create.

Built by Skyleen. The source code is available on GitHub.

Last updated: 10/18/2026

On this page