Button
Displays a button or a component that looks like a button.
import { ArrowUpIcon } from "lucide-react";import { Button } from "@/components/ui/button";export default function ButtonDemo() { return ( <div className="flex flex-wrap items-center gap-2 md:flex-row"> <Button variant="outline">Button</Button> <Button variant="outline" size="icon" aria-label="Submit"> <ArrowUpIcon /> </Button> </div> );}Installation
npx shadcn@latest add @cortexcn/buttonnpm install radix-ui class-variance-authorityimport * as React from "react";import { cva, type VariantProps } from "class-variance-authority";import { Slot } from "radix-ui";import { cn } from "@/lib/utils";const buttonVariants = cva( "group/button inline-flex shrink-0 cursor-pointer items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:scale-[0.98] disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/80", outline: "border-border bg-background shadow-xs hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground", ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50", destructive: "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40", link: "text-primary decoration-1 underline-offset-3 hover:underline active:scale-none", }, size: { default: "h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", xs: "h-6 gap-1 rounded-[min(var(--radius-lg),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3", sm: "h-8 gap-1 rounded-[min(var(--radius-lg),10px)] px-2.5 in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5", lg: "h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3", icon: "size-9", "icon-xs": "size-6 rounded-[min(var(--radius-lg),8px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3", "icon-sm": "size-8 rounded-[min(var(--radius-lg),10px)] in-data-[slot=button-group]:rounded-lg", "icon-lg": "size-10", }, }, defaultVariants: { variant: "default", size: "default", }, },);function Button({ className, variant = "default", size = "default", asChild = false, ...props}: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & { asChild?: boolean; }) { const Comp = asChild ? Slot.Root : "button"; return ( <Comp data-slot="button" data-variant={variant} data-size={size} className={cn(buttonVariants({ variant, size, className }))} {...props} /> );}export { Button, buttonVariants };Usage
import { Button } from "@/components/ui/button";<Button variant="outline">Button</Button>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:
@layer base {
button:not(:disabled),
[role="button"]:not(:disabled) {
cursor: pointer;
}
}Examples
Size
Use the size prop to change the size of the button.
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 /> </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 /> </Button> </div> <div className="flex items-start gap-2"> <Button variant="outline">Default</Button> <Button size="icon" aria-label="Submit" variant="outline"> <ArrowUpRightIcon /> </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
import { Button } from "@/components/ui/button";export default function ButtonDefault() { return <Button>Button</Button>;}Outline
import { Button } from "@/components/ui/button";export default function ButtonOutline() { return <Button variant="outline">Outline</Button>;}Secondary
import { Button } from "@/components/ui/button";export default function ButtonSecondary() { return <Button variant="secondary">Secondary</Button>;}Ghost
import { Button } from "@/components/ui/button";export default function ButtonGhost() { return <Button variant="ghost">Ghost</Button>;}Destructive
import { Button } from "@/components/ui/button";export default function ButtonDestructive() { return <Button variant="destructive">Destructive</Button>;}Link
import { Button } from "@/components/ui/button";export default function ButtonLink() { return <Button variant="link">Link</Button>;}Icon
import { CircleFadingArrowUpIcon } from "lucide-react";import { Button } from "@/components/ui/button";export default function ButtonIcon() { return ( <Button variant="outline" size="icon"> <CircleFadingArrowUpIcon /> </Button> );}With Icon
Remember to add the data-icon="inline-start" or data-icon="inline-end" attribute to the icon for the correct spacing.
import { GitBranchIcon } from "lucide-react";import { Button } from "@/components/ui/button";export default function ButtonWithIcon() { return ( <Button variant="outline" size="sm"> <GitBranchIcon data-icon="inline-start" /> New Branch </Button> );}Rounded
Use the rounded-full class to make the button rounded.
import { ArrowUpIcon } from "lucide-react";import { Button } from "@/components/ui/button";export default function ButtonRounded() { return ( <div className="flex flex-col gap-8"> <Button variant="outline" size="icon" className="rounded-full"> <ArrowUpIcon /> </Button> </div> );}Spinner
Render a <Spinner /> component inside the button to show a loading state. Remember to add the data-icon="inline-start" or data-icon="inline-end" attribute to the spinner for the correct spacing.
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-start" /> </Button> </div> );}Button Group
To create a button group, use the ButtonGroup component.
import { PlusIcon } from "lucide-react";import { Button } from "@/components/ui/button";import { ButtonGroup } from "@/components/ui/button-group";export default function ButtonGroupDemo() { return ( <ButtonGroup> <Button variant="outline">Archive</Button> <Button variant="outline">Report</Button> <Button variant="outline">Snooze</Button> <Button variant="outline" size="icon" aria-label="Add"> <PlusIcon /> </Button> </ButtonGroup> );}As Child
You can use the asChild prop on <Button /> to make another component look like a button. Here's an example of a link that looks like a button.
import Link from "next/link";import { Button } from "@/components/ui/button";export default function ButtonAsChild() { return ( <Button asChild> <Link href="/login">Login</Link> </Button> );}RTL
Buttons respect the document direction. Use the rtl: variant to flip
directional icons.
"use client";import * as React from "react";import { ArrowRightIcon, PlusIcon } from "lucide-react";import { Button } from "@/components/ui/button";import { Spinner } from "@/components/ui/spinner";const content = { ltr: { button: "Button", delete: "Delete", submit: "Submit", loading: "Loading", }, rtl: { button: "زر", delete: "حذف", submit: "إرسال", loading: "جاري التحميل", },} as const;export default function ButtonRtl() { const [dir, setDir] = React.useState<"ltr" | "rtl">("rtl"); const t = content[dir]; return ( <div className="flex flex-col items-center gap-6"> <Button variant="ghost" size="sm" onClick={() => setDir((d) => (d === "rtl" ? "ltr" : "rtl"))} > Toggle dir ({dir}) </Button> <div className="flex flex-wrap items-center gap-2 md:flex-row" dir={dir}> <Button variant="outline">{t.button}</Button> <Button variant="destructive">{t.delete}</Button> <Button variant="outline"> {t.submit}{" "} <ArrowRightIcon className="rtl:rotate-180" data-icon="inline-end" /> </Button> <Button variant="outline" size="icon" aria-label="Add"> <PlusIcon /> </Button> <Button variant="secondary" disabled> <Spinner data-icon="inline-start" /> {t.loading} </Button> </div> </div> );}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 | "default" | "outline" | "ghost" | "destructive" | "secondary" | "link" | "default" |
size | "default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | "default" |
asChild | boolean | false |