UI.MD

Button

An interactive button with multiple variants, sizes, icons, loading state, and Motion layout animation.

Installation

$
npx shadcn@latest add https://ui.mattdowney.com/r/button.json

Usage

import { Button } from "@/components/ui/button"

Examples

Default

Variants

Seven fills. soft is the accent-tinted secondary that pairs with default, and subtle is its neutral version for toolbars. link paints no fill, so it ignores the size's height and sits on the text line.

Sizes

Four heights (24/28/32/40) plus three square icon sizes. Radius scales with height, so all four read as one shape, and an Input given the same size matches the button beside it exactly.

With Icon

Pass an element: it works anywhere, including from a Server Component. A bare component reference (iconStart={Mail}) also works, but only from a Client Component, because a function cannot cross the server/client boundary.

Icon sizes

The icon box scales with the button (12/14/14/16px across the four heights) and the icon-side padding is 2px tighter than the label side at every size, so leading and trailing icons both read balanced. The stroke follows the label weight too, matching semibold Geist.

With Badge

The counter holds one treatment through hover instead of restyling with the button, so the number never gets harder to read as the pointer lands. On filled buttons it is an opaque chip; on tinted ones it steps a shade deeper than the surface.

Loading

loading swaps the label for a spinner, blocks the click, and sets aria-busy. The box does not resize, so a row of buttons holds its layout. Each variant keeps its own hover fill while busy, so a loading button reads as the same button mid-press rather than turning into a different one.

API Reference

PropTypeDefaultDescription
variant"default" | "soft" | "outline" | "ghost" | "destructive" | "subtle" | "link""default"Visual style of the button. soft is the accent-tinted secondary that pairs with default; subtle is its neutral equivalent for toolbars. link paints no fill, so it ignores the size's height and padding and sits on the text line at every size.
size"default" | "xs" | "sm" | "lg" | "icon" | "icon-sm" | "icon-lg" | "inline""default"Height and padding preset. icon (32px), icon-sm (24px), and icon-lg (40px) are square; inline flows with surrounding text. variant="link" takes only the type scale from this, never the height or padding.
iconStartTablerIcon | ReactNode-Icon rendered before the label. Prefer an element (<Mail />); a bare component (Mail) still works but cannot be passed from a Server Component.
iconEndTablerIcon | ReactNode-Icon rendered after the label. Prefer an element (<ChevronRight />); a bare component still works but cannot be passed from a Server Component.
iconSize"xs" | "sm" | "default" | "lg" | "xl"-Overrides the icon size implied by size.
badgeReact.ReactNode-Count badge rendered after the label, tinted to match the variant.
badgeClassNamestring-Extra classes for the badge element.
loadingbooleanfalseShows a centered spinner, locks in the variant's hover styling, and blocks clicks while preserving the button's width.
layoutAnimationbooleanfalseMotion layout animation on content changes. Set false to prevent glitches on rapid state changes; disabled automatically under reduced motion.
asChildbooleanfalseRenders the child element instead of a <button>, merging classes and injecting icons, badge, and spinner into it.