apps/docs/content/docs/react/migration/(components)/button.mdx
In v2, Button used a combination of color and variant props:
import { Button } from "@heroui/react";
export default function App() {
return <Button color="primary" variant="solid">Button</Button>;
}
In v3, Button uses only the variant prop (no separate color prop):
import { Button } from "@heroui/react";
export default function App() {
return <Button variant="primary">Button</Button>;
}
v2: Used color + variant combination
v3: Uses variant only (no separate color prop)
| v2 Color + Variant | v3 Variant | Notes |
|---|---|---|
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, outline, ghost, danger, danger-soft
isLoading → isPendingv2: Used isLoading prop
v3: Uses isPending prop
v2: Buttons had minimum widths based on size (min-w-16 for sm, min-w-20 for md, min-w-24 for lg)
v3: Buttons use w-fit by default (width fits content, no minimum width)
This means v3 buttons will be narrower than v2 buttons when they have short text. To maintain v2's minimum width behavior, add Tailwind classes see the Minimum Width example section.
| v2 Prop | v3 Location | Notes |
|---|---|---|
isLoading | Button | Renamed to isPending |
isIconOnly | Button | Still available in v3 — renders a square button with only an icon |
color | - | Removed (variants handle styling) |
radius | - | Removed (use Tailwind e.g. rounded-lg) |
startContent, endContent | - | Place icons as children |
spinner, spinnerPlacement | - | Handle loading manually with render props |
disableRipple | - | Removed (ripple removed in v3) |
disableAnimation | - | Removed (animations handled internally) |
classNames | - | Use className |
v2: Had a dedicated ButtonGroup component
v3: ButtonGroup continues to exist. See the ButtonGroup migration guide for details.
<Tabs items={["v2", "v3"]}>
<Tab value="v2">
tsx <Button color="primary" variant="solid">Solid</Button> <Button color="primary" variant="bordered">Bordered</Button> <Button color="primary" variant="ghost">Ghost</Button>
</Tab>
<Tab value="v3">
tsx <Button variant="primary">Primary</Button> <Button variant="secondary">Secondary</Button> <Button variant="ghost">Ghost</Button>
</Tab>
</Tabs>
The danger-soft variant is new in v3. It replaces the v2 pattern of using a flat danger button.
<Tabs items={["v2", "v3"]}>
<Tab value="v2">
tsx <Button color="danger" variant="flat">Delete</Button>
</Tab>
<Tab value="v3">
tsx <Button variant="danger-soft">Delete</Button>
</Tab>
</Tabs>
isIconOnly)The isIconOnly prop is available in both v2 and v3. It renders a square button sized to fit a single icon. In v3, use variant instead of color for styling.
<Tabs items={["v2", "v3"]}>
<Tab value="v2">
tsx <Button isIconOnly color="danger" variant="flat"> <HeartIcon /> </Button> <Button isIconOnly color="primary" variant="bordered" size="sm"> <SearchIcon /> </Button>
</Tab>
<Tab value="v3">
```tsx
import { Icon } from "@iconify/react";
<Button isIconOnly variant="danger-soft">
<Icon icon="gravity-ui:heart" />
</Button>
<Button isIconOnly variant="secondary" size="sm">
<Icon icon="gravity-ui:magnifier" />
</Button>
```
<Tabs items={["v2", "v3"]}>
<Tab value="v2">
tsx <Button isLoading color="primary"> Loading </Button> <Button isLoading={isLoading} spinnerPlacement="start" color="primary" onPress={() => setIsLoading(true)} > Upload File </Button>
</Tab>
<Tab value="v3">
```tsx
import { useState } from "react";
import { Spinner } from "@heroui/react";
import { Icon } from "@iconify/react";
const [isLoading, setIsLoading] = useState(false);
<Button isPending={isLoading}>
{({isPending}) => (
<>
{isPending && <Spinner color="current" size="sm" />}
Loading
</>
)}
</Button>
<Button
isPending={isLoading}
onPress={() => setIsLoading(true)}
>
{({isPending}) => (
<>
{isPending ? (
<Spinner color="current" size="sm" />
) : (
<Icon icon="gravity-ui:paperclip" />
)}
{isPending ? "Uploading..." : "Upload File"}
</>
)}
</Button>
```
<Tabs items={["v2", "v3"]}> <Tab value="v2"> ```tsx 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>
```
<Button variant="primary">
<Icon icon="gravity-ui:camera" />
Take a photo
</Button>
<Button variant="secondary">
<Icon icon="gravity-ui:trash-bin" />
Delete user
</Button>
```
<Tabs items={["v2", "v3"]}>
<Tab value="v2">
tsx <Button isIconOnly color="danger"> <HeartIcon /> </Button>
</Tab>
<Tab value="v3">
```tsx
import { Icon } from "@iconify/react";
<Button isIconOnly variant="danger">
<Icon icon="gravity-ui:heart" />
</Button>
```
<Tabs items={["v2", "v3"]}> <Tab value="v2"> ```tsx import { Button, ButtonGroup } from "@heroui/react";
<ButtonGroup size="sm" color="primary" variant="solid">
<Button>One</Button>
<Button>Two</Button>
<Button>Three</Button>
</ButtonGroup>
```
<ButtonGroup size="sm" variant="primary">
<Button>One</Button>
<Button>Two</Button>
<Button>Three</Button>
</ButtonGroup>
```
<Tabs items={["v2", "v3"]}>
<Tab value="v2">
tsx <Button size="md" color="primary">Save</Button>
</Tab>
<Tab value="v3">
tsx <Button size="md" variant="primary" className="min-w-20"> Save </Button>
</Tab>
</Tabs>
v3 Button supports a render prop pattern that provides state information:
<Button isPending={isLoading}>
{({isPending, isPressed, isHovered, isFocused, isFocusVisible, isDisabled}) => (
<>
{isPending && <Spinner size="sm" />}
{isPressed ? "Pressed!" : "Click me"}
</>
)}
</Button>
Available render props:
isPending - Whether button is in loading stateisPressed - Whether button is currently pressedisHovered - Whether button is hoveredisFocused - Whether button is focusedisFocusVisible - Whether button should show focus indicatorisDisabled - Whether button is disabledvariant prop instead of color + variantprimary, secondary, tertiary, outline, ghost, danger, danger-soft)danger-soft Variant: New in v3, replaces v2's color="danger" variant="flat" patternw-fit instead of minimum widths - add min-w-* classes to match v2 behaviorstartContent/endContent removed - place icons as childrenisPending, isPressed, isHovered, isFocused, isFocusVisible, and isDisabledclassName prop