Loading page…
Motion the cursor writes. Magnetic and TiltCard read the pointer and lean toward it; SharedLayoutBg glides one shared pill between rows; MorphingModal morphs a single panel between views; Dock and WheelPicker are the two direct-manipulation surfaces. The two cursor-follow wrappers are decorative, so they stand down entirely on touch; the other four keep working under a finger and only shed their choreography under reduced motion.
Wraps anything and pulls it toward the cursor, sprung on the house mouse physics; leaving the box releases it to origin. strength is the fraction of the cursor's offset from center that the child travels — 0.35 by default. MagneticButton already wraps this, so reach for the bare wrapper only for things that are not buttons.
Barely there — the cargo leans, it does not chase.
Loose in the swell. Reserve for single hero targets.
The wrapper is display:inline-block and animates transform only — it never reflows its neighbours.
import { Magnetic } from "@coldharbor/webui";
<Magnetic strength={0.35}>
<BrandMark />
</Magnetic>--ch-surface--ch-border--ch-fg--ch-fg-muted--ch-primaryPointer position maps to rotateX/rotateY inside a 1000px perspective, with a radial glare tracking the cursor. max is the peak-to-peak sweep in degrees — each axis reaches ±max/2, so max=12 tops out at 6° of lean. glare defaults on. The child owns its own surface; TiltCard only supplies the rounded, clipped frame.
Bitcoin
1.204 517 09
$140,204.11 · +1.6%
Monero
482.140 000
$78,412.90 · −0.4%
±12.5° each way. Past that the perspective starts to read as distortion.
Reserve
0.038 400 00
$4,472.06 · flat
Tilt without the specular wash — for cards that already carry a gradient. The glare layer reads --ch-fg, so it inverts with the theme.
import { TiltCard } from "@coldharbor/webui";
<TiltCard max={12} glare>
<AccountCard account={account} />
</TiltCard>--ch-surface--ch-border--ch-fg--ch-fg-muted--ch-primaryOne pill, shared by every row through a single layoutId, so hovering a new row glides the pill there instead of cross-fading two of them. It is the primitive under the animated sidebar's active-item highlight — AnimatedSidebarMenu is a SharedLayoutBg with as=ul, inset=0 and its own pill class.
The pill overhangs each row by 20px on both sides.
inset=0 and a fixed-height pill container — exactly how AnimatedSidebarMenu configures it.
Where the pill lands. Nothing is painted until a row is entered, and mouse-leave clears it entirely.
import { SharedLayoutBg } from "@coldharbor/webui";
<SharedLayoutBg as="ul" inset={0} pillClassName="rounded-xl bg-muted/70">
{routes.map((route) => (
<li key={route.href}>
<Link href={route.href}>{route.title}</Link>
</li>
))}
</SharedLayoutBg>--ch-surface--ch-border--ch-fg--ch-fg-muted--ch-primarysprings.glideThe other modal family. viewId is both the open switch and the content key: a string opens the panel and names the view, null closes it. Swap the string and the surface resizes around the new body while the old one blurs out. Placements, the closed/inert state and the CenterMorphModal comparison are charted on Primitives → Dialog; this specimen is only the multi-view route.
Rendered inside a transformed box so the position:fixed panel is captured in the specimen rather than covering the page.
import { MorphingModal } from "@coldharbor/webui";
const [view, setView] = useState<string | null>(null);
<MorphingModal viewId={view} onClose={() => setView(null)} placement="center">
{view === "signing" ? <Signing /> : <Review />}
</MorphingModal>--ch-bg--ch-surface--ch-border--ch-fg--ch-fg-muted--ch-overlaysprings.unfoldA macOS-style rail: a bordered, blurred bar of fixed-size items with one shared pill marking the active berth. Set active on an item and the pill glides to it on the layout spring. DockItem renders a button when given onClick, otherwise a plain box so its children can carry their own link and accessible name.
The separator is decorative — it splits berths, it is not an item.
With no active item the pill is unmounted; the bar still reads as a bar.
import { Dock, DockItem, DockSeparator } from "@coldharbor/webui";
<Dock size={44}>
<DockItem active aria-label="Portfolio" onClick={() => go("/portfolio")}>
<Wallet size={20} />
</DockItem>
<DockSeparator />
<DockItem aria-label="Settings" onClick={() => go("/settings")}>
<Settings size={20} />
</DockItem>
</Dock>--ch-surface--ch-border--ch-fg--ch-fg-muted--ch-primarysprings.glideAn inertial picker. Rows scroll through a masked window and settle on an integer detent with a small spring overshoot; the selected value is painted crisp in a flat centre band.
defaultValue seeds the drum; onValueChange reports every settle.
6 sat/vB
Taller window, shorter rows — same equal spacing.
Dimmed and pointer-inert; the value still reads.
import { WheelPicker } from "@coldharbor/webui";
<WheelPicker
options={[{ label: "6 sat/vB", value: "6" }]}
value={rate}
onValueChange={setRate}
visibleCount={5}
sound
aria-label="Fee rate"
/>--ch-surface--ch-border--ch-fg--ch-fg-muted--ch-primaryEvery primitive here degrades on purpose. Nothing is disabled — the content survives; only the choreography is struck.
useHoverCapable gates on (hover: hover) and (pointer: fine), so a tap never triggers phantom pull.
These stay usable — reduced motion swaps the choreography, not the control.
const reduce = useReducedMotion();
const canHover = useHoverCapable();
const enabled = !reduce && canHover; // decorative pointer tracking only--ch-motion-duration-fast--ch-motion-duration-baseDock, tilt card and the resting pill on the canvas layer.
Bitcoin
1.204 517 09
$140,204.11 · +1.6%
--ch-surface--ch-border--ch-fg--ch-fg-muted--ch-primary