ComponentProduct
Collapse
Expandable content with height and opacity transitions.
$ npx context-ds add collapseUsage
Use it for
Advanced settings, optional fields and details shown on demand.
Avoid
- Hiding content people need to finish a task
Example
examples/collapse.tsx
"use client";import { useState } from "react";import { Button } from "@/components/ui/button";import { Collapse } from "@/components/ui/collapse";import { Field } from "@/components/ui/field";import { Input } from "@/components/ui/input";export function Product() { const [open, setOpen] = useState(true); return ( <div className="flex w-full max-w-sm flex-col gap-3"> <Button variant="ghost" size="sm" className="self-start" aria-expanded={open} aria-controls="advanced" onClick={() => setOpen((o) => !o)}> {open ? "Hide advanced settings" : "Show advanced settings"} </Button> <Collapse open={open} id="advanced" className="flex flex-col gap-4 pt-1"> <Field label="Timeout" htmlFor="timeout" help="Seconds before a request gives up."> <Input id="timeout" defaultValue="30" /> </Field> </Collapse> </div> );}Source
components/ui/collapse.tsx
"use client";import * as React from "react";import { cn } from "@/lib/utils";/** * Grows from nothing to its content's height and back (300ms ease-out on the grid's rows, with a fade), so whatever * sits below glides rather than jumps. Stays mounted while closed (inert, so nothing inside takes focus), and clips only * until it has fully opened, so menus inside can still spill out of it. Reduced motion opens and closes at once. */function Collapse({ open, id, className, children }: { open: boolean; id?: string; className?: string; children: React.ReactNode }) { const [isFullyOpen, setFullyOpen] = React.useState(open); if (!open && isFullyOpen) setFullyOpen(false); const handleTransitionEnd = (event: React.TransitionEvent<HTMLDivElement>) => { if (open && event.target === event.currentTarget && event.propertyName === "grid-template-rows") setFullyOpen(true); }; return ( <div id={id} data-slot="collapse" data-state={open ? "open" : "closed"} inert={!open} onTransitionEnd={handleTransitionEnd} className={cn("grid transition-[grid-template-rows,opacity] duration-300 ease-out motion-reduce:transition-none", open ? "grid-rows-[1fr] opacity-100" : "grid-rows-[0fr] opacity-0")} > <div className={cn("min-h-0", !isFullyOpen && "overflow-hidden", open && "motion-reduce:overflow-visible")}> <div className={className}>{children}</div> </div> </div> );}export { Collapse };Reference: src/components/dashboard-ui/collapse.tsx.