# Checkbox

> Checkbox and radio controls for picking options, with a linked label and description.

Source: /docs/components/checkbox

```tsx
import { Checkbox, Radio, RadioGroup } from '@aksornproject/aksara-ui'
```

## Label and description

`label` renders a real `<label>` linked to the control, so clicking the text toggles it. `description` renders under the label and is linked with `aria-describedby`. A long label wraps and the control stays on its first line.

```tsx
import { Checkbox } from '@aksornproject/aksara-ui'

export default function CheckboxBasic() {
  return (
    <div className="flex flex-col gap-spacing-12">
      <Checkbox
        label="Email me about new lessons"
        description="A weekly digest, every Monday."
        defaultChecked
      />
      <Checkbox label="Send reminders before class" />
    </div>
  )
}
```

## Indeterminate

`indeterminate` draws a dash for a parent whose children are partly selected. The library does not wrap Base UI `CheckboxGroup`, so you derive `checked` and `indeterminate` from your own state. `onCheckedChange` receives the next checked boolean first.

```tsx
import { Checkbox } from '@aksornproject/aksara-ui'
import { useState } from 'react'

const SUBJECTS = ['Thai', 'Mathematics', 'Science']

export default function CheckboxIndeterminate() {
  const [selected, setSelected] = useState(['Thai'])
  const all = selected.length === SUBJECTS.length
  const some = selected.length > 0 && !all

  function toggle(subject: string, checked: boolean) {
    setSelected((current) =>
      checked
        ? [...current, subject]
        : current.filter((item) => item !== subject),
    )
  }

  return (
    <div className="flex flex-col gap-spacing-12">
      <Checkbox
        label="All subjects"
        checked={all}
        indeterminate={some}
        onCheckedChange={(checked) => setSelected(checked ? SUBJECTS : [])}
      />
      <div className="flex flex-col gap-spacing-12 ps-spacing-32">
        {SUBJECTS.map((subject) => (
          <Checkbox
            key={subject}
            label={subject}
            checked={selected.includes(subject)}
            onCheckedChange={(checked) => toggle(subject, checked)}
          />
        ))}
      </div>
    </div>
  )
}
```

## Sizes

`medium` is a 20px control and the default. `large` is 24px. The label and description are 16px at both sizes.

```tsx
import { Checkbox } from '@aksornproject/aksara-ui'

export default function CheckboxSizes() {
  return (
    <>
      <Checkbox label="Medium" defaultChecked />
      <Checkbox size="large" label="Large" defaultChecked />
    </>
  )
}
```

## States

`disabled` greys the control and its text and sets a `not-allowed` cursor. `readOnly` keeps the default look without hover and blocks changes. `loading` turns the control, label and description into skeleton blocks, sets `aria-busy` and blocks changes.

```tsx
import { Checkbox } from '@aksornproject/aksara-ui'

export default function CheckboxStates() {
  return (
    <div className="flex flex-col gap-spacing-12">
      <Checkbox label="Disabled" disabled />
      <Checkbox label="Disabled and checked" disabled defaultChecked />
      <Checkbox label="Read-only" readOnly defaultChecked />
      <Checkbox label="Loading" description="Fetching your settings" loading />
    </div>
  )
}
```

## Required

`required` marks the hidden input required and draws a red star after the label. Without a label there is no star. A Checkbox submits with its form through a hidden native input, which carries `name`, `value` and `required`.

```tsx
import { Checkbox } from '@aksornproject/aksara-ui'

export default function CheckboxRequired() {
  return (
    <Checkbox
      required
      name="terms"
      label="I accept the terms of use"
      description="You can read them on the Help page."
    />
  )
}
```

## Without a label

Without `label` or `description` the Checkbox renders only the control and its hidden input. Name it with `aria-label`, or point an external `<label htmlFor>` at its `id`. `className` lands on the control in both cases.

```tsx
import { Checkbox } from '@aksornproject/aksara-ui'

export default function CheckboxBare() {
  return (
    <>
      <Checkbox aria-label="Select row" />
      <span className="flex items-center gap-spacing-8">
        <Checkbox id="select-all-students" />
        <label htmlFor="select-all-students">Select all students</label>
      </span>
    </>
  )
}
```

## Radio

`mode="radio"` renders a radio with the same sizes, states, label and description. A radio needs a `value` and a surrounding `RadioGroup`, which owns the value. TypeScript rejects `checked`, `defaultChecked`, `onCheckedChange` and `indeterminate` on a radio. `RadioGroup` adds no layout, so pass the classes you need. A disabled or read-only `RadioGroup` styles its items.

```tsx
import { Checkbox, RadioGroup } from '@aksornproject/aksara-ui'

export default function CheckboxRadio() {
  return (
    <RadioGroup
      defaultValue="monthly"
      aria-label="Billing"
      className="flex flex-col gap-spacing-12"
    >
      <Checkbox mode="radio" value="monthly" label="Monthly" />
      <Checkbox
        mode="radio"
        value="yearly"
        label="Yearly"
        description="Two months free"
      />
      <Checkbox
        mode="radio"
        value="school"
        label="School licence"
        description="Contact sales"
        disabled
      />
    </RadioGroup>
  )
}
```

## Radio namespace

`Radio` groups the radio parts under one name. `Radio.Group` is `RadioGroup`, and `Radio.Item` is a Checkbox with `mode="radio"`. The two forms render the same thing, so pick one and use it across the app.

```tsx
import { Radio } from '@aksornproject/aksara-ui'

export default function CheckboxRadioNamespace() {
  return (
    <Radio.Group
      defaultValue="th"
      aria-label="Language"
      className="flex flex-col gap-spacing-12"
    >
      <Radio.Item value="th" label="ภาษาไทย" />
      <Radio.Item value="en" label="English" />
    </Radio.Group>
  )
}
```

## Accessibility

- Give every control a name with `label`, an external `<label htmlFor>` or `aria-label`.
- An indeterminate checkbox announces `aria-checked="mixed"`.
- The required star is `aria-hidden`. The control announces `aria-required`.
- `loading` sets `aria-busy` and blocks changes.
- Arrow keys move the selection inside a `RadioGroup`.
- A decorative control inside a clickable card can take `tabIndex={-1}`, `aria-hidden` and `pointer-events-none`. The card then owns the role and the keyboard.

## API

### Checkbox

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, CheckboxRootState> \| ComponentRenderFn<HTMLProps, RadioRootState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `React.CSSProperties \| ((state: CheckboxRootState) => React.CSSProperties \| undefined) \| ((state: RadioRootState) => React.CSSProperties \| undefined)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |
| `nativeButton` | `boolean` | `false` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `true` if the rendered element is a native button. |
| `id` | `string` |  | The id of the input element. |
| `checked` | `boolean` | `undefined` | Whether the checkbox is currently ticked. To render an uncontrolled checkbox, use the `defaultChecked` prop instead. A radio takes its state from the `RadioGroup`. |
| `defaultChecked` | `boolean` | `false` | Whether the checkbox is initially ticked. To render a controlled checkbox, use the `checked` prop instead. A radio takes its state from the `RadioGroup`. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `onCheckedChange` | `((checked: boolean, eventDetails: CheckboxRootChangeEventDetails) => void)` |  | Event handler called when the checkbox is ticked or unticked. A radio reports through the `RadioGroup` `onValueChange`. |
| `readOnly` | `boolean` | `false` | Whether the user should be unable to tick or untick the checkbox. Whether the user should be unable to select the radio button. |
| `required` | `boolean` | `false` | Whether the user must tick the checkbox before submitting a form. Whether the user must choose a value before submitting a form. |
| `indeterminate` | `boolean` | `false` | Whether the checkbox is in a mixed state: neither ticked, nor unticked. A radio has no mixed state. |
| `inputRef` | `React.Ref<HTMLInputElement>` |  | A ref to access the hidden `<input>` element. A ref to access the hidden input element. |
| `value` | `unknown` |  | The checkbox's value. Identifies it within a [Checkbox Group](https://base-ui.com/react/components/checkbox-group), falling back to `name` when omitted. When submitting a form, a checked box submits `value`; with no `value`, it submits the native "on". The unique identifying value of the radio in a group. |
| `size` | `"large" \| "medium"` | `'medium'` | `'medium'` is a 20px control. `'large'` is 24px. |
| `label` | `ReactNode` |  | Rendered in a `<label>` linked to the control, so clicking it toggles. |
| `description` | `ReactNode` |  | Rendered under the label and linked with `aria-describedby`. |
| `loading` | `boolean` | `false` | Shows the skeleton fill, hides the text and blocks changes. |
| `indicatorClassName` | `string` |  | Classes for the indicator, the tick box or the radio dot. |
| `className` | `string` |  | Classes for the control, merged last. |
| `mode` | `"checkbox" \| "radio"` | `'checkbox'` | Renders a checkbox. `'radio'` renders a radio. A radio needs a `value` and a surrounding `RadioGroup`. |
| `checkedIndicatorSymbol` | `ReactNode` |  | Replaces the tick icon. A radio draws a dot, not an icon. |
| `indeterminateIndicatorSymbol` | `ReactNode` |  | Replaces the dash icon. A radio draws a dot, not an icon. |

### RadioGroup

`Radio.Group` takes the same props.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, RadioGroupState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `React.CSSProperties \| ((state: RadioGroupState) => React.CSSProperties \| undefined)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `readOnly` | `boolean` | `false` | Whether the user should be unable to select a different radio button in the group. |
| `required` | `boolean` | `false` | Whether the user must choose a value before submitting a form. |
| `name` | `string` |  | Identifies the field when a form is submitted. |
| `form` | `string` |  | Identifies the form that owns the radio inputs. Useful when the radio group is rendered outside the form. |
| `value` | `unknown` |  | The controlled value of the radio item that should be currently selected. To render an uncontrolled radio group, use the `defaultValue` prop instead. |
| `defaultValue` | `unknown` |  | The uncontrolled value of the radio button that should be initially selected. To render a controlled radio group, use the `value` prop instead. |
| `onValueChange` | `((value: unknown, eventDetails: RadioGroup.ChangeEventDetails) => void)` |  | Callback fired when the value changes. |
| `inputRef` | `React.Ref<HTMLInputElement>` |  | A ref to access the hidden input element. |
| `className` | `string` |  | Classes for the group. It adds no layout of its own. |

### Radio.Item

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, RadioRootState> \| React.ReactElement<unknown, string \| React.JSXElementConstructor<any>>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `React.CSSProperties \| ((state: RadioRootState) => React.CSSProperties \| undefined)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |
| `nativeButton` | `boolean` | `false` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `true` if the rendered element is a native button. |
| `value` (required) | `unknown` |  | The unique identifying value of the radio in a group. |
| `disabled` | `boolean` |  | Whether the component should ignore user interaction. |
| `required` | `boolean` |  | Whether the user must choose a value before submitting a form. |
| `readOnly` | `boolean` |  | Whether the user should be unable to select the radio button. |
| `inputRef` | `React.Ref<HTMLInputElement>` |  | A ref to access the hidden input element. |
| `size` | `"large" \| "medium"` | `'medium'` | `'medium'` is a 20px control. `'large'` is 24px. |
| `label` | `ReactNode` |  | Rendered in a `<label>` linked to the control, so clicking it toggles. |
| `description` | `ReactNode` |  | Rendered under the label and linked with `aria-describedby`. |
| `loading` | `boolean` | `false` | Shows the skeleton fill, hides the text and blocks changes. |
| `indicatorClassName` | `string` |  | Classes for the indicator, the tick box or the radio dot. |
| `className` | `string` |  | Classes for the control, merged last. |