shadcn.io is not affiliated with official shadcn/ui
Shadcn Button for React and Tailwind
Triggers an action or navigates when used as a link.
Installation
bunx --bun shadcn@latest add https://kit.dev/r/button.jsonpnpm dlx shadcn@latest add https://kit.dev/r/button.jsonnpx shadcn@latest add https://kit.dev/r/button.jsonyarn shadcn@latest add https://kit.dev/r/button.jsonThis component depends on Spinner. Install it first if you haven't already.
Install the following dependencies:
bun add @ark-ui/react tailwind-variantspnpm add @ark-ui/react tailwind-variantsnpm install @ark-ui/react tailwind-variantsyarn add @ark-ui/react tailwind-variantsAdd the following to your globals.css.
:root {
--destructive-foreground: var(--color-red-700);
--info: var(--color-blue-500);
--info-foreground: var(--color-blue-700);
--success: var(--color-emerald-500);
--success-foreground: var(--color-emerald-700);
--warning: var(--color-amber-500);
--warning-foreground: var(--color-amber-700);
}
.dark {
--destructive-foreground: var(--color-red-400);
--info: var(--color-blue-500);
--info-foreground: var(--color-blue-400);
--success: var(--color-emerald-500);
--success-foreground: var(--color-emerald-400);
--warning: var(--color-amber-500);
--warning-foreground: var(--color-amber-400);
}Copy and paste the following code into your project.
import { ark } from "@ark-ui/react/factory";
import type React from "react";
import { tv, type VariantProps } from "tailwind-variants";
import { cn } from "@/lib/utils";
import { Spinner } from "@/components/ui/spinner";
export const buttonVariants = tv({
base: [
"relative",
"inline-flex shrink-0 items-center justify-center gap-2",
"whitespace-nowrap font-medium text-sm",
"rounded-lg",
"transition-[color,background-color,border-color,box-shadow,opacity,transform]",
"outline-none focus-visible:ring-[3px] focus-visible:ring-ring/32",
"disabled:pointer-events-none disabled:opacity-64",
"data-disabled:pointer-events-none data-disabled:opacity-64",
"aria-disabled:pointer-events-none aria-disabled:opacity-64",
"data-[state=loading]:pointer-events-none",
"aria-invalid:border-destructive aria-invalid:ring-destructive/24",
"[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
"motion-reduce:transition-none!",
],
variants: {
variant: {
default: [
"bg-primary",
"border border-transparent shadow-primary/24 shadow-sm",
"text-primary-foreground",
"hover:bg-primary/90",
"focus-visible:border-background",
],
outline: [
"bg-transparent",
"text-foreground",
"border border-input shadow-sm/5",
"hover:bg-accent hover:text-accent-foreground",
"dark:bg-input/32 dark:hover:bg-input/64",
"focus-visible:border-primary",
],
destructive: [
"bg-destructive",
"text-white",
"border border-transparent shadow-destructive/24 shadow-sm",
"hover:bg-destructive/90",
"focus-visible:border-background focus-visible:ring-destructive-foreground/32",
],
secondary: [
"bg-secondary",
"text-secondary-foreground",
"border border-transparent",
"focus-visible:border-primary",
"hover:bg-secondary/80",
],
ghost: [
"hover:bg-accent hover:text-accent-foreground",
"border border-transparent",
"focus-visible:border-primary",
],
link: [
"text-primary",
"underline-offset-4",
"border border-transparent",
"hover:underline",
"focus-visible:border-primary",
],
},
size: {
xs: [
"h-6",
"gap-1.5",
"px-2",
"text-xs",
"rounded-sm",
"[&_svg:not([class*='size-'])]:size-2.5",
],
sm: [
"h-7",
"px-2.5",
"gap-1.5",
"[&_svg:not([class*='size-'])]:size-3.5",
],
md: ["h-8", "px-3", "py-2"],
lg: ["h-9", "px-3.5"],
xl: ["h-10", "text-base", "px-4"],
"icon-xs": "size-6 rounded-sm",
"icon-sm": "size-7",
"icon-md": "size-8",
"icon-lg": "size-9",
"icon-xl": "size-10 [&_svg:not([class*='size-'])]:size-5",
},
clickEffect: {
true: "active:not-aria-[haspopup]:scale-[0.98]",
},
pill: {
true: [
"rounded-full",
"has-[>svg]:data-[size=xs]:pe-3",
"has-[>svg]:data-[size=sm]:pe-3.5",
"has-[>svg]:data-[size=md]:pe-4",
"has-[>svg]:data-[size=lg]:pe-4.5",
"has-[>svg]:data-[size=xl]:pe-5",
],
},
},
defaultVariants: {
variant: "default",
size: "md",
clickEffect: true,
pill: false,
},
});
export interface ButtonProps
extends React.ComponentProps<typeof ark.button>,
VariantProps<typeof buttonVariants> {
/**
* Apply a click effect to the button
*
* @default true
*/
clickEffect?: boolean;
/**
* Show a loading indicator
*
* @default false
*/
isLoading?: boolean;
}
export const Button = (props: ButtonProps) => {
const {
variant = "default",
size = "md",
clickEffect = true,
pill = false,
isLoading = false,
asChild,
type = "button",
disabled = false,
className,
children,
...rest
} = props;
return (
<ark.button
asChild={asChild}
className={cn(
buttonVariants({ clickEffect, pill, size, variant }),
className
)}
data-pill={pill || undefined}
data-size={size}
data-slot="button"
data-state={isLoading ? "loading" : "idle"}
data-variant={variant}
disabled={disabled || isLoading}
type={asChild ? undefined : type}
{...rest}
aria-busy={isLoading || undefined}
>
{asChild || !isLoading ? (
children
) : (
<>
<span aria-hidden className="invisible">
{children}
</span>
<span className="sr-only">{children}</span>
<span className="absolute inset-0 flex items-center justify-center">
<Spinner aria-hidden />
</span>
</>
)}
</ark.button>
);
};Update the import paths to match your project setup.
Usage
import { CirclePlusIcon } from "lucide-react";
import { Button } from "@/components/ui/button";<div className="flex flex-wrap gap-2">
<Button variant="outline">Button</Button>
<Button aria-label="Add" size="icon-md" variant="outline">
<CirclePlusIcon aria-hidden="true" />
</Button>
</div>Accessibility
| Key | Description |
|---|---|
Enter | Activate the focused control. |
Space | Activate the focused control. |
Demos
Custom Color
Disabled
Hitbox
Icon
Icon Sizes
Loading
No Click Effect
Pill
Size Lg
Size Md
Size Sm
Size Xl
Size Xs
State As Child
Variant Default
Variant Destructive
Variant Ghost
Variant Link
Variant Outline
Variant Secondary
With Icon
API Reference
Button
button
PropType
AttributeType