Components
Boop
Gives an icon a small springy nudge when the control around it is hovered or focused, then lets it settle back on its own even if the pointer stays: a bell rings, a cog turns, an arrow leans forward.
pnpm dlx shadcn@latest add @caliper/boopWhen to use
Use it for
- Icons in buttons and links that benefit from a moment of character.
- Navigation and toolbars on friendly products.
Skip it when
- Every icon on a screen; pick the few that carry a verb.
- Icons that convey state; movement should not change meaning.
Decisions
Why it is built this way, so you know what to keep when you change your copy.
Behaviour
- A push, not a hold
- The icon moves for a moment and springs home while the pointer stays, so it acknowledges arrival without staying off balance.
- The parent triggers it
- It listens to its parent's hover and focus, so the whole button is the target and keyboard focus boops too.
Installation
pnpm dlx shadcn@latest add @caliper/boop
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.
import { ArrowRight, Bell, Heart, Settings } from "lucide-react" import { type ReactNode } from "react" import { Boop } from "@/components/ui/boop" import { Button } from "@/components/ui/button" function Panel({ title, context, className = "max-w-md", children }: { title: string; context: string; className?: string; children: ReactNode }) { return ( <section className={`grid w-full gap-5 rounded-overlay border border-border bg-background p-6 shadow-resting max-sm:p-4 ${className}`}> <div className="grid gap-1"> <p className="font-medium">{title}</p> <p className="text-sm text-muted-foreground">{context}</p> </div> {children} </section> ); } export function BoopDemo() { return ( <Panel title="Workspace" context="Hover or tab to each button: the icon gets a nudge, then settles." className="max-w-md"> <div className="flex flex-wrap gap-2"> <Button variant="secondary"> <Boop rotate={18} timing={150}> <Bell aria-hidden className="size-4" /> </Boop> Notify me </Button> <Button variant="secondary"> <Boop rotate={60} timing={220}> <Settings aria-hidden className="size-4" /> </Boop> Settings </Button> <Button variant="secondary"> <Boop scale={1.25} timing={140}> <Heart aria-hidden className="size-4" /> </Boop> Save </Button> <Button> Continue <Boop x={4} timing={160}> <ArrowRight aria-hidden className="size-4" /> </Boop> </Button> </div> </Panel> ); }
API reference
Boop
component
Also accepts the native props of its root element and a ref.
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 declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.
Related
- Icon swapTurns one icon into another in place for a control whose meaning flips: copy to copied, play to pause, menu to close.
- MagneticWraps one button or link so it leans toward the pointer as it comes near, up to 8px, and settles back when the pointer leaves.
- ButtonA familiar action with immediate focus, a tactile press, and optional pending or label feedback that stays on the button itself.