Browse the docs
ComponentsNo. 09 of 109
Button
Triggers an action. Variants default, secondary, outline, ghost, destructive and link; sizes sm, default, lg and three square icon sizes. Supports asChild and a loading state.
Installation
pnpm dlx shadcn@latest add @corsair-ui/buttonNo setup needed: the CLI finds @corsair-ui in the shadcn registry directory. The files land in your project as source: edit them like your own code. More in Installation.
- Also adds
- utils
- npm
@radix-ui/react-slotclass-variance-authorityclsxlucide-reacttailwind-merge
Examples
Variants
Sizes
With icons
Icon only
Square sizes. Always give them an aria-label.
Loading
Click the first button.
Disabled
As a link
asChild renders the child element with the button styles.
API
| Prop | Type | Default |
|---|---|---|
variantVisual weight. | "default" | "secondary" | "outline" | "ghost" | "destructive" | "link" | "default" |
sizeHeight and padding; the icon sizes are square. | "sm" | "default" | "lg" | "icon-sm" | "icon" | "icon-lg" | "default" |
loadingShows a spinner, sets aria-busy and blocks clicks. | boolean | false |
asChildStyles the child element, e.g. a link, instead of rendering a button. | boolean | false |
Accessibility
- A native button, so Enter and Space work and it is announced as a button.
- Icon-only sizes need an aria-label.
- With asChild, a disabled link gets aria-disabled instead of the disabled attribute.
Compatibility
- Tailwind CSS
- 3.4 and 4, both checked in CI
- React
- 19
- Rendering
- Works in server components
Source
1 file, installed as source you own
button.tsxView on GitHubimport { Slot, Slottable } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { LoaderCircleIcon } from "lucide-react";
import type { ComponentProps } from "react";
import { cn } from "@/registry/default/lib/utils";
const buttonVariants = cva(
[
"inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap select-none",
"transition-[color,background-color,border-color,box-shadow,opacity] outline-none motion-reduce:transition-none",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50",
"aria-[invalid=true]:border-destructive aria-[invalid=true]:ring-destructive/20",
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
],
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
outline: "border-input bg-field hover:bg-accent hover:text-accent-foreground border",
ghost: "hover:bg-accent hover:text-accent-foreground",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90 focus-visible:ring-destructive/20",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
sm: "h-8 gap-1.5 px-3 has-[>svg]:px-2.5",
default: "h-9 px-4 py-2 has-[>svg]:px-3",
lg: "h-10 px-6 has-[>svg]:px-4",
"icon-sm": "size-8",
icon: "size-9",
"icon-lg": "size-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
interface ButtonProps extends ComponentProps<"button">, VariantProps<typeof buttonVariants> {
/** Render the single child element (a link, for example) with the button styles instead. */
asChild?: boolean;
/** Shows a spinner and blocks interaction while an action is running. */
loading?: boolean;
}
/**
* The icon sizes render a square button: give it an `aria-label`, since there
* is no visible text to name it.
*/
function Button({
className,
variant,
size,
asChild = false,
loading = false,
disabled,
children,
...props
}: ButtonProps) {
const Comp = asChild ? Slot : "button";
const inactive = disabled || loading;
return (
<Comp
data-slot="button"
data-variant={variant ?? "default"}
data-size={size ?? "default"}
className={cn(buttonVariants({ variant, size, className }))}
// A native button can be disabled; any other element only announces it.
disabled={asChild ? undefined : inactive}
aria-disabled={asChild && inactive ? true : undefined}
aria-busy={loading || undefined}
{...props}
>
{loading ? (
<LoaderCircleIcon
aria-hidden="true"
data-slot="button-spinner"
className="animate-spin motion-reduce:animate-[spin_1.5s_linear_infinite]"
/>
) : null}
<Slottable>{children}</Slottable>
</Comp>
);
}
export { Button, buttonVariants, type ButtonProps };