Button

A clickable button component with multiple variants and states

Import

import { Button } from '@heroui/react';

Usage

"use client";

import {Button} from "@heroui/react";

export function Basic() {
  return <Button onPress={() => console.log("Button pressed")}>Click me</Button>;
}

Variants

import {Button} from "@heroui/react";

export function Variants() {
  return (
    <div className="flex flex-wrap gap-3">
      <Button>Primary</Button>
      <Button variant="secondary">Secondary</Button>
      <Button variant="tertiary">Tertiary</Button>
      <Button variant="ghost">Ghost</Button>
      <Button variant="danger">Danger</Button>
    </div>
  );
}

With Icons

import {Button} from "@heroui/react";
import {Icon} from "@iconify/react";

export function WithIcons() {
  return (
    <div className="flex flex-wrap gap-3">
      <Button>
        <Icon icon="gravity-ui:globe" />
        Search
      </Button>
      <Button variant="secondary">
        <Icon icon="gravity-ui:plus" />
        Add Member
      </Button>
      <Button variant="tertiary">
        <Icon icon="gravity-ui:envelope" />
        Email
      </Button>
      <Button variant="danger">
        <Icon icon="gravity-ui:trash-bin" />
        Delete
      </Button>
    </div>
  );
}

Icon Only

import {Button} from "@heroui/react";
import {Icon} from "@iconify/react";

export function IconOnly() {
  return (
    <div className="flex gap-3">
      <Button isIconOnly variant="tertiary">
        <Icon icon="gravity-ui:ellipsis" />
      </Button>
      <Button isIconOnly variant="secondary">
        <Icon icon="gravity-ui:gear" />
      </Button>
      <Button isIconOnly variant="danger">
        <Icon icon="gravity-ui:trash-bin" />
      </Button>
    </div>
  );
}

Loading

"use client";

import {Button, Spinner} from "@heroui/react";
import React from "react";

export function Loading() {
  return (
    <Button isPending>
      {({isPending}) => (
        <>
          {isPending ? <Spinner size="sm" /> : null}
          Uploading...
        </>
      )}
    </Button>
  );
}

Loading State

"use client";

import {Button, Spinner} from "@heroui/react";
import {Icon} from "@iconify/react";
import React, {useState} from "react";

export function LoadingState() {
  const [isLoading, setLoading] = useState(false);

  const handlePress = () => {
    setLoading(true);
    setTimeout(() => setLoading(false), 2000);
  };

  return (
    <Button isPending={isLoading} onPress={handlePress}>
      {({isPending}) => (
        <>
          {isPending ? <Spinner size="sm" /> : <Icon icon="gravity-ui:paperclip" />}
          {isPending ? "Uploading..." : "Upload File"}
        </>
      )}
    </Button>
  );
}

Sizes

import {Button} from "@heroui/react";

export function Sizes() {
  return (
    <div className="flex items-center gap-3">
      <Button size="sm">Small</Button>
      <Button size="md">Medium</Button>
      <Button size="lg">Large</Button>
    </div>
  );
}

Disabled State

import {Button} from "@heroui/react";

export function Disabled() {
  return (
    <div className="flex gap-3">
      <Button isDisabled>Primary</Button>
      <Button isDisabled variant="secondary">
        Secondary
      </Button>
      <Button isDisabled variant="tertiary">
        Tertiary
      </Button>
      <Button isDisabled variant="ghost">
        Ghost
      </Button>
      <Button isDisabled variant="danger">
        Danger
      </Button>
    </div>
  );
}

Social Buttons

import {Button} from "@heroui/react";
import {Icon} from "@iconify/react";

export function Social() {
  return (
    <div className="flex w-full max-w-xs flex-col gap-3">
      <Button className="w-full" variant="tertiary">
        <Icon icon="devicon:google" />
        Sign in with Google
      </Button>
      <Button className="w-full" variant="tertiary">
        <Icon icon="mdi:github" />
        Sign in with GitHub
      </Button>
      <Button className="w-full" variant="tertiary">
        <Icon icon="ion:logo-apple" />
        Sign in with Apple
      </Button>
    </div>
  );
}

Styling

Passing Tailwind CSS classes

import { Button } from '@heroui/react';

function CustomButton() {
  return (
    <Button className="bg-purple-500 text-white hover:bg-purple-600">
      Purple Button
    </Button>
  );
}

Customizing the component classes

To customize the Button component classes, you can use the @layer components directive.
Learn more.

@layer components {
  .button {
    @apply bg-purple-500 text-white hover:bg-purple-600;
  }

  .button--icon-only {
    @apply rounded-lg bg-blue-500;
  }
}

HeroUI follows the BEM methodology to ensure component variants and states are reusable and easy to customize.

Adding custom variants

You can extend HeroUI components by wrapping them and adding your own custom variants.

import type {ButtonProps} from "@heroui/react";
import type {VariantProps} from "tailwind-variants";

import {Button, buttonVariants} from "@heroui/react";
import {tv} from "tailwind-variants";

const myButtonVariants = tv({
  base: "text-md text-shadow-lg font-semibold shadow-md data-[pending=true]:opacity-40",
  defaultVariants: {
    radius: "full",
    variant: "primary",
  },
  extend: buttonVariants,
  variants: {
    radius: {
      full: "rounded-full",
      lg: "rounded-lg",
      md: "rounded-md",
      sm: "rounded-sm",
    },
    size: {
      lg: "h-12 px-8",
      md: "h-11 px-6",
      sm: "h-10 px-4",
      xl: "h-13 px-10",
    },
    variant: {
      primary: "text-white dark:bg-white/10 dark:text-white dark:hover:bg-white/15",
    },
  },
});

type MyButtonVariants = VariantProps<typeof myButtonVariants>;
export type MyButtonProps = Omit<ButtonProps, "className"> &
  MyButtonVariants & {className?: string};

function CustomButton({className, radius, variant, ...props}: MyButtonProps) {
  return <Button className={myButtonVariants({className, radius, variant})} {...props} />;
}

export function CustomVariants() {
  return <CustomButton>Custom Button</CustomButton>;
}

CSS Classes

The Button component uses these CSS classes (View source styles):

Base & Size Classes

  • .button - Base button styles
  • .button--sm - Small size variant
  • .button--md - Medium size variant
  • .button--lg - Large size variant

Variant Classes

  • .button--primary
  • .button--secondary
  • .button--tertiary
  • .button--ghost
  • .button--danger

Modifier Classes

  • .button--icon-only
  • .button--icon-only.button--sm
  • .button--icon-only.button--lg

Interactive States

The button supports both CSS pseudo-classes and data attributes for flexibility:

  • Hover: :hover or [data-hover="true"]
  • Active/Pressed: :active or [data-pressed="true"] (includes scale transform)
  • Focus: :focus-visible or [data-focus-visible="true"] (shows focus ring)
  • Disabled: :disabled or [aria-disabled="true"] (reduced opacity, no pointer events)
  • Pending: [data-pending] (no pointer events during loading)

API Reference

Button Props

PropTypeDefaultDescription
variant'primary' | 'secondary' | 'tertiary' | 'ghost' | 'danger''primary'Visual style variant
size'sm' | 'md' | 'lg''md'Size of the button
isDisabledbooleanfalseWhether the button is disabled
isPendingbooleanfalseWhether the button is in a loading state
isIconOnlybooleanfalseWhether the button contains only an icon
onPress(e: PressEvent) => void-Handler called when the button is pressed
childrenReact.ReactNode | (values: ButtonRenderProps) => React.ReactNode-Button content or render prop

ButtonRenderProps

When using the render prop pattern, these values are provided:

PropTypeDescription
isPendingbooleanWhether the button is in a loading state
isPressedbooleanWhether the button is currently pressed
isHoveredbooleanWhether the button is hovered
isFocusedbooleanWhether the button is focused
isFocusVisiblebooleanWhether the button should show focus indicator
isDisabledbooleanWhether the button is disabled