Morph surface
FreeA floating surface that grows out of the element that opened it.
pnpm dlx shadcn@latest add https://caliperui.site/r/morph-surface.jsonWhen to use #
Use it for
- Building your own menus, pickers and cards that should feel attached to their trigger.
- You need the shared overlay frost and edge.
Skip it when
- A standard menu or popover already fits.
Installation #
terminal
pnpm dlx shadcn@latest add https://caliperui.site/r/morph-surface.json
Adds its 2 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.
example.tsx
import { useRef, useState } from "react" import { Button } from "@/components/ui/button" import { MorphSurface } from "@/components/ui/morph-surface" export function OriginSurface() { const origin = useRef<HTMLButtonElement>(null); const [open, setOpen] = useState(false); return ( <div className="pb-24"> <Button ref={origin} variant="secondary" onClick={() => setOpen((value) => !value)}> {open ? "Close" : "Open surface"} </Button> <MorphSurface open={open} originRef={origin} width={240}> <p className="p-4 text-sm">This surface grows from the button that opened it.</p> </MorphSurface> </div> ); }
API reference #
MorphSurface
component
Also accepts the native props of its root element and a ref.
PropTypeDefaultDescription
open *boolean—
originRef *RefObject<HTMLElement | null>—
placement"center" | "below""below""below" opens under the origin; "center" opens in the middle of the screen near the top.
widthnumber | "origin"280Pixels, or "origin" to match the origin's width at least.
gapnumber6
radiusnumber12
onExited(() => void)—
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 #
It checks prefers-reduced-motion itself and settles immediately instead of travelling. Every state is still reachable.