Button
Displays a button or a component that looks like a button.
"use client"
import { ArrowRightIcon, SparklesIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
export default function ButtonDemo() {
return (
<div className="flex flex-wrap items-center justify-center gap-3">
<Button>
Build my resume
<ArrowRightIcon data-icon="inline-end" />
</Button>
<Button variant="dark">See examples</Button>
<Button variant="gradient-glow">
<SparklesIcon data-icon="inline-start" />
Upgrade
</Button>
</div>
)
}Installation
pnpm dlx shadcn@latest add @kickresume/buttonUsage
import { Button } from "@/components/ui/button"<div className="flex flex-wrap items-center justify-center gap-3">
<Button>
Build my resume
<ArrowRightIcon data-icon="inline-end" />
</Button>
<Button variant="dark">See examples</Button>
<Button variant="gradient-glow">
<SparklesIcon data-icon="inline-start" />
Upgrade
</Button>
</div>Cursor
Tailwind v4 switched from cursor: pointer to cursor: default for the button component.
If you want to keep the cursor: pointer behavior, add the following code to your CSS file:
You can also enable this during project setup with npx shadcn@latest init --pointer.
@layer base {
button:not(:disabled),
[role="button"]:not(:disabled) {
cursor: pointer;
}
}Examples
Kickresume brand variants
The complete design-system matrix includes five filled and five outline variants:
- fills:
coral,white,dark,gradient,gradient-glow - outlines:
coral-ol,white-ol,dark-ol,gradient-ol,gradient-glow-ol
Filled variants use the dark focus ring. The coral outline uses the source gray focus ring; the other outlines use coral. Every brand variant implements its source hover, focus, pressed, and disabled colors. Hover and focus preserve the button's geometry, so surrounding layout does not shift between states.
"use client"
import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"
const filledVariants = [
{ value: "coral", label: "Coral" },
{ value: "white", label: "White" },
{ value: "dark", label: "Dark Blue" },
{ value: "gradient", label: "Gradient" },
{ value: "gradient-glow", label: "Gradient Glow" },
] as const
const outlineVariants = [
{ value: "coral-ol", label: "Coral Outline" },
{ value: "white-ol", label: "White Outline", dark: true },
{ value: "dark-ol", label: "Dark Blue Outline" },
{ value: "gradient-ol", label: "Gradient Outline" },
{ value: "gradient-glow-ol", label: "Gradient Glow Outline" },
] as const
const compatibilityVariants = [
{ value: "primary", label: "Primary" },
{ value: "outline-primary", label: "Outline Primary" },
{ value: "outline-dark", label: "Outline Dark" },
{ value: "secondary", label: "Secondary" },
{ value: "upgrade", label: "Upgrade" },
{ value: "ghost-dark", label: "Ghost Dark" },
{ value: "dashed-primary", label: "Dashed Primary" },
{ value: "text-primary", label: "Text Primary" },
{ value: "text-secondary", label: "Text Secondary" },
{ value: "upgrade-inverted", label: "Upgrade Inverted", dark: true },
{ value: "text-dark", label: "Text Dark" },
{ value: "link-dark", label: "Link Dark" },
{ value: "light", label: "Light", dark: true },
] as const
export default function ButtonBrandVariants() {
return (
<div className="flex w-full flex-col gap-8">
<div className="flex flex-col gap-3">
<p className="text-sm font-medium">Filled variants</p>
<div className="grid grid-cols-[repeat(auto-fit,minmax(min(100%,12rem),1fr))] gap-3">
{filledVariants.map(({ value, label }) => (
<div
key={value}
className="flex min-h-20 items-center justify-center rounded-md border p-3"
>
<Button variant={value}>{label}</Button>
</div>
))}
</div>
</div>
<div className="flex flex-col gap-3">
<p className="text-sm font-medium">Outline variants</p>
<div className="grid grid-cols-[repeat(auto-fit,minmax(min(100%,12rem),1fr))] gap-3">
{outlineVariants.map(({ value, label, ...variant }) => (
<div
key={value}
className={cn(
"flex min-h-20 items-center justify-center rounded-md border p-3",
"dark" in variant && "bg-[var(--kr-blue-dark)]"
)}
>
<Button variant={value}>{label}</Button>
</div>
))}
</div>
</div>
<div className="flex flex-col gap-3">
<p className="text-sm font-medium">Ghost treatment</p>
<div className="grid grid-cols-[repeat(auto-fit,minmax(min(100%,12rem),1fr))] gap-3">
{outlineVariants.map(({ value, label, ...variant }) => (
<div
key={value}
className={cn(
"flex min-h-20 items-center justify-center rounded-md border p-3",
"dark" in variant && "bg-[var(--kr-blue-dark)]"
)}
>
<Button variant={value} ghost>
{label.replace(" Outline", "")} Ghost
</Button>
</div>
))}
</div>
</div>
<div className="flex flex-col gap-3">
<p className="text-sm font-medium">Compatibility variants</p>
<div className="grid grid-cols-[repeat(auto-fit,minmax(min(100%,12rem),1fr))] gap-3">
{compatibilityVariants.map(({ value, label, ...variant }) => (
<div
key={value}
className={cn(
"flex min-h-28 flex-col items-center justify-center gap-2 rounded-md border p-3",
"dark" in variant && "bg-[var(--kr-blue-dark)]"
)}
>
<Button className="w-full max-w-40" variant={value}>
{label}
</Button>
<Button className="w-full max-w-40" variant={value} disabled>
Disabled
</Button>
</div>
))}
</div>
</div>
</div>
)
}Compatibility API
The compatibility layer keeps the established Kickresume UI-kit names while the standard shadcn variants remain available:
| Variant | Intended treatment |
|---|---|
primary | Solid coral action |
outline-primary | Coral outline action |
outline-dark | Charcoal outline action |
secondary | Solid Kickresume blue action |
upgrade | Premium gradient action |
ghost-dark | Dark ghost/outline action |
dashed-primary | Dashed coral outline action |
text-primary | Coral text action |
text-secondary | Kickresume blue text action |
upgrade-inverted | White premium action for dark surfaces |
text-dark | Charcoal text action |
link-dark | Charcoal underlined action |
light | White action for dark surfaces |
The legacy aliases dashed, text, and secondary-blue remain supported for
source compatibility. The live matrix above shows both normal and disabled
states for all compatibility variants without replacing the ten generic brand
variants.
Use ghost to remove a brand variant's fill and border while retaining its
text color. Use outline to convert coral, white, dark, gradient, or
gradient-glow to the corresponding -ol variant.
<Button variant="coral" outline>
Coral Outline
</Button>
<Button variant="gradient-glow-ol" ghost>
Premium Ghost
</Button>Size
Use the size prop to change the size of the button.
"use client"
import { ArrowUpRightIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
export default function ButtonSize() {
return (
<div className="flex flex-col items-start gap-8 sm:flex-row">
<div className="flex items-start gap-2">
<Button size="xs" variant="outline">
Extra Small
</Button>
<Button size="icon-xs" aria-label="Submit" variant="outline">
<ArrowUpRightIcon data-icon="inline-start" />
</Button>
</div>
<div className="flex items-start gap-2">
<Button size="sm" variant="outline">
Small
</Button>
<Button size="icon-sm" aria-label="Submit" variant="outline">
<ArrowUpRightIcon data-icon="inline-start" />
</Button>
</div>
<div className="flex items-start gap-2">
<Button variant="outline">Default</Button>
<Button size="icon" aria-label="Submit" variant="outline">
<ArrowUpRightIcon data-icon="inline-start" />
</Button>
</div>
<div className="flex items-start gap-2">
<Button variant="outline" size="lg">
Large
</Button>
<Button size="icon-lg" aria-label="Submit" variant="outline">
<ArrowUpRightIcon />
</Button>
</div>
</div>
)
}Default
"use client"
import { Button } from "@/components/ui/button"
export default function ButtonDefault() {
return <Button>Button</Button>
}Outline
"use client"
import { Button } from "@/components/ui/button"
export default function ButtonOutline() {
return <Button variant="outline">Outline</Button>
}Secondary
"use client"
import { Button } from "@/components/ui/button"
export default function ButtonSecondary() {
return <Button variant="secondary">Secondary</Button>
}Ghost
"use client"
import { Button } from "@/components/ui/button"
export default function ButtonGhost() {
return <Button variant="ghost">Ghost</Button>
}Destructive
"use client"
import { Button } from "@/components/ui/button"
export default function ButtonDestructive() {
return <Button variant="destructive">Destructive</Button>
}Link
"use client"
import { Button } from "@/components/ui/button"
export default function ButtonLink() {
return <Button variant="link">Link</Button>
}Icon
"use client"
import { CircleFadingArrowUpIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
export default function ButtonIcon() {
return (
<Button variant="outline" size="icon" aria-label="Upgrade">
<CircleFadingArrowUpIcon data-icon="inline-start" />
</Button>
)
}With Icon
Place an icon before or after the label — the button spaces its children automatically with gap.
"use client"
import { GitBranchIcon, GitForkIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
export default function ButtonWithIcon() {
return (
<div className="flex gap-2">
<Button variant="outline">
<GitBranchIcon data-icon="inline-start" /> New Branch
</Button>
<Button variant="outline">
Fork
<GitForkIcon data-icon="inline-end" />
</Button>
</div>
)
}Rounded
Use the rounded-full class to make the button rounded.
"use client"
import { ArrowUpIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
export default function ButtonRounded() {
return (
<div className="flex gap-2">
<Button className="rounded-full">Get Started</Button>
<Button
variant="outline"
size="icon"
aria-label="Submit"
className="rounded-full"
>
<ArrowUpIcon data-icon="inline-start" />
</Button>
</div>
)
}Spinner
Render a <Spinner /> component inside the button to show a loading state.
"use client"
import { Button } from "@/components/ui/button"
import { Spinner } from "@/components/ui/spinner"
export default function ButtonSpinner() {
return (
<div className="flex gap-2">
<Button variant="outline" disabled>
<Spinner data-icon="inline-start" />
Generating
</Button>
<Button variant="secondary" disabled>
Downloading
<Spinner data-icon="inline-end" />
</Button>
</div>
)
}Button Group
To create a button group, use the ButtonGroup component. See the Button Group documentation for more details.
"use client"
import * as React from "react"
import {
ArchiveIcon,
ArrowLeftIcon,
CalendarPlusIcon,
ClockIcon,
ListFilterIcon,
MailCheckIcon,
MoreHorizontalIcon,
TagIcon,
Trash2Icon,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export default function ButtonGroupDemo() {
const [label, setLabel] = React.useState("personal")
return (
<ButtonGroup>
<ButtonGroup className="hidden sm:flex">
<Button
variant="outline"
size="icon-sm"
className="max-md:size-10"
aria-label="Go Back"
>
<ArrowLeftIcon data-icon="inline-start" />
</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">Archive</Button>
<Button variant="outline">Report</Button>
</ButtonGroup>
<ButtonGroup>
<Button variant="outline">Snooze</Button>
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button
variant="outline"
size="icon-sm"
className="max-md:size-10"
aria-label="More Options"
/>
}
>
<MoreHorizontalIcon data-icon="inline-start" />
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-40">
<DropdownMenuGroup>
<DropdownMenuItem>
<MailCheckIcon />
Mark as Read
</DropdownMenuItem>
<DropdownMenuItem>
<ArchiveIcon />
Archive
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<ClockIcon />
Snooze
</DropdownMenuItem>
<DropdownMenuItem>
<CalendarPlusIcon />
Add to Calendar
</DropdownMenuItem>
<DropdownMenuItem>
<ListFilterIcon />
Add to List
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<TagIcon />
Label As...
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuRadioGroup
value={label}
onValueChange={setLabel}
>
<DropdownMenuRadioItem value="personal">
Personal
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="work">
Work
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="other">
Other
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuSub>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem variant="destructive">
<Trash2Icon />
Trash
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
</ButtonGroup>
)
}As Link
You can use the buttonVariants helper to make a link look like a button.
Do not use <Button render={<a />} nativeButton={false} /> for links. The Base UI Button component always applies role="button", which overrides the semantic link role on <a> elements. Use buttonVariants with a plain <a> tag instead.
"use client"
import { buttonVariants } from "@/components/ui/button"
export default function ButtonRender() {
return (
<a
href="#"
className={buttonVariants({ variant: "secondary", size: "sm" })}
>
Login
</a>
)
}API Reference
Button
The Button component is a wrapper around the button element that adds a variety of styles and functionality.
| Prop | Type | Default |
|---|---|---|
variant | shadcn: "default" | "outline" | "secondary" | "ghost" | "destructive" | "link"; brand: the ten variants above; compatibility: "primary" | "outline-primary" | "outline-dark" | "dashed-primary" | "text-primary" | "text-secondary" | "upgrade" | "upgrade-inverted" | "ghost-dark" | "text-dark" | "link-dark" | "light"; legacy aliases: "dashed" | "secondary-blue" | "text" | "default" |
size | "default" | "xs" | "sm" | "lg" | "xl" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | "default" |
outline | boolean | false |
ghost | boolean | false |
The default is 14/20 with 13px horizontal and 6px
vertical padding, a fixed 34px height, a 120px minimum width, and a 10px
radius. Below 768px its fixed height becomes 40px. sm uses 14/20 with 23px ×
4px padding; lg uses 16/24 with 31px × 9px padding.