Button
Free · MITOne button with ten variants, six tones, seven shadows, ten animations, three shapes and five sizes that all combine, plus loading and icon slots.
Installation
$ npx shadcn@latest add https://goodui.dev/r/button.jsonThe file lands in components/goodui/button.tsx and is yours to edit. With the @goodui registry added, npx shadcn@latest add @goodui/button works too.
Usage
import { ArrowRight } from "lucide-react"
import { Button } from "@/components/goodui/button"
export function Hero() {
return (
<div className="flex gap-3">
<Button size="lg" shadow="glow" animation="arrow" iconRight={<ArrowRight />}>
Start free trial
</Button>
<Button size="lg" variant="outline" tone="neutral">
Book a demo
</Button>
</div>
)
}When to use
- Any action on a page: submit a form, open a dialog, start a checkout.
- When one product needs several button styles that still match: solid for the primary action, soft or outline for secondary, ghost in toolbars.
- Marketing pages that want motion (shine, shimmer, arrow) without a second library.
- Navigating to another page: use link (or render an <a> with the same classes), so the browser treats it as a link.
- Turning a setting on and off: use switch.
- Choosing one option from a few: use tabs or a segmented control.
| If you need | Use |
|---|---|
| The one main action on the screen | variant="solid" (add shadow="glow" for a hero CTA) |
| Secondary actions next to a primary one | variant="soft" or "outline" |
| Toolbars and dense tables | variant="ghost" size="sm", iconOnly for single icons |
| Destructive actions | tone="danger" with variant="soft"; solid only inside a confirm dialog |
| Playful or retro brands | variant="neo" or "pushable" |
| On photos or colorful backgrounds | variant="glass" |
Examples
Async action with loading
const [saving, setSaving] = React.useState(false)
<Button loading={saving} onClick={async () => { setSaving(true); await save(); setSaving(false) }}>
Save changes
</Button>Icon-only toolbar button
<Button variant="ghost" tone="neutral" size="sm" iconOnly aria-label="Notifications">
<Bell />
</Button>API reference
| Prop | Type | Default |
|---|---|---|
| variant | "solid" | "soft" | "outline" | "ghost" | "link" | "gradient" | "glass" | "neo" | "pushable" | "dashed" | "solid" |
| tone | "primary" | "neutral" | "success" | "warning" | "danger" | "info" | "primary" |
| shadow | "none" | "sm" | "md" | "lg" | "glow" | "hard" | "inner" | "none" |
| animation | "none" | "lift" | "press" | "shine" | "shimmer" | "ripple" | "pulse" | "arrow" | "glow" | "bounce" | "none" |
| shape | "square" | "rounded" | "pill" | "rounded" |
| size | "xs" | "sm" | "md" | "lg" | "xl" | "md" |
| iconOnly Square button for a single icon. | boolean | — |
| fullWidth | boolean | — |
| loading Shows a spinner, keeps the width and disables the button. | boolean | — |
| iconLeft | React.ReactNode | — |
| iconRight | React.ReactNode | — |
| sound Plays "tap" on click when the visitor has sounds on (lib/goodui-sounds); pass a sound name to pick another. Off by default. | boolean | SoundName | — |
Also accepts every <button> attribute.
Accessibility
| Enter / Space | Activates the button (native <button>). |
| Tab | Moves focus; a focus ring shows on keyboard focus only. |
- Renders a native <button>, so roles, focus and form submission work without extra code.
- loading sets disabled and aria-busy and keeps the width, so the layout does not jump.
- iconOnly buttons need an aria-label; the icon itself is decorative.
Motion
- Hover: lift rises 2px in 180 ms, glow blooms a tone-colored shadow, shine sweeps a highlight once, arrow nudges the icons.
- Press: press scales to 0.95, bounce springs back from 0.92, ripple grows from the pointer.
- Looping: shimmer sweeps every 2.6 s and pulse sends a ring every 2 s; keep looping buttons to one per screen.
- Reduced motion: transitions, sweeps, rings and ripples are off; the loading spinner slows down instead of stopping.
- All effects are CSS (transform, opacity, background-position) on the compositor; no JavaScript runs on hover. ripple adds one short-lived span per click.
Notes for AI
Coding agents read these rules through MCP and llms.txt, or paste this page with Copy page.
- Layers are independent props: variant, tone, shadow, animation, shape, size. Combine them; do not write custom classes for colors.
- Use tone for meaning (danger, success) instead of hard-coded colors like bg-red-500.
- Pass icons through iconLeft and iconRight, not as children, so spacing and the arrow animation work.
- For links use <a> or the link component; do not wrap Button in <a>.
- Keep looping animations (shimmer, pulse) to one button per screen.
FAQ
Does it work with my shadcn theme?
Yes. Colors come from your theme tokens (--primary, --foreground…) through the tone variables, so it follows your theme and dark mode.
Can I use it as a link?
Use the link component for navigation. If you need the button look on a link, copy the classes onto an <a>.
Related
Inline link with seven underline styles (static, slide-in, grow from center, marker sweep, nudging arrow), line decorations, tones, leading icons and automatic external-link handling. Works with next/link via as.
One badge, every layer: solid, soft, outline, status dot, gradient, glass and neo in six tones, with pulse, shine, bounce-in and count-pop animations, icons, removable chips and clickable filters.
A light sweep runs across the button. Pure CSS; the sweep is removed for reduced motion.
Button and its label are pulled toward a nearby cursor with a smooth falloff and spring back on leave; off for touch and reduced motion.
Button with a seamlessly flowing rainbow border and a soft colored glow underneath, tuned to invert cleanly between light and dark themes.