Skip to content

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.

PropTypeRequired
checkedboolean | "mixed"false
onChangefuncfalse
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

PropTypeRequired
span props
checkedboolean | "mixed"false
childrennode | funcfalse
defaultCheckedbooleanfalse
disabledbooleanfalse
namestringfalse
onChangefuncfalse
value`stringnumber`
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

PropTypeRequired
span props
checkedboolean | "mixed"false
childrennode | functrue
defaultCheckedbooleanfalse
disabledbooleanfalse
onChangefuncfalse
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.