Skip to main content

Checkbox

Available from version 0.2.0

Checkboxes give users binary choices when presented with multiple options in a series.

Basics

import { Checkbox } from 'tailwind-joy/components';

The basic Checkbox component is a single input set to the unchecked state. Use the label prop to provide text, and add defaultChecked when the input should be checked by default.

import { Box, Checkbox } from 'tailwind-joy/components';

export function CheckboxBasics() {
return (
<Box className="flex flex-wrap justify-center gap-6">
<Checkbox label="Label" />
<Checkbox label="Label" defaultChecked />
</Box>
);
}

Customization

Variants

The Checkbox component supports four variants: solid, soft, outlined, and plain. By default, when unchecked, the Checkbox is set to outlined; when checked, the variant changes to solid.

import { Box, Checkbox } from 'tailwind-joy/components';

export function CheckboxVariants() {
return (
<Box className="flex flex-wrap justify-center gap-6">
<Checkbox label="Solid" variant="solid" defaultChecked />
<Checkbox label="Soft" variant="soft" defaultChecked />
<Checkbox label="Outlined" variant="outlined" defaultChecked />
<Checkbox label="Plain" variant="plain" defaultChecked />
</Box>
);
}

Sizes

The Checkbox component supports three sizes: sm, md (default), and lg.

import { Box, Checkbox } from 'tailwind-joy/components';

export function CheckboxSizes() {
return (
<Box className="flex flex-wrap items-center justify-center gap-6">
<Checkbox label="Small" size="sm" defaultChecked />
<Checkbox label="Medium" size="md" defaultChecked />
<Checkbox label="Large" size="lg" defaultChecked />
</Box>
);
}

Colors

The Checkbox component supports five colors: primary, neutral, danger, success, and warning. By default, when unchecked, the Checkbox is set to neutral; when checked, the color changes to primary.

import { Box, Checkbox } from 'tailwind-joy/components';

export function CheckboxColors() {
return (
<Box className="flex flex-wrap justify-center gap-6">
<Checkbox label="Primary" color="primary" defaultChecked />
<Checkbox label="Neutral" color="neutral" defaultChecked />
<Checkbox label="Danger" color="danger" defaultChecked />
<Checkbox label="Success" color="success" defaultChecked />
<Checkbox label="Warning" color="warning" defaultChecked />
</Box>
);
}

Icons

By default, the Checkbox component is empty when unchecked. Use the uncheckedIcon prop to add a custom icon for the unchecked state. You can also use checkedIcon to customize the checked state.

import { MdClose } from 'react-icons/md';
import { Checkbox } from 'tailwind-joy/components';
import { iconClass } from 'tailwind-joy/utils';

export function CheckboxIcons() {
return (
<Checkbox
label="I have an icon when unchecked"
uncheckedIcon={<MdClose className={iconClass()} />}
/>
);
}
info

iconClass() is an adapter function provided by Tailwind-Joy.

Appear on hover

You can use the uncheckedIcon as a "preview" of the checked state by making it appear when the user hovers over the empty Checkbox.

The demo below shows how to target the icon by using the svg selector and apply opacity for a smooth effect:

import { MdCheck } from 'react-icons/md';
import { Checkbox } from 'tailwind-joy/components';
import { iconClass } from 'tailwind-joy/utils';

export function CheckboxAppearOnHover() {
return (
<Checkbox
label="My unchecked icon appears on hover"
uncheckedIcon={<MdCheck className={iconClass()} />}
className="
[&:has(:checked)_svg]:opacity-100
[&:has(:focus-visible)_svg]:opacity-100
[&:hover_svg]:opacity-100
[&_svg]:opacity-0
"
/>
);
}

No icons

Use the disableIcon prop to remove the icon entirely. In this case, the state of the Checkbox is communicated through the type of variant applied to the label. Try clicking on the Checkbox labels in the demo below to see how this works:

Pizza toppings
import { Box, Checkbox } from 'tailwind-joy/components';

export function CheckboxNoIcons() {
return (
<Box className="w-[343px] max-w-full">
<div className="text-joy-neutral-600 dark:text-joy-neutral-400 mb-4 text-sm font-semibold">
Pizza toppings
</div>
<div role="group" aria-labelledby="topping">
<div className="flex flex-wrap gap-2">
{[
'Pepperoni',
'Cheese',
'Olives',
'Tomatoes',
'Fried Bacon',
'Spinach',
].map((item, index) => (
<div
key={item}
className="relative px-3 py-1 [--unstable_actionRadius:20px]"
>
<Checkbox
disabled={index === 0}
overlay
disableIcon
variant="soft"
label={item}
/>
</div>
))}
</div>
</div>
</Box>
);
}

Focus outline

By default, the focus outline wraps both the Checkbox input and its label. To set the focus outline so that it only wraps the input, target the tj-checkbox-checkbox class and add position: 'relative', as shown in the demo below:

Try using keyboard navigation.
import { Box, Checkbox } from 'tailwind-joy/components';

export function CheckboxFocusOutline() {
return (
<div>
<div className="text-joy-neutral-600 dark:text-joy-neutral-400 mb-4 text-sm font-semibold">
Try using keyboard navigation.
</div>
<Box className="flex flex-wrap justify-center gap-6">
<Checkbox label="Fully wrapped" defaultChecked />
<Checkbox
label="Input wrapped"
defaultChecked
className="[&>.tj-checkbox-checkbox]:relative"
/>
</Box>
</div>
);
}

Clickable container

Use the overlay prop to shift the focus outline from the Checkbox to its container, making the entire container clickable to toggle the state of the Checkbox. This works with any positioned wrapper element:

Try using keyboard navigation.
import { Checkbox, Sheet } from 'tailwind-joy/components';

export function CheckboxClickableContainer() {
return (
<div>
<div className="text-joy-neutral-600 dark:text-joy-neutral-400 mb-4 text-sm font-semibold">
Try using keyboard navigation.
</div>
<Sheet variant="outlined" className="flex rounded-[8px] p-4">
<Checkbox label="Focus on me" overlay />
</Sheet>
</div>
);
}

Indeterminate

The default Checkbox is dual-state: the user can toggle between checked and unchecked.

There is also the option for a tri-state or indeterminate Checkbox that supports a state known as "partially checked."

This indeterminate state is often used to communicate the fact that only some out of a set of Checkboxes are checked. As such, it's usually reserved for parent Checkboxes that can control the states of their children.

The demo below shows how to implement the indeterminate prop on a parent Checkbox that watches for the checked state in its children. If only one child is checked, then the parent displays the indeterminate state. Clicking on the parent Checkbox toggles selecting and deselecting all children.

import { useState } from 'react';
import { Box, Checkbox } from 'tailwind-joy/components';

export function CheckboxIndeterminate() {
const [checked, setChecked] = useState([true, false]);

return (
<div>
<Checkbox
label="Parent"
checked={checked[0] && checked[1]}
indeterminate={checked[0] !== checked[1]}
onChange={(e) => {
setChecked([e.currentTarget.checked, e.currentTarget.checked]);
}}
/>
<Box className="ml-6 mt-2 flex flex-col gap-2">
<Checkbox
label="Child 1"
checked={checked[0]}
onChange={(e) => {
setChecked([e.currentTarget.checked, checked[1]]);
}}
/>
<Checkbox
label="Child 2"
checked={checked[1]}
onChange={(e) => {
setChecked([checked[0], e.currentTarget.checked]);
}}
/>
</Box>
</div>
);
}

Anatomy

The Checkbox component is composed of a root <span> that wraps the input and <label> (if present). Note that the actual <input type="checkbox"> is doubly nested within <span> elements that represent the checkbox and action slots, respectively.

<span class="tj-checkbox-root ...">
<span class="tj-checkbox-checkbox ...">
<span class="tj-checkbox-action ...">
<input type="checkbox" class="tj-checkbox-input ..." />
</span>
<!-- icon is nested here when present -->
</span>
<label class="tj-checkbox-label ...">
<!-- label text -->
</label>
</span>

API

See the documentation below for a complete reference to all of the props available to the components mentioned here.