CatalystUI

DocsTemplate

Button

Buttons with multiple variants

Installations

Install lucide-react, clsx, and tailwind-merge to use button component.

npm install lucide-react clsx tailwind-merge

Add the following function in lib/utils

import clsx, { type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
 
export function cx(...args: ClassValue[]) {
  return twMerge(clsx(...args));
}

Copy paste the component

import React, { ReactNode } from "react";
 
import { cx } from "@/lib/utils";
import { Loader2Icon } from "lucide-react";
 
const buttonVariants = {
  base: [
    // base
    "relative inline-flex items-center justify-center whitespace-nowrap rounded-md border px-3 py-2 text-center text-sm font-medium shadow-xs",
    // disabled
    "disabled:pointer-event-none disabled:shadow-none",
  ],
  variants: {
    primary: [
      // border
      "border-transparent",
      // text color
      "text-white dark:text-white",
      // background color
      "bg-blue-500 dark:bg-blue-500",
      // hover color
      "hover:bg-blue-600 dark:hover:bg-blue-600",
      // disabled
      "disabled:bg-blue-300 disabled:text-white",
      "dark:disabled:bg-blue-800 dark:disabled:text-blue-400",
    ],
    secondary: [
      // border
      "border-gray-300 dark:border-gray-800",
      // text color
      "text-gray-900 dark:text-gray-50",
      // background color
      "bg-white dark:bg-gray-950",
      // hover color
      "hover:bg-gray-50 dark:hover:bg-gray-900/60",
      // disabled
      "disabled:text-gray-400",
      "dark:disabled:text-gray-600",
    ],
    ghost: [
      // base
      "shadow-none",
      // border
      "border-transparent",
      // text color
      "text-gray-900 dark:text-gray-50",
      // hover color
      "bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800/80",
      // disabled
      "disabled:text-gray-400",
      "dark:disabled:text-gray-600",
    ],
    destructive: [
      // base
      "text-white",
      // border
      "border-transparent",
      // background color
      "bg-red-600 dark:bg-red-700",
      // hover color
      "hover:bg-red-700 dark:hover-red-600",
      // disabled
      "disabled:bg-red-300 disabled:text-white",
      "dark:disabled:bg-red-950 dark:disabled:text-red-400",
    ],
  },
  defaultVariant: "primary",
};
 
interface ButtonProps {
  isLoading?: boolean;
  className?: string;
  disabled?: false;
  variant?: keyof typeof buttonVariants.variants;
  children: ReactNode;
}
 
function Button({
  isLoading = false,
  className,
  disabled = false,
  variant = "primary",
  children,
  ...props
}: ButtonProps) {
  return (
    <button
      className={cx(
        ...buttonVariants.base,
        ...buttonVariants.variants[variant],
        className
      )}
      disabled={disabled || isLoading}
      {...props}
    >
      {isLoading ? (
        <span className="pointer-events-none flex shrink-0 items-center gap-1.5">
          <Loader2Icon
            className="size-4 shrink-0 animate-spin"
            aria-hidden={true}
          />
          <span>Loading...</span>
        </span>
      ) : (
        children
      )}
    </button>
  );
}
 
Button.displayName = "Button";
 
export { Button, buttonVariants, type ButtonProps };

Usage

import { Button } from "@/library/components/ui/button";
 
export default function ButtonSet() {
  return (
    <CardRoot className="flex gap-4 flex-wrap">
      <Button variant="primary">Primary</Button>
      <Button variant="secondary">Secondary</Button>
      <Button variant="ghost">Ghost</Button>
      <Button variant="destructive">Destructive</Button>
    </CardRoot>
  );
}

Next Card

Found a bug? Report here.