Button

A versatile button component with multiple variants and sizes. Perfect for actions, forms, and navigation.

Installation

Terminal
ReactTailwind
TSX
npx @uiblox/cli add button

Visual Variations

Toggle between three different visual styles: solid (filled), soft (tinted background), and outline (bordered).

Main call-to-action

Secondary actions

Non-interactive state

Basic Usage

Copy & paste ready
import { Button } from "@/components/ui";

<Button>Click me</Button>

Variants

Choose from 8 different button variants to match your design needs.

All Variants

Copy & paste ready
<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>
<Button variant="success">Success</Button>
<Button variant="warning">Warning</Button>

Sizes

Size Options

Copy & paste ready
<Button size="sm">Small</Button>
<Button size="default">Default</Button>
<Button size="lg">Large</Button>
<Button size="icon">
  <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
  </svg>
</Button>

Disabled State

Copy & paste ready
<Button disabled>Disabled</Button>
<Button variant="outline" disabled>Disabled Outline</Button>

With Icons

Copy & paste ready
<Button>
  <svg className="mr-2 h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
  </svg>
  Upload
</Button>
<Button variant="outline">
  Settings
  <svg className="ml-2 h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
  </svg>
</Button>

Props

PropTypeDefaultDescription
variant"default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | "success" | "warning""default"The visual style of the button
size"default" | "sm" | "lg" | "icon""default"The size of the button
disabledbooleanfalseWhether the button is disabled
classNamestring-Additional CSS classes to apply

Source Code

Copy this code into src/components/ui/button.tsx:

button.tsx
ReactTailwind
TSX
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

const buttonVariants = cva(
  "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
  {
    variants: {
      variant: {
        default: "bg-primary-600 text-white hover:bg-primary-700 focus-visible:ring-primary-500",
        destructive: "bg-red-600 text-white hover:bg-red-700 focus-visible:ring-red-500",
        outline: "border border-gray-300 dark:border-gray-700 bg-transparent hover:bg-gray-100 dark:hover:bg-gray-800",
        secondary: "bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100 hover:bg-gray-200 dark:hover:bg-gray-700",
        ghost: "hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-gray-100",
        link: "text-primary-600 underline-offset-4 hover:underline",
        success: "bg-green-600 text-white hover:bg-green-700 focus-visible:ring-green-500",
        warning: "bg-yellow-600 text-white hover:bg-yellow-700 focus-visible:ring-yellow-500",
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-8 rounded-md px-3 text-xs",
        lg: "h-12 rounded-md px-8 text-base",
        icon: "h-10 w-10",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
);

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, ...props }, ref) => {
    return (
      <button
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        {...props}
      />
    );
  }
);
Button.displayName = "Button";

export { Button, buttonVariants };

Accessibility

The Button component is built with accessibility in mind, following WAI-ARIA best practices.

Keyboard Support

  • Enter - Activates the button
  • Space - Activates the button
  • Tab - Moves focus to/from button

Built-in Features

  • Focus-visible ring for keyboard navigation
  • Semantic <button> element
  • Disabled state properly announced

Accessibility Examples

// Icon-only buttons need aria-label
<Button size="icon" aria-label="Close dialog">
  <XIcon className="h-4 w-4" />
</Button>

// Loading state with aria-busy
<Button disabled aria-busy="true">
  <Spinner className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" />
  Saving...
</Button>

// Destructive actions - confirm intent
<Button
  variant="destructive"
  aria-describedby="delete-warning"
>
  Delete Account
</Button>
<p id="delete-warning" className="sr-only">
  This action cannot be undone
</p>

// Toggle buttons use aria-pressed
<Button
  aria-pressed={isActive}
  onClick={() => setIsActive(!isActive)}
>
  {isActive ? "Active" : "Inactive"}
</Button>

// External links with announcement
<Button asChild>
  <a href="https://example.com" target="_blank" rel="noopener noreferrer">
    Visit Site
    <span className="sr-only">(opens in new tab)</span>
    <ExternalLinkIcon className="ml-2 h-4 w-4" aria-hidden="true" />
  </a>
</Button>

Best Practices

  • • Use descriptive button text that explains the action
  • • Always add aria-label to icon-only buttons
  • • Use aria-busy="true" during loading states
  • • Don't disable buttons without explaining why
  • • Ensure sufficient color contrast (4.5:1 minimum)

Customization

Adding new variants

To add a new variant, simply extend the variants object in buttonVariants:

const buttonVariants = cva("...", {
  variants: {
    variant: {
      // existing variants...
      custom: "bg-purple-600 text-white hover:bg-purple-700",
    },
  },
});

Changing default styles

Modify the base classes in the first argument to cva() to change the default appearance of all buttons.