Dashral Pro is on the wayGet 30% off at launch

Dashral

Button

Button with motion: it presses in on a spring, a new label crosses over while the button glides to its new width, and loading pops the spinner in.

$ pnpm dlx shadcn@latest add https://dashral.com/r/motion-button.json

Installation

$ pnpm dlx shadcn@latest add https://dashral.com/r/motion-button.json

Adds components/dashral/motion/button.tsx, the Button it builds on (in components/dashral/), motion and the motion files every motion component shares. With the registry in your components.json:

$ pnpm dlx shadcn@latest add @dashral/motion-button

Usage

The same as Button, imported from components/dashral/motion/: swap the import and it moves.

example.tsx
import { Button } from "@/components/dashral/motion/button"

<Button loading={saving} loadingText="Saving…" onClick={save}>
  Save changes
</Button>

Examples

Styles, sizes and states

Every button does something: Save goes Saving… then Saved, Delete asks once, Add item pops a count in, ••• turns into ×, Copy pops a tick in, Upload works before it says it's done.

Styles

Icons and sizes

Feedback and states

Hold to confirm

holdToConfirm: a click does nothing; held, a fill sweeps across, and when it's full the button pops, the delete (or payment) runs and a toast says so. Let go early and it drains back.

Acme workspace

12 dashboards, 8 members.

Pay Fernpay invoice

$1,240.00 from Operating

Follow

morph: the text label changes letter by letter: Follow becomes Following (and Unfollow under the pointer), the follower count turning on wheels.

LP

Leo Park

1,284 followers

Hold styles

holdStyle: fill, radial from the press, a ring round the edge, a bar along the foot, or a countdown on odometer wheels. Hold each until it pops.

holdStyle="fill"

Sweeps across

holdStyle="radial"

Grows from your press

holdStyle="ring"

Traces the edge

holdStyle="bar"

A line along the foot

holdStyle="countdown"

Seconds left on wheels

A form that saves

Edit the name and press Save: Saving… pops in with the spinner, then Saved rises in as the button glides to its width.

Sending and copying

Send shows Sending… and keeps its width, then the invite drops into the list; Copy link pops a tick in.

  • mmaya@acme.comInvited

Compared with Default

Default

Presses in to 97% in CSS. A new label snaps the width.

Motion

Presses on a spring, deeper for an icon button and shallower for a wide one. A new label rises in while the width glides to it; the spinner and loadingText pop in.

How the two editions differ in general, and which to use where: Motion.

API reference

Every prop of Button, and every attribute of the element it renders. What the Motion edition adds is marked Motion.

Button

Also takes every prop of <button>(opens in a new tab).

PropTypeDefault
holdToConfirmboolean | number

Motion onClick runs only after a press is held this long: true for 1.2s, or milliseconds. Space and Enter hold it too.

false
holdStyle"fill" | "radial" | "ring" | "bar" | "countdown"

Motion How the hold shows its progress: a sweep, a circle from the press, a ring round the edge, a line along the foot, or the seconds left.

"fill"
morphboolean

Motion A label that's only text changes letter by letter (Follow to Following) instead of crossing over.

false
variant"solid" | "soft" | "outline" | "ghost" | "link"

How much it stands out.

"solid"
color"primary" | "neutral" | "info" | "success" | "warning" | "destructive" | "highlight"

What it means.

"primary" (outline, ghost: "neutral")
glowboolean

A soft colored shadow under a solid button.

false
size"xs" | "sm" | "default" | "lg" | "icon-xs" | "icon-sm" | "icon" | "icon-lg" | "inline"

Height and padding; each icon size is square, as tall as the size it's named after; inline has neither, for a link-style button in a sentence.

"default"
loadingboolean

Shows a spinner in place of the label, which stays (hidden) so the button keeps its width, and ignores clicks, but stays focusable.

false
loadingTextReactNode

Beside the spinner while loading, e.g. "Saving…"; the button is as wide as the wider of it and the label. Not with asChild.

—
classNamestring

Extra classes, merged with cn() so yours win.

—

No asChild: for a link that looks like a button, use the default Button.

Motion

  • Presses in on a spring while held, as deep as its size calls for: an icon button a little more, a wide one a little less.
  • A new label fades in rising 4px while the old one fades out; with morph, a label that's only text changes letter by letter instead: the letters both have glide to their new places, new ones rise out of a soft blur and the rest fade (Follow to Following keeps Follow and grows "ing"). Either way the button glides to the new width (--duration-considered, no overshoot) instead of snapping.
  • loading pops the spinner, and loadingText, in over the label with a small overshoot; the button keeps its width.
  • With holdToConfirm, held, a fill sweeps from start to end at an even pace; full, the button pops (a quick swell and back) and onClick runs, submitting its form if it's a submit button; let go early and the fill drains back on a glide. Space and Enter hold it too; screen readers hear "(press and hold)" after the label; phones tick at the start and buzz when it runs.
  • Disabled, loading, or opening a menu, popover or tooltip, it doesn't press in. With reduced motion (the OS's, or data-motion="reduce" on <html>) everything only fades.