Auto height
FreeA container that follows its content's height, so a wizard step, a validation summary or a details panel grows and shrinks instead of snapping. The content changes at once.
pnpm dlx shadcn@latest add https://caliperui.site/r/auto-height.jsonWhen to use #
Use it for
- Content that changes length after an action: a step, an error list, a toggle that adds fields.
- Cards whose body swaps between states.
Skip it when
- Content that changes while people read it, such as streaming text.
- Accordions; the accordion handles its own height.
Installation #
pnpm dlx shadcn@latest add https://caliperui.site/r/auto-height.json
Adds its 4 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage #
This is the code behind the preview above.
import { Check } from "lucide-react" import { useState } from "react" import { AutoHeight } from "@/components/ui/auto-height" import { Button } from "@/components/ui/button" import { Field } from "@/components/ui/field" import { Input } from "@/components/ui/input" import { SegmentedControl } from "@/components/ui/segmented-control" export function AutoHeightDemo() { const [kind, setKind] = useState("card"); const [errors, setErrors] = useState<string[]>([]); return ( <div className="grid w-full max-w-md gap-4"> <SegmentedControl label="Payment method" value={kind} onValueChange={setKind} options={[{ value: "card", label: "Card" }, { value: "bank", label: "Bank transfer" }, { value: "invoice", label: "Invoice" }]} /> <AutoHeight className="rounded-surface border border-border bg-surface"> <div className="grid gap-3 p-4"> {kind === "card" && ( <> <Field label="Card number">{(field) => <Input {...field} inputMode="numeric" placeholder="4242 4242 4242 4242" />}</Field> <div className="grid grid-cols-2 gap-3"> <Field label="Expiry">{(field) => <Input {...field} placeholder="MM / YY" />}</Field> <Field label="CVC">{(field) => <Input {...field} inputMode="numeric" placeholder="123" />}</Field> </div> </> )} {kind === "bank" && <Field label="IBAN">{(field) => <Input {...field} placeholder="DE89 3704 0044 0532 0130 00" />}</Field>} {kind === "invoice" && <p className="text-sm text-muted-foreground">We email an invoice on the 1st, payable within 30 days.</p>} {errors.length > 0 && ( <ul className="grid gap-1 rounded-control bg-destructive-subtle px-3 py-2 text-sm text-destructive" role="alert"> {errors.map((e) => <li key={e}>{e}</li>)} </ul> )} <div className="flex gap-2"> <Button size="sm" onClick={() => setErrors(kind === "invoice" ? [] : ["Enter the full number.", "Check the expiry date."].slice(0, kind === "card" ? 2 : 1))}>Check details</Button> {errors.length > 0 && <Button size="sm" variant="ghost" onClick={() => setErrors([])}>Clear</Button>} </div> </div> </AutoHeight> </div> ); }
API reference #
AutoHeight
component
No props of its own; it accepts the native props of its root element.
Accessibility #
It is built on native elements, so roles and focus come from the browser.
No custom key handling; Tab and the native activation keys do the work.
Motion #
Transitions use the shared duration and easing tokens. Under prefers-reduced-motion those tokens collapse to instant and travel distances go to zero, so state changes still happen without movement.