Skip to content
caliper ui docs

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.

Free
pnpm dlx shadcn@latest add @caliper/boop

Workspace

Hover or tab to each button: the icon gets a nudge, then settles.

Installs components/ui/boop.tsxDepends on none beyond ReactMotion none in component source

When 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

terminal
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.

example.tsx
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.

PropTypeDefaultDescription
rotatenumber0Degrees to turn.
scalenumber1Size at the peak, 1 for none.
xnumber0Pixels to shift sideways and down.
ynumber0
timingnumber160How long the push lasts before the spring brings it back, in ms.

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.

All Surfaces examples