Pro--% off in--d : --h : --m : --s
HeroUI

Checkbox

Migration guide for Checkbox from HeroUI v2 to v3

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

Structure Changes

In v2, Checkbox accepted children as the label:

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

export default function App() {
  return <Checkbox defaultSelected>Option</Checkbox>;
}

In v3, Checkbox uses a compound component pattern with explicit subcomponents:

import { Checkbox, Label } from "@heroui/react";

export default function App() {
  return (
    <Checkbox defaultSelected id="option">
      <Checkbox.Control>
        <Checkbox.Indicator />
      </Checkbox.Control>
      <Checkbox.Content>
        <Label htmlFor="option">Option</Label>
      </Checkbox.Content>
    </Checkbox>
  );
}

Key Changes

1. Component Structure

v2: Simple component with children as label
v3: Compound components: Checkbox.Control, Checkbox.Indicator, Checkbox.Content

2. Label Handling

v2: Label passed as children directly to Checkbox
v3: Label must be wrapped in Checkbox.Content using the Label component

3. Prop Changes

v2 Propv3 LocationNotes
onValueChangeCheckboxUse onChange
color-Removed (use Tailwind on Checkbox.Control)
size-Removed (use Tailwind e.g. size-4, size-5)
radius-Removed (use Tailwind e.g. rounded-sm)
lineThrough-Removed (use Tailwind line-through on label)
icon-Use custom content in Checkbox.Indicator
classNames-Use className on parts
disableAnimation-Removed
variantNew prop: "primary" (default) or "secondary" for lower emphasis styling

4. CheckboxGroup Changes

v2: CheckboxGroup (separate component) with label prop and simple Checkbox children
v3: CheckboxGroup (separate component) with Label/Description as children and compound Checkbox structure

For migrating checkbox groups, see the CheckboxGroup migration guide.

Migration Examples

Controlled Checkbox

import { useState } from "react";

const [isSelected, setIsSelected] = useState(false);

<Checkbox isSelected={isSelected} onValueChange={setIsSelected}>
  Subscribe
</Checkbox>
import { useState } from "react";
import { Label } from "@heroui/react";

const [isSelected, setIsSelected] = useState(false);

<Checkbox id="subscribe" isSelected={isSelected} onChange={setIsSelected}>
  <Checkbox.Control>
    <Checkbox.Indicator />
  </Checkbox.Control>
  <Checkbox.Content>
    <Label htmlFor="subscribe">Subscribe</Label>
  </Checkbox.Content>
</Checkbox>

Checkbox with Description

{/* v2 doesn't have built-in description support */}
<Checkbox defaultSelected>
  Option
</Checkbox>
<p className="text-sm text-default-500">Description text</p>
import { Checkbox, Label, Description } from "@heroui/react";

<Checkbox id="option" defaultSelected>
  <Checkbox.Control>
    <Checkbox.Indicator />
  </Checkbox.Control>
  <Checkbox.Content>
    <Label htmlFor="option">Option</Label>
    <Description>Description text</Description>
  </Checkbox.Content>
</Checkbox>

CheckboxGroup

import { Checkbox, CheckboxGroup } from "@heroui/react";

<CheckboxGroup label="Select cities">
  <Checkbox value="buenos-aires">Buenos Aires</Checkbox>
  <Checkbox value="sydney">Sydney</Checkbox>
  <Checkbox value="san-francisco">San Francisco</Checkbox>
</CheckboxGroup>
import { CheckboxGroup, Checkbox, Label, Description } from "@heroui/react";

<CheckboxGroup name="cities">
  <Label>Select cities</Label>
  <Description>Choose all that apply</Description>
  <Checkbox value="buenos-aires">
    <Checkbox.Control>
      <Checkbox.Indicator />
    </Checkbox.Control>
    <Checkbox.Content>
      <Label>Buenos Aires</Label>
    </Checkbox.Content>
  </Checkbox>
  <Checkbox value="sydney">
    <Checkbox.Control>
      <Checkbox.Indicator />
    </Checkbox.Control>
    <Checkbox.Content>
      <Label>Sydney</Label>
    </Checkbox.Content>
  </Checkbox>
  <Checkbox value="san-francisco">
    <Checkbox.Control>
      <Checkbox.Indicator />
    </Checkbox.Control>
    <Checkbox.Content>
      <Label>San Francisco</Label>
    </Checkbox.Content>
  </Checkbox>
</CheckboxGroup>

Colors and Sizes

{/* Colors */}
<Checkbox defaultSelected color="primary">Primary</Checkbox>

{/* Sizes */}
<Checkbox defaultSelected size="md">Medium</Checkbox>
{/* Colors - use Tailwind classes */}
<Checkbox defaultSelected id="primary">
  <Checkbox.Control className="data-[selected=true]:bg-primary data-[selected=true]:border-primary">
    <Checkbox.Indicator />
  </Checkbox.Control>
  <Checkbox.Content>
    <Label htmlFor="primary">Primary</Label>
  </Checkbox.Content>
</Checkbox>

{/* Sizes - use Tailwind classes */}
<Checkbox defaultSelected id="medium">
  <Checkbox.Control className="size-5">
    <Checkbox.Indicator />
  </Checkbox.Control>
  <Checkbox.Content>
    <Label htmlFor="medium">Medium</Label>
  </Checkbox.Content>
</Checkbox>

Custom Icon/Indicator

<Checkbox defaultSelected icon={<HeartIcon />}>
  Option
</Checkbox>
<Checkbox defaultSelected id="option">
  <Checkbox.Control>
    <Checkbox.Indicator>
      {({isSelected}) => isSelected ? <HeartIcon /> : null}
    </Checkbox.Indicator>
  </Checkbox.Control>
  <Checkbox.Content>
    <Label htmlFor="option">Option</Label>
  </Checkbox.Content>
</Checkbox>

Variants

v3 introduces a variant prop with "primary" (default) and "secondary" options:

{/* Primary variant (default) */}
<Checkbox variant="primary" id="primary-option">
  <Checkbox.Control>
    <Checkbox.Indicator />
  </Checkbox.Control>
  <Checkbox.Content>
    <Label htmlFor="primary-option">Primary</Label>
  </Checkbox.Content>
</Checkbox>

{/* Secondary variant - lower emphasis, suitable for Surface components */}
<Checkbox variant="secondary" id="secondary-option">
  <Checkbox.Control>
    <Checkbox.Indicator />
  </Checkbox.Control>
  <Checkbox.Content>
    <Label htmlFor="secondary-option">Secondary</Label>
  </Checkbox.Content>
</Checkbox>

Indeterminate State

<Checkbox isIndeterminate>Option</Checkbox>
<Checkbox isIndeterminate id="option">
  <Checkbox.Control>
    <Checkbox.Indicator />
  </Checkbox.Control>
  <Checkbox.Content>
    <Label htmlFor="option">Option</Label>
  </Checkbox.Content>
</Checkbox>

Render Props Pattern

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

<Checkbox id="terms">
  {({isSelected, isIndeterminate, isHovered, isPressed, isFocused, isDisabled}) => (
    <>
      <Checkbox.Control>
        <Checkbox.Indicator />
      </Checkbox.Control>
      <Checkbox.Content>
        <Label htmlFor="terms">
          {isSelected ? "Terms accepted" : "Accept terms"}
        </Label>
      </Checkbox.Content>
    </>
  )}
</Checkbox>

Checkbox.Indicator also supports a render prop pattern, providing the same state values. This is useful for rendering custom indicator content based on the checkbox state:

<Checkbox id="custom">
  <Checkbox.Control>
    <Checkbox.Indicator>
      {({isSelected, isIndeterminate}) =>
        isIndeterminate ? <MinusIcon /> : isSelected ? <CheckIcon /> : null
      }
    </Checkbox.Indicator>
  </Checkbox.Control>
  <Checkbox.Content>
    <Label htmlFor="custom">Custom indicator</Label>
  </Checkbox.Content>
</Checkbox>

Available render props:

  • isSelected - Whether checkbox is checked
  • isIndeterminate - Whether checkbox is in indeterminate state
  • isHovered - Whether checkbox is hovered
  • isPressed - Whether checkbox is currently pressed
  • isFocused - Whether checkbox is focused
  • isFocusVisible - Whether checkbox should show focus indicator
  • isDisabled - Whether checkbox is disabled
  • isReadOnly - Whether checkbox is read only

Styling Changes

v2: classNames Prop

<Checkbox 
  classNames={{
    base: "custom-base",
    wrapper: "custom-wrapper",
    icon: "custom-icon",
    label: "custom-label"
  }}
/>

v3: Direct className Props

<Checkbox className="custom-base" id="option">
  <Checkbox.Control className="custom-control">
    <Checkbox.Indicator className="custom-indicator" />
  </Checkbox.Control>
  <Checkbox.Content className="custom-content">
    <Label htmlFor="option" className="custom-label">
      Option
    </Label>
  </Checkbox.Content>
</Checkbox>

Component Anatomy

The v3 Checkbox follows this structure:

Checkbox (Root)
  ├── Checkbox.Control
  │   └── Checkbox.Indicator
  └── Checkbox.Content (optional)
      ├── Label (required if using Content)
      └── Description (optional)

Summary

  1. Component Structure: Must use compound components (Control, Indicator, Content)
  2. Label Handling: Labels must use Label component inside Checkbox.Content
  3. onValueChange → onChange: Event handler prop renamed
  4. Color Removed: Use Tailwind CSS classes on Checkbox.Control
  5. Size Removed: Use Tailwind CSS classes on Checkbox.Control
  6. Radius Removed: Use Tailwind CSS classes on Checkbox.Control
  7. LineThrough Removed: Use Tailwind line-through class on label
  8. Icon Prop Removed: Use custom content in Checkbox.Indicator
  9. CheckboxGroup: Same component name; use Label/Description as children and compound Checkbox structure
  10. ClassNames Removed: Use className props on individual components
  11. New variant Prop: Supports "primary" (default) and "secondary" for lower emphasis styling
  12. Indicator Render Props: Checkbox.Indicator accepts a render function with state values (isSelected, isIndeterminate, etc.)

On this page