Button
ButtonsA polymorphic button with five variants and four sizes, built on Radix Slot so it can render as any element via `asChild`.
"use client";
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-base text-sm font-medium transition-[color,background-color,border-color,box-shadow,transform] duration-150 outline-none focus-visible:ring-2 focus-visible:ring-accent/60 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 active:translate-y-px [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
primary:
"bg-accent text-accent-fg shadow-sm hover:opacity-90",
secondary:
"border border-border bg-surface text-foreground hover:bg-surface-2",
ghost: "text-foreground hover:bg-surface-2",
outline:
"border border-border bg-transparent text-foreground hover:bg-surface-2",
link: "text-accent underline-offset-4 hover:underline",
},
size: {
sm: "h-8 px-3 text-xs",
md: "h-9 px-4",
lg: "h-11 px-6 text-base",
icon: "size-9",
},
},
defaultVariants: {
variant: "primary",
size: "md",
},
},
);
export interface ButtonProps
extends React.ComponentProps<"button">,
VariantProps<typeof buttonVariants> {
/** Render as the child element (e.g. an anchor) while keeping button styles. */
asChild?: boolean;
}
export function Button({
className,
variant,
size,
asChild = false,
...props
}: ButtonProps) {
const Comp = asChild ? Slot : "button";
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
);
}
export { buttonVariants };Overview
A foundational button built on class-variance-authority for type-safe variants
and Radix Slot for polymorphism. It is the building block most other components
compose with.
When to use
Use the primary variant for the single most important action in a view, and secondary / outline / ghost for everything supporting. Reserve link for inline, low-emphasis actions.
Polymorphism
Because the button is built on Radix Slot, the asChild prop lets it render as
any element while keeping its styling — most often a Next.js Link:
<Button asChild><Link href="/x">Go</Link></Button>
This keeps a single source of truth for button styling instead of duplicating classes onto anchors.
Accessibility
Renders a native <button> by default, so keyboard activation and focus come for
free. A visible focus-visible ring is included. When using asChild, make sure
the child is an interactive element (a link or button).
Installation
npx shadcn@latest add https://ui.saumyarex.xyz/r/button.json1. Install dependencies
npm install @radix-ui/react-slot class-variance-authority clsx tailwind-merge2. Copy the source into your project
"use client";
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-base text-sm font-medium transition-[color,background-color,border-color,box-shadow,transform] duration-150 outline-none focus-visible:ring-2 focus-visible:ring-accent/60 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 active:translate-y-px [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
primary:
"bg-accent text-accent-fg shadow-sm hover:opacity-90",
secondary:
"border border-border bg-surface text-foreground hover:bg-surface-2",
ghost: "text-foreground hover:bg-surface-2",
outline:
"border border-border bg-transparent text-foreground hover:bg-surface-2",
link: "text-accent underline-offset-4 hover:underline",
},
size: {
sm: "h-8 px-3 text-xs",
md: "h-9 px-4",
lg: "h-11 px-6 text-base",
icon: "size-9",
},
},
defaultVariants: {
variant: "primary",
size: "md",
},
},
);
export interface ButtonProps
extends React.ComponentProps<"button">,
VariantProps<typeof buttonVariants> {
/** Render as the child element (e.g. an anchor) while keeping button styles. */
asChild?: boolean;
}
export function Button({
className,
variant,
size,
asChild = false,
...props
}: ButtonProps) {
const Comp = asChild ? Slot : "button";
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
);
}
export { buttonVariants };import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
/** Merge conditional class names and resolve Tailwind conflicts. */
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "primary" | "secondary" | "ghost" | "outline" | "link" | "primary" | Visual style of the button. |
size | "sm" | "md" | "lg" | "icon" | "md" | Control the height and padding. |
asChild | boolean | false | Merge props onto the child element (e.g. a Next <Link>) instead of rendering a <button>. |
...props | React.ComponentProps<"button"> | — | All native button attributes are forwarded. |