caliper uidocs

Button

Free

A familiar action with immediate focus, a tactile press, and optional pending or label feedback that stays on the button itself.

Source packages/ui/src/button.tsxDepends on @radix-ui/react-slot, lucide-reactMotion 120 ms colour · 1 px press
Invite your team

Teammates get access to every project in Forma Studio.

3 invites left
Variant
Size
State

Installation #

terminal
pnpm dlx shadcn@latest add https://caliperui.site/r/button.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 #

Name the result, not the gesture: “Send invite”, not “Submit”. Keep one primary action per decision and use secondary or ghost for the rest.

Pass loading while your handler runs. The button keeps focus, exposes aria-busy and blocks repeat activation, so you don’t need to disable it yourself.

Use asChild to give a link the button treatment without nesting interactive elements.

invite-footer.tsx
import { Button } from "@/components/ui/button"

export function InviteFooter({ pending, onSend }) {
  return (
    <footer className="flex justify-end gap-2">
      <Button variant="ghost">Cancel</Button>
      <Button loading={pending} onClick={onSend}>
        Send invite
      </Button>
    </footer>
  )
}

Examples #

Four emphases

Default is ink. Secondary sits on the canvas, ghost reveals a fill on interaction, destructive is reserved for irreversible actions.

With an icon

Icons are 16 px with an 8 px gap. Icon-only buttons need an aria-label.

Label feedback

feedbackKey animates a meaningful label change, such as Save to Saved, without moving the layout around it.

As a link

asChild applies the button treatment to an anchor, keeping native link behaviour.

API reference #

Accepts every native button prop and a ref, plus:

PropTypeDefaultDescription
variant"default" | "secondary" | "ghost" | "destructive""default"Visual emphasis. Primary actions stay ink; destructive is for irreversible actions.
size"sm" | "md" | "lg" | "icon""md"md follows --control-height (36 / 32 compact / 44 touch).
loadingbooleanfalseShows a spinner over the hidden label, so the width never changes; sets aria-busy and blocks activation while keeping focus.
feedbackKeystring—Animates a label change when the key changes, e.g. "saving" to "saved".
asChildbooleanfalseMerges button styling onto its single child, such as a link.

Accessibility #

A native button with a visible 2 px focus ring at 2 px offset. Disabled uses the native attribute; loading keeps the button focusable and announces busy state.

KeyBehaviour
TabMoves focus to the button.
EnterActivates it. Blocked while loading.
SpaceActivates it on release.

Motion #

A press sinks rather than shrinks: the button travels 1 px down, its lift becomes an inner shade, and it settles back on the settle curve. Text and layout never scale. Reduced motion removes the travel; the shade still answers the press.

120 msbackground, colour, border
90 ms · 180 mspress down · release on ease-settle
1 px--motion-press-travel
r 8 · 10 · 12radius by height: 32 · 36 · 44