Primitives
Collapsible
Accessible disclosure primitive with measured open and close motion.
Composition
Anatomy
- <Collapsible>
- <CollapsibleTrigger />
- <CollapsibleContent />
Installation
First install and activate one skin. Core deliberately contains no visual token defaults.
The Control UI source installs this primitive from src/registry/sources/control-ui/ui/collapsible.tsx.
npx shadcn@latest add https://control-ui.dev/r/collapsible.jsonRaw code
Primary installed source
"use client";import { Collapsible as CollapsiblePrimitive } from "@base-ui/react/collapsible";import { useRender } from "@base-ui/react/use-render";import type { ComponentProps, CSSProperties, ReactNode } from "react";import type { OpenChangeEventDetails, RenderProp } from "@/components/control-ui/control-props";import type { CollapsibleKnobStyle } from "@/components/control-ui/knob-contracts/collapsible-knobs";import { cn } from "@/components/control-ui/lib/cn";export type CollapsibleProps = Omit<ComponentProps<"div">, "onChange"> & { open?: boolean; defaultOpen?: boolean; onOpenChange?: (open: boolean, eventDetails: OpenChangeEventDetails) => void; children?: ReactNode;} & { style?: CSSProperties & CollapsibleKnobStyle };export type CollapsibleTriggerProps = Omit<ComponentProps<"button">, "style"> & { "data-slot"?: string; render?: RenderProp<ComponentProps<"button">, { open: boolean }>; nativeButton?: boolean; style?: CSSProperties & CollapsibleKnobStyle;};export type CollapsibleContentProps = ComponentProps<"div"> & { "data-slot"?: string; keepMounted?: boolean;} & { style?: CSSProperties & CollapsibleKnobStyle };function CollapsibleTriggerElement({ triggerProps, open, render, className, children,}: { triggerProps: ComponentProps<"button"> & { "data-control-ui"?: string; "data-control-family"?: string; "data-slot"?: string }; open: boolean; render: CollapsibleTriggerProps["render"]; className?: string; children: ReactNode;}) { const state = { open }; return useRender({ defaultTagName: "button", render, state, props: { ...triggerProps, "data-control-ui": triggerProps["data-control-ui"] ?? "collapsible", "data-control-family": triggerProps["data-control-family"] ?? "collapsible", "data-slot": triggerProps["data-slot"] ?? "trigger", "data-state": open ? "open" : "closed", className: cn(triggerProps.className, className), children, }, });}export function Collapsible({ className, ...props }: CollapsibleProps) { return ( <CollapsiblePrimitive.Root className={className} render={(renderProps, state) => ( <div data-control-ui="collapsible" data-control-family="collapsible" data-slot="root" {...renderProps} data-state={state.open ? "open" : "closed"} className={renderProps.className} /> )} {...props} /> );}export function CollapsibleTrigger({ render, className, children, ...props }: CollapsibleTriggerProps) { return ( <CollapsiblePrimitive.Trigger data-control-ui="collapsible" data-control-family="collapsible" data-slot="trigger" className="cursor-pointer" {...props} render={(triggerProps, state) => ( <CollapsibleTriggerElement triggerProps={triggerProps} open={state.open} render={render} className={className}> {children} </CollapsibleTriggerElement> )} /> );}export function CollapsibleContent({ className, children, ...props }: CollapsibleContentProps) { return ( <CollapsiblePrimitive.Panel {...props} render={(renderProps, state) => ( <div data-control-ui="collapsible" data-control-family="collapsible" data-slot="content" {...renderProps} data-state={state.open ? "open" : "closed"} className={renderProps.className} /> )} > <div className={className}>{children}</div> </CollapsiblePrimitive.Panel> );}Knobs
Typed custom properties the recipe paints with. Set one on the root — style, a utility class, or a skin — and every slot inherits it.
--cui-collapsible-* · 2 knobsHow the cascade resolves--cui-collapsible-trigger-radius<length-percentage>var(--radius-control)--cui-collapsible-trigger-hover-background<color>var(--hover-fill)