Skip to content

Commit 91f1905

Browse files
wingkwongtianenpangjrgarciadev
authored
feat(components): checkbox (#5829)
* fix(component): support compound pattern in server component * fix(component): keep accordion and chip in sync with compound pattern * fix(component): type of forward refs * fix(calendar): temporary workaround for ref error * fix(storybook): imports * feat(checkbox): initial draft * refactor(styles): remove orientation * refactor: migration from dot notation to separted components to support RSC * refactor: migrate to React 19 ref pattern - remove forwardRef wrappers and explicit ref declarations from all components * chore(checkbox): revise checkbox story * feat(checkbox): checkmark & indeterminate * chore(styles): checkmark & indeterminate styles * refactor: provider context removed as it is not longer needed on react 19 * feat: made the migration smoother by still supporting the "dot" exports but adjusting the main compound component * fix: compound patter, radio group api, ref on react 19 * refactor(switch): split switch and switch-group into separate components following radio/radio-group pattern * chore(changelog): update v3.0.0-alpha.35 release notes and date * fix(page): update version label to reflect RSC support * chore(changelog): update examples to use new component names CardRoot and TabsRoot * refactor(checkbox): adopt latest component structure * chore: remove radio stories * refactor(checkbox): adopt new api and revised stories * refactor(checkbox-group): adopt new api and add stories * chore(checkbox-group): revise examples * chore(checkbox): separate checkbox group css * chore(styles): revise checkbox styels * fix(styles): add border for invalid * feat(checkbox): add invalid story * chore: sync changes from v3 redesign * refactor(checkbox): remove slots * refactor(checkbox): apply compound and named pattern * feat(docs): checkbox page * refactor(checkbox): remove use client * chore(docs): revise based on the latest revamp --------- Co-authored-by: Tianen Pang <[email protected]> Co-authored-by: Junior Garcia <[email protected]>
1 parent b2956c1 commit 91f1905

25 files changed

+1277
-0
lines changed
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
---
2+
title: Checkbox
3+
description: A checkbox component for selecting multiple options
4+
icon: preview
5+
links:
6+
rac: Checkbox
7+
source: checkbox/checkbox.tsx
8+
styles: checkbox.css
9+
storybook: Components/Forms/Checkbox
10+
figma: true
11+
---
12+
13+
## Import
14+
15+
```tsx
16+
import { Checkbox, Label } from '@heroui/react';
17+
```
18+
19+
### Usage
20+
21+
<ComponentPreview
22+
name="checkbox-basic"
23+
/>
24+
25+
### Anatomy
26+
27+
Import the Checkbox component and access all parts using dot notation.
28+
29+
```tsx
30+
import { Checkbox, Label } from '@heroui/react';
31+
32+
export default () => (
33+
<Checkbox>
34+
<Checkbox.Control>
35+
<Checkbox.Indicator />
36+
</Checkbox.Control>
37+
<Label>Accept terms</Label>
38+
</Checkbox>
39+
);
40+
```
41+
42+
The Checkbox component follows a composition pattern. It works with external `Label` and `Description` components using standard HTML `id` and `htmlFor` attributes:
43+
44+
```tsx
45+
import { Checkbox, Label, Description } from '@heroui/react';
46+
47+
export default () => (
48+
<div className="flex gap-3">
49+
<Checkbox className="mt-0.5" id="notifications">
50+
<Checkbox.Control>
51+
<Checkbox.Indicator />
52+
</Checkbox.Control>
53+
</Checkbox>
54+
<div className="flex flex-col gap-1">
55+
<Label htmlFor="notifications">Email notifications</Label>
56+
<Description>Get notified when someone mentions you</Description>
57+
</div>
58+
</div>
59+
);
60+
```
61+
62+
### Disabled
63+
64+
<ComponentPreview
65+
name="checkbox-disabled"
66+
/>
67+
68+
### Default Selected
69+
70+
<ComponentPreview
71+
name="checkbox-default-selected"
72+
/>
73+
74+
### Controlled
75+
76+
<ComponentPreview
77+
name="checkbox-controlled"
78+
/>
79+
80+
### Indeterminate
81+
82+
<ComponentPreview
83+
name="checkbox-indeterminate"
84+
/>
85+
86+
### With Label
87+
88+
<ComponentPreview
89+
name="checkbox-with-label"
90+
/>
91+
92+
### With Description
93+
94+
<ComponentPreview
95+
name="checkbox-with-description"
96+
/>
97+
98+
### Render Props
99+
100+
<ComponentPreview
101+
name="checkbox-render-props"
102+
/>
103+
104+
### Form Integration
105+
106+
<ComponentPreview
107+
name="checkbox-form"
108+
/>
109+
110+
### Custom Styles
111+
112+
<ComponentPreview
113+
name="checkbox-custom-styles"
114+
/>
115+
116+
## Styling
117+
118+
### Passing Tailwind CSS classes
119+
120+
You can customize individual Checkbox components:
121+
122+
```tsx
123+
import { Checkbox, Label } from '@heroui/react';
124+
125+
function CustomCheckbox() {
126+
return (
127+
<div className="flex items-center gap-3">
128+
<Checkbox id="custom">
129+
<Checkbox.Control className="border-2 border-purple-500 data-[selected=true]:bg-purple-500">
130+
<Checkbox.Indicator className="text-white" />
131+
</Checkbox.Control>
132+
</Checkbox>
133+
<Label htmlFor="custom">Custom Checkbox</Label>
134+
</div>
135+
);
136+
}
137+
```
138+
139+
### Customizing the component classes
140+
141+
To customize the Checkbox component classes, you can use the `@layer components` directive.
142+
<br/>[Learn more](https://tailwindcss.com/docs/adding-custom-styles#adding-component-classes).
143+
144+
```css
145+
@layer components {
146+
.checkbox {
147+
@apply inline-flex gap-3 items-center;
148+
}
149+
150+
.checkbox__control {
151+
@apply size-5 border-2 border-gray-400 rounded data-[selected=true]:bg-blue-500 data-[selected=true]:border-blue-500;
152+
}
153+
154+
.checkbox__indicator {
155+
@apply text-white;
156+
}
157+
158+
.checkbox__content {
159+
@apply flex flex-col gap-1;
160+
}
161+
}
162+
```
163+
164+
HeroUI follows the [BEM](https://getbem.com/) methodology to ensure component variants and states are reusable and easy to customize.
165+
166+
### CSS Classes
167+
168+
The Checkbox component uses these CSS classes ([View source styles](https://github.com/heroui-inc/heroui/blob/v3/packages/styles/components/checkbox.css)):
169+
170+
- `.checkbox` - Base checkbox container
171+
- `.checkbox__control` - Checkbox control box
172+
- `.checkbox__indicator` - Checkbox checkmark indicator
173+
- `.checkbox__content` - Optional content container
174+
175+
### Interactive States
176+
177+
The checkbox supports both CSS pseudo-classes and data attributes for flexibility:
178+
179+
- **Selected**: `[data-selected="true"]` (shows checkmark and background color change)
180+
- **Indeterminate**: `[data-indeterminate="true"]` (shows indeterminate state with dash)
181+
- **Hover**: `:hover` or `[data-hovered="true"]`
182+
- **Focus**: `:focus-visible` or `[data-focus-visible="true"]` (shows focus ring)
183+
- **Disabled**: `:disabled` or `[aria-disabled="true"]` (reduced opacity, no pointer events)
184+
- **Pressed**: `:active` or `[data-pressed="true"]`
185+
186+
## API Reference
187+
188+
### Checkbox Props
189+
190+
Inherits from [React Aria Checkbox](https://react-spectrum.adobe.com/react-aria/Checkbox.html).
191+
192+
| Prop | Type | Default | Description |
193+
|------|------|---------|-------------|
194+
| `isSelected` | `boolean` | `false` | Whether the checkbox is checked |
195+
| `defaultSelected` | `boolean` | `false` | Whether the checkbox is checked by default (uncontrolled) |
196+
| `isIndeterminate` | `boolean` | `false` | Whether the checkbox is in an indeterminate state |
197+
| `isDisabled` | `boolean` | `false` | Whether the checkbox is disabled |
198+
| `isReadOnly` | `boolean` | `false` | Whether the checkbox is read only |
199+
| `name` | `string` | - | The name of the input element, used when submitting an HTML form |
200+
| `value` | `string` | - | The value of the input element, used when submitting an HTML form |
201+
| `onChange` | `(isSelected: boolean) => void` | - | Handler called when the checkbox value changes |
202+
| `children` | `React.ReactNode \| (values: CheckboxRenderProps) => React.ReactNode` | - | Checkbox content or render prop |
203+
204+
### CheckboxRenderProps
205+
206+
When using the render prop pattern, these values are provided:
207+
208+
| Prop | Type | Description |
209+
|------|------|-------------|
210+
| `isSelected` | `boolean` | Whether the checkbox is currently checked |
211+
| `isIndeterminate` | `boolean` | Whether the checkbox is in an indeterminate state |
212+
| `isHovered` | `boolean` | Whether the checkbox is hovered |
213+
| `isPressed` | `boolean` | Whether the checkbox is currently pressed |
214+
| `isFocused` | `boolean` | Whether the checkbox is focused |
215+
| `isFocusVisible` | `boolean` | Whether the checkbox is keyboard focused |
216+
| `isDisabled` | `boolean` | Whether the checkbox is disabled |
217+
| `isReadOnly` | `boolean` | Whether the checkbox is read only |
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import {Checkbox, Label} from "@heroui/react";
2+
3+
export function Basic() {
4+
return (
5+
<div className="flex items-center gap-3">
6+
<Checkbox id="terms">
7+
<Checkbox.Control>
8+
<Checkbox.Indicator />
9+
</Checkbox.Control>
10+
</Checkbox>
11+
<Label htmlFor="terms">Accept terms and conditions</Label>
12+
</div>
13+
);
14+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {Checkbox, Label} from "@heroui/react";
2+
import {useState} from "react";
3+
4+
export function Controlled() {
5+
const [isSelected, setIsSelected] = useState(true);
6+
7+
return (
8+
<div className="flex flex-col gap-3">
9+
<div className="flex items-center gap-3">
10+
<Checkbox id="email-notifications" isSelected={isSelected} onChange={setIsSelected}>
11+
<Checkbox.Control>
12+
<Checkbox.Indicator />
13+
</Checkbox.Control>
14+
</Checkbox>
15+
<Label htmlFor="email-notifications">Email notifications</Label>
16+
</div>
17+
<p className="text-muted text-sm">
18+
Status: <span className="font-medium">{isSelected ? "Enabled" : "Disabled"}</span>
19+
</p>
20+
</div>
21+
);
22+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import {Checkbox, Label} from "@heroui/react";
2+
3+
export function CustomStyles() {
4+
return (
5+
<div className="flex items-center gap-3">
6+
<Checkbox id="custom">
7+
<Checkbox.Control className="border-2 border-purple-500 data-[selected=true]:border-purple-500 data-[selected=true]:bg-purple-500">
8+
<Checkbox.Indicator className="text-white" />
9+
</Checkbox.Control>
10+
</Checkbox>
11+
<Label htmlFor="custom">Custom styled checkbox</Label>
12+
</div>
13+
);
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import {Checkbox, Label} from "@heroui/react";
2+
3+
export function DefaultSelected() {
4+
return (
5+
<div className="flex items-center gap-3">
6+
<Checkbox defaultSelected id="notifications">
7+
<Checkbox.Control>
8+
<Checkbox.Indicator />
9+
</Checkbox.Control>
10+
</Checkbox>
11+
<Label htmlFor="notifications">Enable email notifications</Label>
12+
</div>
13+
);
14+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import {Checkbox, Description, Label} from "@heroui/react";
2+
3+
export function Disabled() {
4+
return (
5+
<div className="flex gap-3">
6+
<Checkbox isDisabled className="mt-0.5" id="feature">
7+
<Checkbox.Control>
8+
<Checkbox.Indicator />
9+
</Checkbox.Control>
10+
</Checkbox>
11+
<div className="flex flex-col gap-1">
12+
<Label htmlFor="feature">Premium Feature</Label>
13+
<Description>This feature is coming soon</Description>
14+
</div>
15+
</div>
16+
);
17+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import {Button, Checkbox, Label} from "@heroui/react";
2+
import React from "react";
3+
4+
export function Form() {
5+
const handleSubmit = (e: React.FormEvent) => {
6+
e.preventDefault();
7+
const formData = new FormData(e.target as HTMLFormElement);
8+
9+
alert(
10+
`Form submitted with:\n${Array.from(formData.entries())
11+
.map(([key, value]) => `${key}: ${value}`)
12+
.join("\n")}`,
13+
);
14+
};
15+
16+
return (
17+
<form className="flex flex-col gap-4" onSubmit={handleSubmit}>
18+
<div className="flex flex-col gap-3">
19+
<div className="flex items-center gap-3">
20+
<Checkbox id="notifications" name="notifications" value="on">
21+
<Checkbox.Control>
22+
<Checkbox.Indicator />
23+
</Checkbox.Control>
24+
</Checkbox>
25+
<Label htmlFor="notifications">Enable notifications</Label>
26+
</div>
27+
<div className="flex items-center gap-3">
28+
<Checkbox defaultSelected id="newsletter" name="newsletter" value="on">
29+
<Checkbox.Control>
30+
<Checkbox.Indicator />
31+
</Checkbox.Control>
32+
</Checkbox>
33+
<Label htmlFor="newsletter">Subscribe to newsletter</Label>
34+
</div>
35+
<div className="flex items-center gap-3">
36+
<Checkbox id="marketing" name="marketing" value="on">
37+
<Checkbox.Control>
38+
<Checkbox.Indicator />
39+
</Checkbox.Control>
40+
</Checkbox>
41+
<Label htmlFor="marketing">Receive marketing updates</Label>
42+
</div>
43+
</div>
44+
<Button className="mt-4" size="sm" type="submit" variant="primary">
45+
Submit
46+
</Button>
47+
</form>
48+
);
49+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import {Checkbox, Description, Label} from "@heroui/react";
2+
import {useState} from "react";
3+
4+
export function Indeterminate() {
5+
const [isIndeterminate, setIsIndeterminate] = useState(true);
6+
const [isSelected, setIsSelected] = useState(false);
7+
8+
return (
9+
<div className="flex gap-3">
10+
<Checkbox
11+
className="mt-0.5"
12+
id="select-all"
13+
isIndeterminate={isIndeterminate}
14+
isSelected={isSelected}
15+
onChange={(selected: boolean) => {
16+
setIsSelected(selected);
17+
setIsIndeterminate(false);
18+
}}
19+
>
20+
<Checkbox.Control>
21+
<Checkbox.Indicator />
22+
</Checkbox.Control>
23+
</Checkbox>
24+
<div className="flex flex-col gap-1">
25+
<Label htmlFor="select-all">Select all</Label>
26+
<Description>Shows indeterminate state (dash icon)</Description>
27+
</div>
28+
</div>
29+
);
30+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export {Basic} from "./basic";
2+
export {Controlled} from "./controlled";
3+
export {CustomStyles} from "./custom-styles";
4+
export {DefaultSelected} from "./default-selected";
5+
export {Disabled} from "./disabled";
6+
export {Form} from "./form";
7+
export {Indeterminate} from "./indeterminate";
8+
export {RenderProps} from "./render-props";
9+
export {WithDescription} from "./with-description";
10+
export {WithLabel} from "./with-label";

0 commit comments

Comments
 (0)