Button
Trigger an action or submit a form. The main action is ink; brand, quieter, and destructive variants step around it.
When to use it
Use a button for anything that does something: saving, sending, opening a dialog. If it takes people somewhere else, itโs a link, even when it looks like a button.
Give each view one primary action, the darkest thing on it. Put the alternatives beside it as secondary, outline, or ghost buttons, and keep destructive for actions that delete or remove.
Do
One primary action leads; Cancel steps back as secondary.
Donโt
Three primary buttons compete, and nothing says which one to press.
Sizes
The default is 32px high, with 13px label text. Use the small sizes in dense toolbars and table rows, and large where a touch target matters.
Icons
Primary, brand, and destructive buttons are text only; an icon on one signals a state, such as pending or done. Quieter buttons may lead with an icon when it helps people recognize the action.
Icon-only buttons need an accessible label and a Tooltip. ButtonIcon crossfades between two icons in a fixed slot, such as saved and unsaved:
123456789101112131415161718192021222324"use client"
import { useState } from "react"import { Bookmark, BookmarkCheck } from "lucide-react"import { Button, ButtonIcon } from "@/components/ui/button"import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
export function SaveButton() { const [saved, setSaved] = useState(false) const [animate, setAnimate] = useState(false) return ( <Tooltip> <TooltipTrigger render={ <Button variant="outline" size="icon" aria-label="Save item" aria-pressed={saved} onClick={(event) => { setAnimate(event.detail > 0); setSaved((value) => !value) }} /> }> <ButtonIcon active={saved} activeIcon={<BookmarkCheck />} animated={animate}> <Bookmark /> </ButtonIcon> </TooltipTrigger> <TooltipContent>{saved ? "Unsave item" : "Save item"}</TooltipContent> </Tooltip> )}Install Tooltip alongside it, and place TooltipProvider once around your app:
1pnpm dlx shadcn@latest add https://jzbui.com/r/tooltip.jsonHow it behaves
It defaults to type="button", so it never submits a form by accident; set type="submit" inside forms. Native props, ref, and events are forwarded, and render composes it into links and triggers.
Disabled buttons use native disabled semantics. Pointer presses scale the button to 96%; keyboard presses and reduced motion skip it, and motion={false} turns it off.
| Prop | Values | Default |
|---|---|---|
| variant | default ยท brand ยท secondary ยท outline ยท ghost ยท destructive ยท link | default |
| size | xs ยท sm ยท default ยท lg ยท icon-xs ยท icon-sm ยท icon ยท icon-lg | default |
| motion | true ยท false | true |
| type | button ยท submit ยท reset | button |
Build it
Add it with the shadcn CLI:
1pnpm dlx shadcn@latest add https://jzbui.com/r/button.jsonEvery variant in one place:
1234567<Button>Primary</Button><Button variant="brand">Brand</Button><Button variant="secondary">Secondary</Button><Button variant="outline">Outline</Button><Button variant="ghost">Ghost</Button><Button variant="destructive">Destructive</Button><Button variant="link">Link</Button>The full source is yours to edit once installed: