Skip to content

Early previewhamur is not on npm yet. The components are being built in the open, follow along on GitHub.

InputsReact

Checkbox Group

Provides shared state to a series of checkboxes: one array value for all of them, and an optional parent checkbox that checks or clears them together.

Rendered by React 19.3.1

Loading React…
import * as React from 'react'
import { Checkbox } from '@hamur/react/checkbox'
import { CheckboxGroup } from '@hamur/react/checkbox-group'

const fruits = ['apple', 'banana', 'cherry']

export default function CheckboxGroupExample() {
  const [value, setValue] = React.useState<string[]>(['banana'])
  return (
    <div className="stack">
      <CheckboxGroup
        className="stack"
        allValues={fruits}
        value={value}
        onValueChange={setValue}
      >
        <label className="row">
          <Checkbox.Root className="checkbox" parent>
            <Checkbox.Indicator className="checkbox-indicator"><span className="checkbox-check">✓</span><span className="checkbox-dash">–</span></Checkbox.Indicator>
          </Checkbox.Root>
          All fruit
        </label>
        {fruits.map((fruit) => (
          <label className="row nested" key={fruit}>
            <Checkbox.Root className="checkbox" value={fruit}>
              <Checkbox.Indicator className="checkbox-indicator">✓</Checkbox.Indicator>
            </Checkbox.Root>
            {fruit}
          </label>
        ))}
      </CheckboxGroup>
      <p className="muted">Selected: {value.join(', ') || 'none'}</p>
    </div>
  )
}
199Tests passing in React · 0 failing
5.0 KiBSize
Not measured yetMemory
Not measured yetSpeed
productdevbook and 11 upstream authors13 commits · last

Installation

npm i @hamur/react

Not on npm yet. The package goes out under the chosen name.

Anatomy

Import the component and assemble its parts:

<CheckboxGroup />

Examples

Parent

allValues and a parent checkbox check or clear every child; the parent is mixed in between.

CheckboxGroupParent.tsx
import * as React from 'react'
import { Checkbox } from '@hamur/react/checkbox'
import { CheckboxGroup } from '@hamur/react/checkbox-group'

const apples = [
  { value: 'fuji-apple', label: 'Fuji' },
  { value: 'gala-apple', label: 'Gala' },
  { value: 'granny-smith-apple', label: 'Granny Smith' },
]

export default function CheckboxGroupParentExample() {
  const [value, setValue] = React.useState<string[]>([])
  return (
    <CheckboxGroup
      aria-labelledby="apples-caption"
      className="checkbox-tree"
      allValues={apples.map((apple) => apple.value)}
      value={value}
      onValueChange={setValue}
    >
      <label className="row" id="apples-caption">
        <Checkbox.Root className="checkbox" parent>
          <Checkbox.Indicator className="checkbox-indicator">
            <span className="checkbox-check">✓</span>
            <span className="checkbox-dash">–</span>
          </Checkbox.Indicator>
        </Checkbox.Root>
        Apples
      </label>
      {apples.map((apple) => (
        <label className="row nested" key={apple.value}>
          <Checkbox.Root className="checkbox" value={apple.value}>
            <Checkbox.Indicator className="checkbox-indicator">✓</Checkbox.Indicator>
          </Checkbox.Root>
          {apple.label}
        </label>
      ))}
    </CheckboxGroup>
  )
}

Nested

A parent checkbox controls a nested group of parents and children.

CheckboxGroupNested.tsx
import * as React from 'react'
import { Checkbox } from '@hamur/react/checkbox'
import { CheckboxGroup } from '@hamur/react/checkbox-group'

const mainPermissions = ['view-dashboard', 'manage-users', 'access-reports']
const userManagementPermissions = ['create-user', 'edit-user', 'delete-user', 'assign-roles']
const management = [
  { value: 'create-user', label: 'Create User' },
  { value: 'edit-user', label: 'Edit User' },
  { value: 'delete-user', label: 'Delete User' },
  { value: 'assign-roles', label: 'Assign Roles' },
]

export default function CheckboxGroupNestedExample() {
  const [mainValue, setMainValue] = React.useState<string[]>([])
  const [managementValue, setManagementValue] = React.useState<string[]>([])
  return (
    <CheckboxGroup
      aria-labelledby="permissions-caption"
      className="checkbox-tree"
      allValues={mainPermissions}
      value={mainValue}
      onValueChange={(value: string[]) => { if (value.includes('manage-users')) { setManagementValue(userManagementPermissions) } else if (managementValue.length === userManagementPermissions.length) { setManagementValue([]) } setMainValue(value) }}
    >
      <label className="row" id="permissions-caption">
        <Checkbox.Root
          className="checkbox"
          parent
          indeterminate={
            managementValue.length > 0 &&
            managementValue.length !== userManagementPermissions.length
          }
        >
          <Checkbox.Indicator className="checkbox-indicator">
            <span className="checkbox-check">✓</span>
            <span className="checkbox-dash">–</span>
          </Checkbox.Indicator>
        </Checkbox.Root>
        User Permissions
      </label>
      <label className="row nested">
        <Checkbox.Root className="checkbox" value="view-dashboard">
          <Checkbox.Indicator className="checkbox-indicator">✓</Checkbox.Indicator>
        </Checkbox.Root>
        View Dashboard
      </label>
      <label className="row nested">
        <Checkbox.Root className="checkbox" value="access-reports">
          <Checkbox.Indicator className="checkbox-indicator">✓</Checkbox.Indicator>
        </Checkbox.Root>
        Access Reports
      </label>
      <CheckboxGroup
        aria-labelledby="manage-users-caption"
        className="checkbox-tree nested"
        allValues={userManagementPermissions}
        value={managementValue}
        onValueChange={(value) => {
          if (value.length === userManagementPermissions.length) {
            setMainValue((prev) => Array.from(new Set([...prev, 'manage-users'])))
          } else {
            setMainValue((prev) => prev.filter((v) => v !== 'manage-users'))
          }
          setManagementValue(value)
        }}
      >
        <label className="row" id="manage-users-caption">
          <Checkbox.Root className="checkbox" parent>
            <Checkbox.Indicator className="checkbox-indicator">
              <span className="checkbox-check">✓</span>
              <span className="checkbox-dash">–</span>
            </Checkbox.Indicator>
          </Checkbox.Root>
          Manage Users
        </label>
        {management.map((permission) => (
          <label className="row nested" key={permission.value}>
            <Checkbox.Root className="checkbox" value={permission.value}>
              <Checkbox.Indicator className="checkbox-indicator">✓</Checkbox.Indicator>
            </Checkbox.Root>
            {permission.label}
          </label>
        ))}
      </CheckboxGroup>
    </CheckboxGroup>
  )
}

API reference

5 props across 1 parts, in React syntax. className, style, render are listed once, under Shared props.

CheckboxGroupCheckboxGroup

Provides a shared state to a series of checkboxes.

CheckboxGroup props
PropTypeDefault
defaultValueNames of the checkboxes in the group that should be initially ticked.string[]—
valueNames of the checkboxes in the group that should be ticked.string[]—
onValueChangeEvent handler called when a checkbox in the group is ticked or unticked.((value: string[], eventDetails: { reason: "none"; event: Event; cancel: () => void; allowPropagation: () => void; isCanceled: boolean; isPropagationAllowed: boolean; trigger: Element | undefined; }) => void)—
allValuesNames of all checkboxes in the group.string[]—
disabledWhether the component should ignore user interaction.booleanfalse
CheckboxGroup data attributes
Data attributeDescription
data-disabledPresent when the checkbox group is disabled.

Shared props

Every part that renders an element takes these.

Shared props
PropType
classNameCSS class applied to the element, or a function that returns a class based on the component's state.string | ((state: State) => string | undefined)
styleStyle applied to the element, or a function that returns a style object based on the component's state.React.CSSProperties | ((state: State) => React.CSSProperties | undefined)
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, State>

Keyboard and accessibility

Keyboard

Each checkbox keeps its own keys (Space toggles it); the group adds none.

Accessibility

What the parts set
PartSetsSo that
CheckboxGrouprole="group"the checkboxes are announced as one set
CheckboxGrouparia-labelledby to the Field label around itthe set is read with its name
CheckboxGrouparia-controls on the parent checkbox, listing the child checkboxesa screen reader knows which boxes the parent changes

Focus order

  1. Each checkbox is its own tab stop, in document order; the group adds none.