Open UI

CheckboxGroup

A checkbox group lets users select one or more options from a list. For single-select, use RadioGroup; for a dropdown, use Select.

import { Checkbox, CheckboxGroup } from "@opengovsg/oui"export const Example = () => {  return (    <CheckboxGroup      defaultValue={["buenos-aires", "london"]}      label="Select cities"    >      <Checkbox value="buenos-aires">Buenos Aires</Checkbox>      <Checkbox value="sydney">Sydney</Checkbox>      <Checkbox value="san-francisco">San Francisco</Checkbox>      <Checkbox value="london">London</Checkbox>      <Checkbox value="tokyo">Tokyo</Checkbox>    </CheckboxGroup>  )}

Usage

Use CheckboxGroup when users can select multiple options from a list rendered inline. Use RadioGroup when only one option can be chosen. Use Select or ComboBox for longer lists best shown in a dropdown.

import { Checkbox, CheckboxGroup } from "@opengovsg/oui"
<CheckboxGroup label="Select cities" defaultValue={["sf"]}>
  <Checkbox value="sf">San Francisco</Checkbox>
  <Checkbox value="ny">New York</Checkbox>
  <Checkbox value="tokyo">Tokyo</Checkbox>
</CheckboxGroup>

Alternatively, install the component as local source via the shadcn CLI:

npx shadcn@latest add https://oui.open.gov.sg/r/checkbox-group.json
pnpm dlx shadcn@latest add https://oui.open.gov.sg/r/checkbox-group.json
npx shadcn@latest add https://oui.open.gov.sg/r/checkbox-group.json
bunx --bun shadcn@latest add https://oui.open.gov.sg/r/checkbox-group.json

CheckboxGroup renders a group of checkboxes where multiple options can be selected, built on React Aria's CheckboxGroup. It manages selection state, keyboard navigation, and ARIA semantics automatically. The size prop propagates to all child Checkbox items via context.

If the checkbox group does not have a visible label, pass aria-label or aria-labelledby to CheckboxGroup to identify it to assistive technology.

Anatomy

CheckboxGroup consists of:

  • CheckboxGroup — the group container; manages selection state, propagates size via context
    • Checkbox — one selectable option; renders the checkbox box, check icon, and label

Examples

Disabled

Use the isDisabled prop on CheckboxGroup to disable the entire group, or on individual Checkbox items to disable specific options.

import { Checkbox, CheckboxGroup } from "@opengovsg/oui"export const Example = () => {  return (    <CheckboxGroup      isDisabled      defaultValue={["buenos-aires", "london"]}      label="Select cities"    >      <Checkbox value="buenos-aires">Buenos Aires</Checkbox>      <Checkbox value="sydney">Sydney</Checkbox>      <Checkbox value="san-francisco">San Francisco</Checkbox>      <Checkbox value="london">London</Checkbox>      <Checkbox value="tokyo">Tokyo</Checkbox>    </CheckboxGroup>  )}

Invalid

Use the isInvalid prop along with errorMessage to display a validation error.

import { useState } from "react"import { Checkbox, CheckboxGroup } from "@opengovsg/oui"export const Example = () => {  const [isInvalid, setIsInvalid] = useState(true)  return (    <CheckboxGroup      isRequired      description="Select the cities you want to visit"      isInvalid={isInvalid}      label="Select cities"      onChange={(value) => {        setIsInvalid(value.length < 1)      }}    >      <Checkbox value="buenos-aires">Buenos Aires</Checkbox>      <Checkbox value="sydney">Sydney</Checkbox>      <Checkbox value="san-francisco">San Francisco</Checkbox>      <Checkbox value="london">London</Checkbox>      <Checkbox value="tokyo">Tokyo</Checkbox>    </CheckboxGroup>  )}

With Checkbox Input

A Checkbox can contain arbitrary children, including other form inputs, for advanced use cases such as an "Other" option that reveals a text field.

import { useRef } from "react"import { Checkbox, CheckboxGroup, Input } from "@opengovsg/oui"export const Example = () => {  const checkboxRef = useRef<HTMLInputElement>(null)  const inputRef = useRef<HTMLInputElement>(null)  return (    <CheckboxGroup>      <Checkbox value="sf">San Francisco</Checkbox>      <Checkbox value="ny">New York</Checkbox>      <Checkbox value="sydney">Sydney</Checkbox>      <Checkbox value="london">London</Checkbox>      <Checkbox value="tokyo">Tokyo</Checkbox>      <Checkbox        value="other"        inputRef={checkboxRef}        onChange={(checked) => {          // Upon checking checkbox, focus text input          if (checked) {            // setTimeout with a delay of 0 ms schedules the code to run after the current call stack is cleared.            // this allows us to wait until the focus event has finished propagating.            setTimeout(() => {              inputRef.current?.focus()            }, 0)          }        }}      >        <div className="flex flex-col gap-2">          Other          <Input            ref={inputRef}            onClick={(e) => e.stopPropagation()} // Prevent parent checkbox from being toggled due to event bubbling            onKeyDownCapture={(e) => e.stopPropagation()} // Prevent parent checkbox from being toggled due to event bubbling            onChange={(e) => {              // If there is text in the input, ensure the checkbox is checked.              if (e.target.value && !checkboxRef.current?.checked) {                checkboxRef.current?.click()              }            }}          />        </div>      </Checkbox>    </CheckboxGroup>  )}

Validation

See the Forms guide for end-to-end patterns including React Hook Form + Zod.

  • isRequired — marks the group as required; validation fires on form submit (native) or immediately (aria).
  • validate — a custom function (value: string[]) => ValidationError | true | null | undefined. Return a string to show as the error, or null/true for valid.
  • isInvalid + errorMessage — use together for realtime validation feedback without waiting for a form submit. errorMessage accepts a string or a function (validation: ValidationResult) => string.
  • validationBehavior"native" (default) blocks form submission when invalid; "aria" only marks the group via ARIA attributes and allows submission — required for React Hook Form.

Events

  • onChange — Called when the selected values change, receiving the new string[] array.
  • onFocus / onBlur — Called when any checkbox in the group receives or loses focus.
  • onFocusChange — Called when the focus state of the group changes, receiving a boolean.

Accessibility

  • CheckboxGroup is built on a <div role="group"> with full ARIA semantics managed by React Aria.
  • The group label, description, and error message are automatically wired via aria-labelledby and aria-describedby.
  • If no visible label is provided, pass aria-label or aria-labelledby to CheckboxGroup.
  • Keyboard interaction: Tab to move focus between checkboxes; Space to toggle the focused checkbox.
  • An indeterminate state (isIndeterminate) renders a minus icon instead of a check icon and is supported on individual Checkbox items.

Slots

Slots are named regions of the component you can target with custom Tailwind classes via the classNames prop. Each slot below corresponds to a key on the classNames object.

CheckboxGroup slots:

  • base: The root flex-column container wrapping the label, checkbox items, description, and error.
  • label: The label element rendered above the checkbox items.
  • description: The helper text rendered below the checkbox items.
  • error: Nested object of FieldError slots — pass { icon: "…", text: "…" } to style the error icon and text independently.

Checkbox slots (via Checkbox's classNames):

  • base: The checkbox row wrapper; handles alignment, spacing, and interactive state.
  • box: The square checkbox element that shows the check or indeterminate icon.
  • icon: The check or minus icon rendered inside the box when selected or indeterminate.

Custom Styles

You can customize the Checkbox and CheckboxGroup components by passing custom Tailwind CSS classes to the component slots via the classNames prop.

<CheckboxGroup label="Select cities" classNames={{ base: "gap-4" }}>
  <Checkbox
    value="sf"
    classNames={{
      base: "border rounded-lg border-gray-200 p-4 data-[selected]:border-interaction-main-default",
    }}
  >
    San Francisco
  </Checkbox>
</CheckboxGroup>

Props

CheckboxGroup

PropTypeDefaultDescription
childrenReactNode-The Checkbox items in the group
labelstring-The visible label for the group
valuestring[]-The currently selected values (controlled)
defaultValuestring[]-The default selected values (uncontrolled)
onChange(value: string[]) => void-Called when the selected values change
isDisabledbooleanfalseWhether the entire group is disabled
isReadOnlybooleanfalseWhether the group is read-only
isRequiredbooleanfalseWhether the group is required
isInvalidbooleanfalseWhether the group should display as invalid
validate(value: string[]) => ValidationError | true | null | undefined-Custom validation function
errorMessagestring | ((validation: ValidationResult) => string)-Error message to display when invalid
validationBehavior"native" | "aria""native"Whether to use native HTML form validation or ARIA-only
size"xs" | "sm" | "md""md"The size of all child Checkbox items
descriptionstring-Helper text shown below the checkbox items
classNamesSlotsToClasses<"base" | "label" | "description"> & { error?: SlotsToClasses<FieldErrorSlots> }-Custom Tailwind classes for component slots
namestring-The HTML name attribute; used for form submission
orientation"horizontal" | "vertical""vertical"The layout orientation of the checkbox items

Inherits all remaining props from React Aria's CheckboxGroup. Notable ones: autoFocus, excludeFromTabOrder, form.

errorMessage render-prop argument

When errorMessage is a function, it receives a ValidationResult object:

PropertyTypeDescription
isInvalidbooleanWhether the field is currently invalid
validationErrorsstring[]List of validation error strings
validationDetailsValidityStateThe native HTML ValidityState object

Checkbox

PropTypeDefaultDescription
childrenReactNode | ((renderProps: CheckboxRenderProps) => ReactNode)-The label content, or a render function
valuestring-The value included in the group's selected array when checked
isDisabledbooleanfalseWhether this specific checkbox is disabled
isReadOnlybooleanfalseWhether this specific checkbox is read-only
isSelectedboolean-Whether the checkbox is checked (controlled, standalone use)
defaultSelectedboolean-Whether the checkbox is checked by default (uncontrolled, standalone)
isIndeterminatebooleanfalseWhether to show the indeterminate (minus) state
isInvalidbooleanfalseWhether this specific checkbox is in an invalid state
onChange(isSelected: boolean) => void-Called when the checkbox state changes (standalone use)
classNamesSlotsToClassesWithRenderProps<"base" | "box" | "icon", CheckboxRenderProps>-Custom Tailwind classes for checkbox slots
inputRefRefObject<HTMLInputElement | null>-A ref for the underlying <input type="checkbox"> element

Inherits all remaining props from React Aria's Checkbox. Notable ones: autoFocus, id, name, form.

On this page