Skip to content
GetUrUI

Button

Updated
.md

Pill button in twelve styles and five sizes with a spring press, leading and trailing icons, inline links whose underline draws in, a Spinner loading state, an async state machine and a magnetic hover.

buttonctalinkdestructiveloadingasyncmagneticspring

Installation

$npx shadcn@latest add @geturui/button
Download source (.zip)

Also installs: Spinner

Usage

usage.tsx
1import { Plus, Trash2 } from "lucide-react";
2import { Button, StatefulButton, MagneticButton } from "@/components/ui/button";
3
4export function Actions() {
5  return (
6    <div className="flex flex-wrap items-center gap-3">
7      <Button trailing="arrow">Get started</Button>
8      <Button variant="secondary" iconLeading={Plus}>New project</Button>
9      <Button variant="destructive" iconLeading={Trash2}>Delete project</Button>
10      <Button variant="link" href="/changelog">See what shipped</Button>
11      <StatefulButton
12        variant="secondary"
13        successLabel="Saved"
14        onAction={() => fetch("/api/save", { method: "POST" })}
15      >
16        Save changes
17      </StatefulButton>
18      <MagneticButton variant="inverse">Hover me</MagneticButton>
19    </div>
20  );
21}

Props

PropTypeDefaultDescription
variant"primary" | "secondary" | "outline" | "ghost" | "inverse" | "pro" | "danger" | "destructive" | "destructive-ghost" | "link" | "link-muted" | "link-destructive""primary"Visual style. danger is the outlined destructive, destructive the solid one, destructive-ghost the quiet one; link variants drop the pill and draw an underline on hover.
size"xs" | "sm" | "md" | "lg" | "xl""md"Height and padding: 28, 32, 40, 48 or 56px. Link variants keep only the type size.
iconLeadingComponentType | ReactNode—Icon before the label: a component (Plus) or a node (<Plus />). Sized to the button.
iconTrailingComponentType | ReactNode—Icon after the label; nudges right on hover.
trailing"arrow" | "external" | ReactNode—Glyph after the label. The built-in arrows nudge on hover.
hrefstring—Renders an <a> with the same look (or use ButtonLink). Disabled links drop the href.
loadingbooleanfalseShows a Spinner, keeps the width and blocks presses while keeping focus on the button.
showTextWhileLoadingbooleanfalseKeep the label visible with the spinner in place of the leading icon.
iconOnlybooleanfalseSquare button sized for a single icon. Pass aria-label.
blockbooleanfalseStretch to the container width.
onAction*() => Promise<unknown>—StatefulButton only. Resolve to show the check, reject to shake.
successLabelReactNode"Done"StatefulButton only. Label shown for 1.8s after the action resolves.
strengthnumber0.35MagneticButton only. How far the button follows the cursor (0–1).
buttonClasses()({ variant, size, iconOnly, block, className }) => string—Server-safe class string for anything that should look like a button.
...propsHTMLMotionProps<"button">—Any button or motion prop, forwarded to the element.

Accessibility

  • Renders a native <button> with type="button" by default, so Enter and Space work and it never submits forms by accident; with href it renders a real <a>.
  • loading sets aria-busy and aria-disabled instead of disabled, so keyboard focus is not lost; presses and implicit form submits are blocked until it finishes.
  • Disabled links render without href and with aria-disabled, so they are skipped by Tab and can't be followed.
  • Icons and the spinner are aria-hidden; icon-only buttons need an aria-label. Visible focus ring on keyboard focus.

Related