27.5k

Button

Migration guide for Button from HeroUI v2 to v3

Refer to the v3 Button documentation for complete API reference, styling guide, and advanced examples. This guide only focuses on migrating from HeroUI v2.

Overview

The Button component in HeroUI v3 has been simplified with variant-based styling replacing the separate color prop, and onClick has been renamed to onPress.

Structure Changes

v2: Single Component with Color and Variant Props

In v2, Button used a combination of color and variant props:

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

<Button color="primary" variant="solid">
  Button
</Button>

v3: Single Component with Variant Only

In v3, Button uses only the variant prop (no separate color prop):

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

<Button variant="primary">
  Button
</Button>

Key Changes

1. Event Handler: onClickonPress

v2: Supported both onClick (deprecated) and onPress
v3: Only onPress is supported

<Button onClick={() => console.log("clicked")}>Click me</Button>
<Button onPress={() => console.log("pressed")}>Click me</Button>

2. Variants and Colors

v2: Used color + variant combination
v3: Uses variant only (no separate color prop)

v2 Color + Variantv3 VariantNotes
color="primary" variant="solid"variant="primary"Default primary button
color="default" variant="solid"variant="primary"Use primary variant
color="secondary" variant="solid"variant="secondary"Same
color="success" variant="solid"variant="primary"Use primary with custom styling if needed
color="warning" variant="solid"variant="primary"Use primary with custom styling if needed
color="danger" variant="solid"variant="danger"Danger variant available
color="primary" variant="bordered"variant="secondary"Similar appearance
color="primary" variant="light"variant="tertiary"Similar appearance
color="primary" variant="flat"variant="tertiary"Similar appearance
color="primary" variant="faded"variant="secondary"Similar appearance
color="primary" variant="ghost"variant="ghost"Same
color="danger" variant="flat"variant="danger-soft"New soft danger variant

v2 Variants: solid, bordered, light, flat, faded, shadow, ghost
v3 Variants: primary, secondary, tertiary, ghost, danger, danger-soft

3. Loading State: isLoadingisPending

v2: Used isLoading prop
v3: Uses isPending prop

4. Removed Props

The following props are no longer available in v3:

  • color - Variants handle styling instead
  • radius - Use Tailwind CSS classes like rounded-lg, rounded-full, etc.
  • startContent / endContent - Place icons directly as children
  • spinner / spinnerPlacement - Handle loading state manually with render props
  • fullWidth - Use Tailwind CSS class w-full
  • disableRipple - Ripple effect removed in v3
  • disableAnimation - Animations are handled internally
  • classNames - Use className prop instead

5. ButtonGroup Removed

v2: Had a dedicated ButtonGroup component
v3: No ButtonGroup component - create groups manually with CSS classes

Migration Examples

Basic Usage

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

export default function App() {
  return <Button color="primary">Button</Button>;
}
import { Button } from "@heroui/react";

export default function App() {
  return <Button variant="primary">Button</Button>;
}

Variants

<Button color="primary" variant="solid">Solid</Button>
<Button color="primary" variant="bordered">Bordered</Button>
<Button color="primary" variant="light">Light</Button>
<Button color="primary" variant="flat">Flat</Button>
<Button color="primary" variant="ghost">Ghost</Button>
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="tertiary">Tertiary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="danger">Danger</Button>

Colors

<Button color="default">Default</Button>
<Button color="primary">Primary</Button>
<Button color="secondary">Secondary</Button>
<Button color="success">Success</Button>
<Button color="warning">Warning</Button>
<Button color="danger">Danger</Button>
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="tertiary">Tertiary</Button>
<Button variant="danger">Danger</Button>
<Button variant="danger-soft">Danger Soft</Button>
{/* For success/warning, use primary with custom styling */}
<Button variant="primary" className="bg-success text-white">
  Success
</Button>

With Icons

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

<Button 
  color="success" 
  endContent={<Icon icon="gravity-ui:camera" />}
>
  Take a photo
</Button>
<Button 
  color="danger" 
  startContent={<Icon icon="gravity-ui:trash-bin" />}
  variant="bordered"
>
  Delete user
</Button>
import { Icon } from "@iconify/react";

<Button variant="primary">
  <Icon icon="gravity-ui:camera" />
  Take a photo
</Button>
<Button variant="secondary">
  <Icon icon="gravity-ui:trash-bin" />
  Delete user
</Button>

Icon Only Button

<Button isIconOnly color="danger">
  <HeartIcon />
</Button>
import { Icon } from "@iconify/react";

<Button isIconOnly variant="danger">
  <Icon icon="gravity-ui:heart" />
</Button>

Loading State

<Button isLoading color="primary">
  Loading
</Button>
import { Spinner } from "@heroui/react";

<Button isPending>
  {({isPending}) => (
    <>
      {isPending ? <Spinner color="current" size="sm" /> : null}
      Loading
    </>
  )}
</Button>

Loading State with Conditional Content

import { useState } from "react";

const [isLoading, setIsLoading] = useState(false);

<Button 
  isLoading={isLoading}
  spinnerPlacement="start"
  color="primary"
  onPress={() => setIsLoading(true)}
>
  Upload File
</Button>
import { useState } from "react";
import { Spinner } from "@heroui/react";
import { Icon } from "@iconify/react";

const [isLoading, setIsLoading] = useState(false);

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

Full Width Button

<Button fullWidth color="primary">
  Full Width
</Button>
<Button className="w-full" variant="primary">
  Full Width
</Button>

Radius

<Button radius="full" color="primary">
  Rounded
</Button>
<Button radius="lg" color="primary">
  Large Radius
</Button>
<Button className="rounded-full" variant="primary">
  Rounded
</Button>
<Button className="rounded-lg" variant="primary">
  Large Radius
</Button>

Button Group

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

<ButtonGroup>
  <Button>One</Button>
  <Button>Two</Button>
  <Button>Three</Button>
</ButtonGroup>
<div className="inline-flex rounded-lg border border-divider" role="group">
  <Button className="rounded-r-none border-r border-divider">
    One
  </Button>
  <Button className="rounded-none border-r border-divider">
    Two
  </Button>
  <Button className="rounded-l-none">
    Three
  </Button>
</div>

Disabled State

<Button isDisabled color="primary">
  Disabled
</Button>
<Button isDisabled variant="primary">
  Disabled
</Button>

Sizes

<Button size="sm" color="primary">Small</Button>
<Button size="md" color="primary">Medium</Button>
<Button size="lg" color="primary">Large</Button>
<Button size="sm" variant="primary">Small</Button>
<Button size="md" variant="primary">Medium</Button>
<Button size="lg" variant="primary">Large</Button>

Render Props Pattern

v3 Button supports a render prop pattern that provides state information:

<Button isPending={isLoading}>
  {({isPending, isPressed, isHovered, isFocused, isDisabled}) => (
    <>
      {isPending && <Spinner size="sm" />}
      {isPressed ? "Pressed!" : "Click me"}
    </>
  )}
</Button>

Available render props:

  • isPending - Whether button is in loading state
  • isPressed - Whether button is currently pressed
  • isHovered - Whether button is hovered
  • isFocused - Whether button is focused
  • isFocusVisible - Whether button should show focus indicator
  • isDisabled - Whether button is disabled

Breaking Changes Summary

  1. onClick → onPress: Must use onPress instead of onClick
  2. Color Prop Removed: Use variant prop instead of color + variant
  3. Variants Changed: New variant system (primary, secondary, tertiary, ghost, danger, danger-soft)
  4. isLoading → isPending: Loading prop renamed
  5. Icons: startContent/endContent removed - place icons as children
  6. Loading Spinner: Must handle spinner manually with render props
  7. Radius Removed: Use Tailwind CSS classes
  8. FullWidth Removed: Use Tailwind w-full class
  9. Ripple Removed: No ripple effect in v3
  10. ButtonGroup Removed: Create groups manually with CSS
  11. ClassNames Removed: Use className prop

Tips for Migration

  1. Replace onClick: Change all onClick handlers to onPress
  2. Map variants: Use the variant mapping table to find equivalent v3 variants
  3. Handle loading: Use render props pattern for loading states with spinners
  4. Icons as children: Move icons from startContent/endContent to direct children
  5. Use Tailwind: Many removed props can be replaced with Tailwind utility classes
  6. Button groups: Use flexbox and border utilities to create button groups
  7. Custom colors: For success/warning colors, use variant="primary" with custom Tailwind classes

Need Help?

For v3 Button features and API:

For community support: