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
Prop | Type | Default | Description |
---|---|---|---|
variant | 'primary' | 'secondary' | 'tertiary' | 'ghost' | 'danger' | 'primary' | Visual style variant |
size | 'sm' | 'md' | 'lg' | 'md' | Size of the button |
isDisabled | boolean | false | Whether the button is disabled |
isPending | boolean | false | Whether the button is in a loading state |
isIconOnly | boolean | false | Whether the button contains only an icon |
onPress | (e: PressEvent) => void | - | Handler called when the button is pressed |
children | React.ReactNode | (values: ButtonRenderProps) => React.ReactNode | - | Button content or render prop |
ButtonRenderProps
When using the render prop pattern, these values are provided:
Prop | Type | Description |
---|---|---|
isPending | boolean | Whether the button is in a loading state |
isPressed | boolean | Whether the button is currently pressed |
isHovered | boolean | Whether the button is hovered |
isFocused | boolean | Whether the button is focused |
isFocusVisible | boolean | Whether the button should show focus indicator |
isDisabled | boolean | Whether the button is disabled |