Skip to content
Corsair UI
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/button

No 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

asChild renders the child element with the button styles.

API

PropTypeDefault
variant

Visual weight.

"default" | "secondary" | "outline" | "ghost" | "destructive" | "link""default"
size

Height and padding; the icon sizes are square.

"sm" | "default" | "lg" | "icon-sm" | "icon" | "icon-lg""default"
loading

Shows a spinner, sets aria-busy and blocks clicks.

booleanfalse
asChild

Styles the child element, e.g. a link, instead of rendering a button.

booleanfalse

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 GitHub
import { 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 };