useCheckbox

Provides the behavior and accessibility implementation for a checkbox component. Checkboxes allow users to select multiple items from a list of individual items, or to mark one individual item as selected.

installyarn add @react-aria/checkbox
version3.2.3
usageimport {useCheckbox} from '@react-aria/checkbox'

API#


useCheckbox( props: AriaCheckboxProps, state: ToggleState, inputRef: RefObject<HTMLInputElement> ): CheckboxAria

Features#


Checkboxes can be built with the <input> HTML element, but this can be difficult to style. useCheckbox helps achieve accessible checkboxes that can be styled as needed.

  • Built with a native HTML <input> element, which can be optionally visually hidden to allow custom styling
  • Full support for browser features like form autofill
  • Keyboard focus management and cross browser normalization
  • Labeling support for assistive technology
  • Indeterminate state support

Anatomy#


SubscribeLabelInput

A checkbox consists of a visual selection indicator and a label. Checkboxes support three selection states: checked, unchecked, and indeterminate. Users may click or touch a checkbox to toggle the selection state, or use the Tab key to navigate to it and the Space key to toggle it.

useCheckbox returns props to be spread onto its input element:

NameTypeDescription
inputPropsInputHTMLAttributes<HTMLInputElement>Props for the input element.

Selection state is managed by the useToggleState hook in @react-stately/toggle. The state object should be passed as an option to useCheckbox.

In most cases, checkboxes should have a visual label. If the checkbox does not have a visible label, an aria-label or aria-labelledby prop must be passed instead to identify the element to assistive technology.

Example#


import {useToggleState} from '@react-stately/toggle';

function Checkbox(props) {
  let {children} = props;
  let state = useToggleState(props);
  let ref = React.useRef();
  let {inputProps} = useCheckbox(props, state, ref);

  return (
    <label style={{display: 'block'}}>
      <input {...inputProps} ref={ref} />
      {children}
    </label>
  );
}

<Checkbox>Test</Checkbox>
<Checkbox isIndeterminate>Test</Checkbox>
import {useToggleState} from '@react-stately/toggle';

function Checkbox(props) {
  let {children} = props;
  let state = useToggleState(props);
  let ref = React.useRef();
  let {inputProps} = useCheckbox(props, state, ref);

  return (
    <label style={{display: 'block'}}>
      <input {...inputProps} ref={ref} />
      {children}
    </label>
  );
}

<Checkbox>Test</Checkbox>
<Checkbox isIndeterminate>Test</Checkbox>
import {useToggleState} from '@react-stately/toggle';

function Checkbox(
  props
) {
  let { children } =
    props;
  let state =
    useToggleState(
      props
    );
  let ref = React
    .useRef();
  let { inputProps } =
    useCheckbox(
      props,
      state,
      ref
    );

  return (
    <label
      style={{
        display: 'block'
      }}
    >
      <input
        {...inputProps}
        ref={ref}
      />
      {children}
    </label>
  );
}

<Checkbox>
  Test
</Checkbox>
<Checkbox
  isIndeterminate
>
  Test
</Checkbox>

Styling#


To build a custom styled checkbox, you can make the native input element visually hidden. This is possible using the <VisuallyHidden> utility component from @react-aria/visually-hidden. It is still in the DOM and accessible to assistive technology, but invisible. This example uses SVG to build the visual checkbox, which is hidden from screen readers with aria-hidden.

For keyboard accessibility, a focus ring is important to indicate which element has keyboard focus. This is implemented with the useFocusRing hook from @react-aria/focus. When isFocusVisible is true, an extra SVG element is rendered to indicate focus. The focus ring is only visible when the user is interacting with a keyboard, not with a mouse or touch.

import {VisuallyHidden} from '@react-aria/visually-hidden';
import {useFocusRing} from '@react-aria/focus';

function Checkbox(props) {
  let state = useToggleState(props);
  let ref = React.useRef();
  let {inputProps} = useCheckbox(props, state, ref);
  let {isFocusVisible, focusProps} = useFocusRing();

  return (
    <label style={{display: 'flex', alignItems: 'center'}}>
      <VisuallyHidden>
        <input {...inputProps} {...focusProps} ref={ref} />
      </VisuallyHidden>
      <svg
        width={24}
        height={24}
        aria-hidden="true"
        style={{marginRight: 4}}>
        <rect
          x={state.isSelected ? 4 : 5}
          y={state.isSelected ? 4 : 5}
          width={state.isSelected ? 16 : 14}
          height={state.isSelected ? 16 : 14}
          fill={state.isSelected ? 'orange' : 'none'}
          stroke={state.isSelected ? 'none' : 'gray'}
          strokeWidth={2} />
        {state.isSelected &&
          <path
            transform="translate(7 7)"
            d={`M3.788 9A.999.999 0 0 1 3 8.615l-2.288-3a1 1 0 1 1
            1.576-1.23l1.5 1.991 3.924-4.991a1 1 0 1 1 1.576 1.23l-4.712
            6A.999.999 0 0 1 3.788 9z`} />
        }
        {isFocusVisible &&
          <rect
            x={1}
            y={1}
            width={22}
            height={22}
            fill="none"
            stroke="orange"
            strokeWidth={2} />
        }
      </svg>
      {props.children}
    </label>
  );
}

<Checkbox>Foo</Checkbox>
import {VisuallyHidden} from '@react-aria/visually-hidden';
import {useFocusRing} from '@react-aria/focus';

function Checkbox(props) {
  let state = useToggleState(props);
  let ref = React.useRef();
  let { inputProps } = useCheckbox(props, state, ref);
  let { isFocusVisible, focusProps } = useFocusRing();

  return (
    <label
      style={{ display: 'flex', alignItems: 'center' }}
    >
      <VisuallyHidden>
        <input {...inputProps} {...focusProps} ref={ref} />
      </VisuallyHidden>
      <svg
        width={24}
        height={24}
        aria-hidden="true"
        style={{ marginRight: 4 }}
      >
        <rect
          x={state.isSelected ? 4 : 5}
          y={state.isSelected ? 4 : 5}
          width={state.isSelected ? 16 : 14}
          height={state.isSelected ? 16 : 14}
          fill={state.isSelected ? 'orange' : 'none'}
          stroke={state.isSelected ? 'none' : 'gray'}
          strokeWidth={2}
        />
        {state.isSelected &&
          (
            <path
              transform="translate(7 7)"
              d={`M3.788 9A.999.999 0 0 1 3 8.615l-2.288-3a1 1 0 1 1
            1.576-1.23l1.5 1.991 3.924-4.991a1 1 0 1 1 1.576 1.23l-4.712
            6A.999.999 0 0 1 3.788 9z`}
            />
          )}
        {isFocusVisible &&
          (
            <rect
              x={1}
              y={1}
              width={22}
              height={22}
              fill="none"
              stroke="orange"
              strokeWidth={2}
            />
          )}
      </svg>
      {props.children}
    </label>
  );
}

<Checkbox>Foo</Checkbox>
import {VisuallyHidden} from '@react-aria/visually-hidden';
import {useFocusRing} from '@react-aria/focus';

function Checkbox(
  props
) {
  let state =
    useToggleState(
      props
    );
  let ref = React
    .useRef();
  let { inputProps } =
    useCheckbox(
      props,
      state,
      ref
    );
  let {
    isFocusVisible,
    focusProps
  } = useFocusRing();

  return (
    <label
      style={{
        display: 'flex',
        alignItems:
          'center'
      }}
    >
      <VisuallyHidden>
        <input
          {...inputProps}
          {...focusProps}
          ref={ref}
        />
      </VisuallyHidden>
      <svg
        width={24}
        height={24}
        aria-hidden="true"
        style={{
          marginRight: 4
        }}
      >
        <rect
          x={state
              .isSelected
            ? 4
            : 5}
          y={state
              .isSelected
            ? 4
            : 5}
          width={state
              .isSelected
            ? 16
            : 14}
          height={state
              .isSelected
            ? 16
            : 14}
          fill={state
              .isSelected
            ? 'orange'
            : 'none'}
          stroke={state
              .isSelected
            ? 'none'
            : 'gray'}
          strokeWidth={2}
        />
        {state
          .isSelected &&
          (
            <path
              transform="translate(7 7)"
              d={`M3.788 9A.999.999 0 0 1 3 8.615l-2.288-3a1 1 0 1 1
            1.576-1.23l1.5 1.991 3.924-4.991a1 1 0 1 1 1.576 1.23l-4.712
            6A.999.999 0 0 1 3.788 9z`}
            />
          )}
        {isFocusVisible &&
          (
            <rect
              x={1}
              y={1}
              width={22}
              height={22}
              fill="none"
              stroke="orange"
              strokeWidth={2}
            />
          )}
      </svg>
      {props.children}
    </label>
  );
}

<Checkbox>
  Foo
</Checkbox>

Internationalization#


RTL#

In right-to-left languages, the checkbox should be mirrored. The checkbox should be placed on the right side of the label. Ensure that your CSS accounts for this.