Checkbox
- Source: https://github.com/reach/reach-ui/tree/main/packages/checkbox
- WAI-ARIA: https://www.w3.org/TR/wai-aria-practices-1.2/#checkbox
@reach/checkbox
provides two top-level components:
MixedCheckbox
CustomCheckbox
MixedCheckbox
A MixedCheckbox simply renders an HTML input element where the type
attribute is equal to "checked"
. Whereas the native input element technically only has two states (true
or false
), there is a third visual state of indeterminate
that is designed to suggest that a user has fulfilled some part of whatever the checkbox is meant to control. For example, you may have multiple hierarchal checkboxes nested:
[-] All fruits-- [ ] Apple-- [x] Banana-- [x] Orange
In this example, the All fruits
checkbox is in an indeterminate
state because some (but not all) fruits in the list are checked. While this effect is possible with plain input components, the MixedCheckbox
component makes managing/syncing its state with the correct DOM attributes much simpler. All you have to do is pass the checked
state, and @reach/checkbox
handles the necessary aria attributes and related node data!
A mixed checkbox is not something you can naturally toggle by simply clicking the box itself. As such, you should manage its state in your app by passing a checked
prop and an onChange
handler. A mixed checkbox is necessarily controlled. If you use a MixedCheckbox
component without controlling its state, it will behave exactly the same way a native HTML input element behaves.
If you don't need indeterminate
state, you should probably just use a native HTML input for your checkboxes. But of course, sometimes designers have some other ideas that call for a custom solution. In that case, the CustomCheckbox
component provides a customizable wrapper element that can be styled to fit your needs.
CustomCheckbox
A CustomCheckbox
is useful because full control of a native HTML input's design is not always possible. You may want to provide custom check graphics or change the shape of the check or its color. This component provides a handy wrapper around a visually hidden native checkbox so that we avoid re-creating all of its native event behavior.
CustomCheckbox
uses our MixedCheckbox
so you get the same benefits for dealing with indeterminate
state when you use either!
Accessibility Note: If you use our
CustomCheckbox
component, you will still need to ensure that your styles follow the guidelines outlined in the WAI-ARIA specifications for checkboxes. Pay special attention to focus styles for keyboard navigation. Our default styles provide focus styles by default.
Installation
From the command line in your project directory, run npm install @reach/checkbox
or yarn add @reach/checkbox
. Then import the components and styles that you need:
npm install @reach/checkbox# oryarn add @reach/checkbox
import { CustomCheckbox, CustomCheckboxContainer, CustomCheckboxInput,} from "@reach/checkbox";import "@reach/checkbox/styles.css";
If you are only using MixedCheckbox
or useMixedCheckbox
, there is no need to include the stylesheet.
import { MixedCheckbox, useMixedCheckbox } from "@reach/checkbox";
Usage
Example MixedCheckbox
function Example() { const [checked, setChecked] = React.useState(true); return ( <div> <label> <MixedCheckbox value="whatever" checked={checked} onChange={(event) => { setChecked(event.target.checked); }} /> I am feeling good today </label> <label> <MixedCheckbox checked="mixed" /> Perma-mixed </label> <div style={{ marginTop: 10 }}> <button onClick={() => setChecked("mixed")}> I'm not sure how I feel! </button> </div> </div> );}
Example CustomCheckbox
With custom checkbox, you can choose between a high-level API where DOM elements are not individually exposed as components, or use the composed API to access each sub-component directly.
High-level CustomCheckbox API
(() => { function MyCheckbox({ children, ...props }) { return ( <span className={`example-custom-checkbox ${props.value}`}> <label> <CustomCheckbox {...props} /> {children} </label> </span> ); } function Checklist() { return ( <fieldset> <legend>What is your favorite fruit?</legend> <MyCheckbox name="favorite-fruit" value="apple" color="#B22222"> Apple <span aria-hidden>🍎</span> </MyCheckbox> <MyCheckbox name="favorite-fruit" value="orange" color="#FF8C00"> Orange <span aria-hidden>🍊</span> </MyCheckbox> <MyCheckbox name="favorite-fruit" value="banana" color="#FFD700"> Banana <span aria-hidden>🍌</span> </MyCheckbox> </fieldset> ); } return <Checklist />;})();
Composed CustomCheckbox API
(() => { function Example() { return ( <div> <label style={{ display: "flex", alignItems: "center" }}> <MyCheckbox value="whatever" /> This is a pretty cool checkbox; do you agree? </label> <br /> <label style={{ display: "flex", alignItems: "center" }}> <MyCheckbox checked="mixed" value="something-else" /> I'm just an example of what I'd look like if I had a mixed state. </label> </div> ); } function MyCheckbox(props) { const [checkedState, setChecked] = React.useState(props.checked || false); const checked = props.checked != null ? props.checked : checkedState; return ( <CustomCheckboxContainer checked={props.checked != null ? props.checked : checked} onChange={(event) => setChecked(event.target.checked)} style={getContainerStyle()} > <CustomCheckboxInput {...props} /> <span aria-hidden style={getCheckStyle(checked)} /> </CustomCheckboxContainer> ); } function getContainerStyle() { return { background: "rgba(240, 240, 250, 0.8)", border: "2px solid rgba(0, 0, 0, 0.8)", borderRadius: "3px", height: 26, width: 26, }; } function getCheckStyle(checked) { return { display: "block", position: "absolute", width: "60%", height: "60%", top: "50%", left: "50%", transform: `translate(-50%, -50%) scaleX(${!!checked ? 1 : 0}) scaleY(${ checked === true ? 1 : checked === "mixed" ? 0.4 : 0 })`, transition: "transform 200ms ease-out, background 200ms ease-out", zIndex: 1, background: checked === true ? "green" : checked === "mixed" ? "goldenrod" : "transparent", }; } return <Example />;})();
Component API
MixedCheckbox
Tri-state checkbox that accepts checked
values of true
, false
or "mixed"
.
function Example() { let [favoriteCondiments, setFavoriteCondiments] = React.useState({ mayo: true, mustard: true, ketchup: false, }); // We can determine if all or some of the nested checkboxes are selected and // use that to determine the state of our parent checkbox. let allCondimentsChecked = Object.keys(favoriteCondiments).every( (condiment) => favoriteCondiments[condiment] === true ); let someCondimentsChecked = allCondimentsChecked ? false : Object.keys(favoriteCondiments).some( (condiment) => favoriteCondiments[condiment] === true ); let parentIsChecked = allCondimentsChecked ? true : someCondimentsChecked ? "mixed" : false; // When we toggle a parent checkbox, we expect all of the nested checkboxes // to toggle with it. function handleParentChange(event) { setFavoriteCondiments( Object.keys(favoriteCondiments).reduce( (state, condiment) => ({ ...state, [condiment]: !allCondimentsChecked, }), {} ) ); } function handleChildChange(event) { let { checked, value } = event.target; setFavoriteCondiments({ ...favoriteCondiments, [value]: checked, }); } return ( <fieldset> <label> <MixedCheckbox value="condiments" checked={parentIsChecked} onChange={handleParentChange} /> {allCondimentsChecked ? "Unselect" : "Select"} all condiments </label> <fieldset style={{ margin: "1rem 0 0", padding: "1rem 1.5rem" }}> <legend>Condiments</legend> <ul style={{ listStyle: "none", padding: 0, margin: 0 }}> {Object.entries(favoriteCondiments).map(([value, state]) => ( <li key={value}> <label> <MixedCheckbox name="condiment" value={value} checked={state} onChange={handleChildChange} /> {value} </label> </li> ))} </ul> </fieldset> </fieldset> );}
MixedCheckbox Props
MixedCheckbox
inherits its props from the React.ComponentProps<'input'>
type, with additional context documented below.
Check out the React documentation for additional information about form inputs in React.
MixedCheckbox checked
checked?: boolean | "mixed
Whether or not the checkbox is checked or in a mixed
(indeterminate) state.
MixedCheckbox onChange
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
The callback that is fired when the checkbox value is changed.
useMixedCheckbox
A hook that can be used to turn any HTML input component into a tri-state checkbox.
You must create a ref and pass it along with additional arguments to return a props object and state-related data.
function Example({ disabled = false }) { const [checked, setChecked] = React.useState(true); let inputRef = React.useRef(null); let [inputProps, stateData] = useMixedCheckbox(inputRef, { // boolean // A mixed checkbox may receive either `defaultChecked` or `checked` // values, but not both! defaultChecked: undefined, // boolean | "mixed" checked, // (event: React.ChangeEvent<HTMLInputElement>) => void onChange: (event) => setChecked(event.target.checked), // boolean disabled, }); return ( <div> <label> <input {...inputProps} ref={inputRef} /> How about this cool example? </label> <button onClick={() => setChecked("mixed")}>Mix it up</button> <hr /> Current state is: <pre>{String(stateData.checked)}</pre> </div> );}
useMixedCheckbox signature
function useMixedCheckbox( ref: React.RefObject<HTMLInputElement | null>, args?: { checked?: boolean | "mixed"; defaultChecked?: boolean; disabled?: boolean; onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void; onClick?: (event: React.MouseEvent<HTMLInputElement>) => void; }, functionOrComponentName: string = "useMixedCheckbox"): [ { "aria-checked": boolean | "mixed"; checked: boolean; disabled: boolean; onChange: (event: React.ChangeEvent<HTMLInputElement>) => void; onClick: (event: React.MouseEvent<HTMLInputElement>) => void; type: "checkbox"; }, { checked: boolean | "mixed" }];
CustomCheckbox
A checkbox component with a wrapper element for custom styling.
CustomCheckbox CSS Selectors
Please see the styling guide.
[data-reach-custom-checkbox] {}[data-reach-custom-checkbox][data-state="checked"] {}[data-reach-custom-checkbox][data-state="unchecked"] {}[data-reach-custom-checkbox][data-state="mixed"] {}
CustomCheckbox Props
Prop | Type | Required |
---|---|---|
span props | ||
checked | boolean | "mixed" | false |
children | node | func | false |
defaultChecked | boolean | false |
disabled | boolean | false |
name | string | false |
onChange | func | false |
value | `string | number` |
CustomCheckbox span props
All props are spread to an underlying span
element.
<CustomCheckbox className="cool-checkbox" />
CustomCheckbox checked
checked?: boolean | "mixed"
Whether or not the checkbox is checked or in a mixed
(indeterminate) state.
CustomCheckbox children
children?: React.ReactNode;
A CustomCheckbox
can accept any React node as children so long as the rendered content is valid HTML. It is best to avoid adding interactive elements inside of a CustomCheckbox
function Example({ innerStyle, ...props }) { return ( <span> <label> <CustomCheckbox {...props}> <span aria-hidden style={...innerStyle} /> </CustomCheckbox> </label> </span> );}
CustomCheckbox defaultChecked
defaultChecked?: boolean
For uncontrolled checkbox components, defaultChecked
dictates whether or not the default initial state for a checkbox is checked
.
Because any checkbox with a mixed
state must be controlled by the app, defaultChecked
only accepts true
or false
values.
CustomCheckbox disabled
disabled?: boolean
Whether or not the checkbox form input is disabled.
CustomCheckbox name
name?: string
The name
attribute passed to the checkbox form input.
CustomCheckbox onChange
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
The callback that is fired when the checkbox value is changed.
CustomCheckbox value
value?: string | number
The value
attribute passed to the checkbox form input.
CustomCheckboxContainer
Wrapper component and context provider for a custom checkbox. It should be used in conjunction with the CustomCheckboxInput
.
CustomCheckboxContainer CSS Selectors
Please see the styling guide.
[data-reach-custom-checkbox-container] {}[data-reach-custom-checkbox-container][data-state="checked"] {}[data-reach-custom-checkbox-container][data-state="unchecked"] {}[data-reach-custom-checkbox-container][data-state="mixed"] {}
CustomCheckboxContainer Props
Prop | Type | Required |
---|---|---|
span props | ||
checked | boolean | "mixed" | false |
children | node | func | true |
defaultChecked | boolean | false |
disabled | boolean | false |
onChange | func | false |
CustomCheckboxContainer span props
All props are spread to an underlying span
element.
<CustomCheckboxContainer className="cool-checkbox"> <CustomCheckboxInput /></CustomCheckboxContainer>
CustomCheckboxContainer checked
checked?: boolean | "mixed"
Same as CustomCheckbox
checked
.
This prop is assigned to the CustomCheckboxContainer
and passed to the CustomCheckboxInput
via the React Context API.
CustomCheckboxContainer children
children: React.ReactNode | ((args: { checked: boolean | "mixed", inputRef: React.RefObject, focused: boolean }) => JSX.Element)
A CustomCheckboxContainer
can accept a React node or render prop function as its child. It should always have one CustomCheckboxInput
component as a descendant.
function Example({ children, name, value, id, label, ...props }) { return ( <span> <CustomCheckbox {...props}> {({ checked, focused }) => ( <span aria-hidden style={{ display: "block", outline: focused ? "2px solid aqua" : undefined, }} > {checked === "mixed" ? "⛔" : checked ? "✅" : "❌"} <CustomCheckboxInput id={id} name={name} value={value} /> </span> )} </CustomCheckbox> <label htmlFor={id}>{label}</label> </span> );}
CustomCheckboxContainer defaultChecked
defaultChecked?: boolean
Same as CustomCheckbox
defaultChecked
.
This prop is assigned to the CustomCheckboxContainer
and passed to the CustomCheckboxInput
via the React Context API.
CustomCheckboxContainer disabled
disabled?: boolean
Same as CustomCheckbox
disabled
.
This prop is assigned to the CustomCheckboxContainer
and passed to the CustomCheckboxInput
via the React Context API.
CustomCheckboxContainer onChange
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
Same as CustomCheckbox
onChange
.
This prop is assigned to the CustomCheckboxContainer
and passed to the CustomCheckboxInput
via the React Context API.
CustomCheckboxInput
Component to render the HTML input element for our custom checkbox. The rendered element should be visually hidden and exists only to manage its state and hold a form name and value.
CustomCheckboxInput CSS Selectors
Please see the styling guide.
[data-reach-custom-checkbox-input] {}[data-reach-custom-checkbox-input][aria-checked="true"] {}[data-reach-custom-checkbox-input][aria-checked="false"] {}[data-reach-custom-checkbox-input][aria-checked="mixed"] {}
CustomCheckboxInput Props
CustomCheckboxInput
inherits its props from the React.ComponentProps<'input'>
type, excluding:
Each of these props, if needed, should instead be assigned to CustomCheckboxContainer
. They are passed to the CustomCheckboxInput
via the React Context API
Check out the React documentation for additional information about form inputs in React.