CollapsiblePanel
SM · 950B gzip budgetSingle collapsible content panel with smooth animation.
- Category
- Media & layout
- Budget
- sm, single-purpose control
- External runtime
- React / React DOM only
axe fixtureSSR rendersource scanbundle budget
Preview
Live fixtureTry the component here. Locale-aware formatting and direction follow the selected language; example content and labels are not automatically translated. Overlay demos use interactive launchers.
Install
npx shadcn@latest add https://gear5-ui.vercel.app/r/collapsible-panel.jsonCopies the source into your project.
New here? Set up Tailwind and import aliases first →Source & props
Edit on GitHub ↗Prop types and inline documentation are included below. This is repository source; the installer rewrites shared imports for your project.
"use client";
import React, { useCallback, useRef, useState } from "react";
export interface CollapsiblePanelProps {
trigger: React.ReactNode;
children: React.ReactNode;
defaultOpen?: boolean;
className?: string;
triggerClassName?: string;
contentClassName?: string;
}
export function CollapsiblePanel({
trigger,
children,
defaultOpen = false,
className = "",
triggerClassName = "",
contentClassName = "",
}: CollapsiblePanelProps) {
const [isOpen, setIsOpen] = useState(defaultOpen);
const contentRef = useRef<HTMLDivElement>(null);
const [height, setHeight] = useState<number | undefined>(
defaultOpen ? undefined : 0
);
const toggle = useCallback(() => {
setIsOpen((prev) => {
const next = !prev;
if (next) {
const el = contentRef.current;
if (el) {
setHeight(el.scrollHeight);
requestAnimationFrame(() => setHeight(undefined));
}
} else {
const el = contentRef.current;
if (el) {
setHeight(el.scrollHeight);
requestAnimationFrame(() => setHeight(0));
}
}
return next;
});
}, []);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
toggle();
}
},
[toggle]
);
return (
<div className={className}>
<button
type="button"
aria-expanded={isOpen}
className={triggerClassName}
onClick={toggle}
onKeyDown={handleKeyDown}
style={{
display: "flex",
alignItems: "center",
gap: "0.5rem",
background: "none",
border: "none",
cursor: "pointer",
fontWeight: 500,
padding: "0.5rem 0",
}}
>
<span
aria-hidden="true"
style={{
display: "inline-block",
transition: "transform 0.2s ease",
transform: isOpen ? "rotate(90deg)" : "rotate(0deg)",
}}
>
▸
</span>
{trigger}
</button>
<div
ref={contentRef}
role="region"
className={contentClassName}
style={{
display: "grid",
gridTemplateRows: isOpen ? "1fr" : "0fr",
transition: "grid-template-rows 0.3s ease",
}}
>
<div style={{ overflow: "hidden" }}>
<div style={{ paddingBlock: isOpen ? "0.5rem" : 0 }}>
{children}
</div>
</div>
</div>
</div>
);
}
What CI checks
Quality contract- Bundled, minified and gzipped against its tier budget
- Audited by axe in the state previewed above
- Rendered through react-dom/server with no browser globals
- Scanned for network calls, dangerous sinks, and unguarded animation