Build websites 10x faster with HextaUI Blocks — Learn more
UI/UI

Button

Displays a button component with various styles and states.

<div className="flex gap-4 flex-wrap">
  <Button>Default</Button>
  <Button variant="secondary">Secondary</Button>
  <Button variant="outline">Outline</Button>
  <Button variant="ghost">Ghost</Button>
  <Button variant="link">Link</Button>
  <Button variant="destructive">Destructive</Button>
</div>

Installation

Install following dependencies:

npm install @radix-ui/react-slot class-variance-authority
pnpm add @radix-ui/react-slot class-variance-authority
yarn add @radix-ui/react-slot class-variance-authority
bun add @radix-ui/react-slot class-variance-authority

Copy and paste the following code into your project.

components/ui/button.tsx
"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-[var(--radius)] text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
  {
    variants: {
      variant: {
        default:
          "bg-[hsl(var(--hu-primary))] text-[hsl(var(--hu-primary-foreground))] hover:bg-[hsl(var(--hu-primary))]/90 focus-visible:ring-[hsl(var(--hu-ring))]",
        destructive:
          "bg-[hsl(var(--hu-destructive))] text-[hsl(var(--hu-destructive-foreground))] hover:bg-[hsl(var(--hu-destructive))]/90 focus-visible:ring-[hsl(var(--hu-destructive))]",
        outline:
          "border border-[hsl(var(--hu-border))] text-[hsl(var(--hu-foreground))] hover:bg-[hsl(var(--hu-accent))] hover:text-[hsl(var(--hu-accent-foreground))] focus-visible:ring-[hsl(var(--hu-ring))]",
        secondary:
          "bg-[hsl(var(--hu-secondary))] text-[hsl(var(--hu-secondary-foreground))] hover:bg-[hsl(var(--hu-secondary))]/80 focus-visible:ring-[hsl(var(--hu-ring))]",
        ghost:
          "text-[hsl(var(--hu-foreground))] hover:bg-[hsl(var(--hu-accent))] hover:text-[hsl(var(--hu-accent-foreground))] focus-visible:ring-[hsl(var(--hu-ring))]",
        link: "text-[hsl(var(--hu-secondary-foreground))] underline-offset-4 hover:underline focus-visible:ring-[hsl(var(--hu-ring))]",
      },
      size: {
        default: "h-9 px-4 py-2",
        sm: "h-8 px-3 text-xs",
        lg: "h-10 px-8",
        xl: "h-12 px-10 text-base",
        icon: "h-9 w-9",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  },
);

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean;
  loading?: boolean;
  leftIcon?: React.ReactNode;
  rightIcon?: React.ReactNode;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  (
    {
      className,
      variant,
      size,
      asChild = false,
      loading = false,
      leftIcon,
      rightIcon,
      children,
      disabled,
      ...props
    },
    ref,
  ) => {
    const Comp = asChild ? Slot : "button";

    return (
      <Comp
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        disabled={disabled || loading}
        {...props}
      >
        {loading && (
          <svg
            className="animate-spin -ml-1 mr-2 h-4 w-4"
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 24 24"
          >
            <circle
              className="opacity-25"
              cx="12"
              cy="12"
              r="10"
              stroke="currentColor"
              strokeWidth="4"
            />
            <path
              className="opacity-75"
              fill="currentColor"
              d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
            />
          </svg>
        )}
        {leftIcon && !loading && leftIcon}
        {children}
        {rightIcon && !loading && rightIcon}
      </Comp>
    );
  },
);

Button.displayName = "Button";

export { Button, buttonVariants };
npx hextaui@latest add button
pnpm dlx hextaui@latest add button
yarn dlx hextaui@latest add button
bun x hextaui@latest add button

Usage

import { Button } from "@/components/ui/Button";
<Button>Click me</Button>

Examples

Default

<Button>Click me</Button>

Variants

<Button variant="default">Default</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>
<Button variant="destructive">Destructive</Button>

Sizes

<Button size="sm">Small</Button>
<Button size="default">Default</Button>
<Button size="lg">Large</Button>
<Button size="xl">Extra Large</Button>

With Icons

<Button leftIcon={<Download />}>Download</Button>
<Button rightIcon={<ArrowRight />}>Continue</Button>
<Button variant="outline" leftIcon={<Heart />}>
  Like
</Button>
<Button variant="secondary" rightIcon={<Settings />}>
  Settings
</Button>

Icon Only

<Button variant="outline" size="icon">
  <Settings />
</Button>
<Button variant="ghost" size="icon">
  <Heart />
</Button>
<Button variant="destructive" size="icon">
  <Trash2 />
</Button>

Loading State

<Button loading>Loading...</Button>
<Button variant="outline" loading>
  Please wait
</Button>
<Button variant="secondary" loading disabled>
  Processing
</Button>

You can use the asChild prop to make another component look like a button. Here's an example of a link that looks like a button.

import { Button } from "@/components/ui/Button";
import Link from "next/link";

export function LinkAsButton() {
  return (
    <Button asChild>
      <Link href="/login">Login</Link>
    </Button>
  );
}

Props

PropTypeDefault
className?
string
undefined
disabled?
boolean
false
rightIcon?
React.ReactNode
undefined
leftIcon?
React.ReactNode
undefined
loading?
boolean
false
asChild?
boolean
false
size?
"default" | "sm" | "lg" | "xl" | "icon"
"default"
variant?
"default" | "destructive" | "outline" | "secondary" | "ghost" | "link"
"default"
Edit on GitHub

Last updated on