---
title: "Radios and checkboxes"
description: "FormRadio and FormCheckbox, single choice controls with a token-styled box, an HTML label and checked, invalid and disabled states."
requested_language: nl
language: en
translation_notice: "This page isn't translated yet"
url: https://docs.systhema.app/nl/components/form-choice
version: unreleased (main)
docs_index: https://docs.systhema.app/nl/llms.txt
---
> This page isn't translated yet. Showing English.


`FormRadio` and `FormCheckbox` render one choice each: the native input, made invisible but still focusable, under a box drawn from the form tokens, followed by its label. For a labelled group with a description, an error message and group roles, use [`RadioField` and `CheckboxField`](https://docs.systhema.app/nl/components/form-fields.md#radiofield-and-checkboxfield).

```tsx preview title="Checkboxes"
import { FormCheckbox } from '@systhemaui/next'

export default function Demo() {
  return (
    <div style={{ display: 'grid', gap: 12 }}>
      <FormCheckbox
        id="choice-news"
        name="choice-topics"
        value="news"
        label="Product news"
        defaultChecked
      />
      <FormCheckbox
        id="choice-events"
        name="choice-topics"
        value="events"
        label="Events and webinars"
      />
      <FormCheckbox id="choice-offers" name="choice-topics" value="offers" label="Offers" />
    </div>
  )
}
```

## Import

```tsx
import { FormCheckbox, FormRadio } from '@systhemaui/next'
```

In a React app without Next.js, import them from `@systhemaui/react`.

## Examples

### Radios

Radios that share a `name` form one choice. Give each one its own `id`: without it, `id` falls back to `name`, so every radio of the group would get the same id and each label would point at the first radio.

```tsx preview title="Radio group"
import { FormRadio } from '@systhemaui/next'

export default function Demo() {
  return (
    <div style={{ display: 'grid', gap: 12 }}>
      <FormRadio id="plan-monthly" name="plan" value="monthly" label="Monthly" defaultChecked />
      <FormRadio id="plan-yearly" name="plan" value="yearly" label="Yearly (2 months free)" />
      <FormRadio id="plan-lifetime" name="plan" value="lifetime" label="Lifetime" />
    </div>
  )
}
```

### Labels with links

`label` is an HTML string, inserted with `dangerouslySetInnerHTML`, so it can carry a link or emphasis. Only pass trusted strings, never visitor input.

```tsx preview title="Consent checkbox"
import { FormCheckbox } from '@systhemaui/next'

export default function Demo() {
  return (
    <FormCheckbox
      id="choice-terms"
      name="choice-terms"
      value="accepted"
      label='I accept the <a class="link" href="#labels-with-links">terms and conditions</a>'
      required
    />
  )
}
```

### States

The box takes the `checked` colors when its input is checked, the `invalid` colors once a `required` input is `:user-invalid` (after the visitor has interacted with it or tried to submit), and the disabled opacity when the input is disabled.

```tsx preview title="Checked and disabled"
import { FormCheckbox, FormRadio } from '@systhemaui/next'

export default function Demo() {
  return (
    <div style={{ display: 'grid', gap: 12 }}>
      <FormCheckbox
        id="state-checked"
        name="state-checked"
        value="1"
        label="Checked"
        defaultChecked
      />
      <FormCheckbox id="state-disabled" name="state-disabled" value="1" label="Disabled" disabled />
      <FormRadio
        id="state-radio-disabled"
        name="state-radio"
        value="1"
        label="Disabled radio"
        disabled
        defaultChecked
      />
    </div>
  )
}
```

### Extra content

`prepend` and `append` render inside the wrapper before the input and after the label, for a badge or a price next to the choice. `wrapperProps` forwards attributes to the wrapper `<div>`; `className` also goes on the wrapper, while every other prop goes to the `<input>`.

## Props

### `FormRadio`

<!-- generated:props @systhemaui/react FormRadioProps -->

| Prop                          | Type                                                                             | Default | Description |
| ----------------------------- | -------------------------------------------------------------------------------- | ------- | ----------- |
| `value` (required)            | `string`                                                                         | -       |             |
| `label` (required)            | `string`                                                                         | -       |             |
| `name` (required)             | `string`                                                                         | -       |             |
| `id`                          | `string`                                                                         | -       |             |
| `prepend`                     | `ReactNode`                                                                      | -       |             |
| `append`                      | `ReactNode`                                                                      | -       |             |
| `className`                   | `string`                                                                         | -       |             |
| `wrapperProps`                | `Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, 'ref'>` | -       |             |
| …and all `<input>` attributes |                                                                                  |         |             |

<!-- /generated -->

### `FormCheckbox`

<!-- generated:props @systhemaui/react FormCheckboxProps -->

| Prop                          | Type                                                                             | Default | Description |
| ----------------------------- | -------------------------------------------------------------------------------- | ------- | ----------- |
| `value` (required)            | `string`                                                                         | -       |             |
| `name` (required)             | `string`                                                                         | -       |             |
| `id`                          | `string`                                                                         | -       |             |
| `label` (required)            | `string`                                                                         | -       |             |
| `prepend`                     | `ReactNode`                                                                      | -       |             |
| `append`                      | `ReactNode`                                                                      | -       |             |
| `className`                   | `string`                                                                         | -       |             |
| `wrapperProps`                | `Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, 'ref'>` | -       |             |
| …and all `<input>` attributes |                                                                                  |         |             |

<!-- /generated -->

## HTML and CSS

```html
<div class="form-checkbox">
  <input type="checkbox" id="news" name="topics" value="news" />
  <label for="news">Product news</label>
</div>

<div class="form-radio">
  <input type="radio" id="plan-monthly" name="plan" value="monthly" checked />
  <label for="plan-monthly">Monthly</label>
</div>
```

The wrapper draws the box with `::before` and the mark with `::after`: a dot for radios, and for checkboxes the icon set with `blocks.form.checkboxIcon` (see [Component CSS blocks](https://docs.systhema.app/nl/styling/blocks.md)). Wrap several choices in `.form-inline-input-group` to space them by `--form-input-inline-space-y`.

<!-- generated:utilities form -->

| Class                     | Styles                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `form`                    | `& > :not([hidden], input[type="hidden"]) ~ :not([hidden], input[type="hidden"]) { margin-top: var(--form-row-gap-y); }`                                                                                                                                                                                                                                                                                                                                                                                                        |
| `form-row`                | `@media (width < 640px) { & { display: flex; flex-direction: column; row-gap: var(--form-row-gap-y); } }`<br>`@media (width >= 640px) { & { display: flex; flex-wrap: wrap; gap: var(--form-row-gap-x); } }`<br>`@media (width >= 640px) { & > .form-group { flex: 1 1 0%; min-width: 0; } }`<br>`@media (width >= 640px) { & > .form-group[style*="--width"] { flex: 0 0 auto; width: calc(var(--width) - var(--form-row-gap-x) * (1 - var(--width) / 100%)); } }`                                                             |
| `form-group`              | `@media (width >= 640px) { .form-row > .form-group { flex: 1 1 0%; min-width: 0; } }`<br>`@media (width >= 640px) { .form-row > .form-group[style*="--width"] { flex: 0 0 auto; width: calc(var(--width) - var(--form-row-gap-x) * (1 - var(--width) / 100%)); } }`<br>`& > :not([hidden]) ~ :not([hidden]) { margin-top: var(--form-group-gap-y); }`<br>`&:has(:required) .form-label::after { content: ' *'; color: var(--color-form-label-required); }`<br>+ 1 more rule                                                     |
| `form-label`              | `& { display: block; color: var(--color-form-label); }`<br>`& { font-family: var(--font-form-label-font-family); font-size: var(--typography-form-label-font-size); font-weight: var(--font-form-label-font-weight); letter-spacing: var(--typography-form-label-letter-spacing); line-height: var(--typography-form-label-line-height); text-transform: none; text-decoration: none; font-style: var(--font-form-label-font-style); }`<br>+ 1 more rule                                                                        |
| `form-description`        | `& { display: block; color: var(--color-form-description); }`<br>`& { font-family: var(--font-form-description-font-family); font-size: var(--typography-form-description-font-size); font-weight: var(--font-form-description-font-weight); letter-spacing: var(--typography-form-description-letter-spacing); line-height: var(--typography-form-description-line-height); text-transform: none; text-decoration: none; font-style: var(--font-form-description-font-style); }`                                               |
| `form-input`              | `& { --tw-shadow-color: var(--color-form-input-block-default-shadow); --tw-shadow: var(--tw-shadow-color); width: 100%; outline: none; border-style: solid; padding-inline: var(--form-input-block-padding-x); padding-block: var(--form-input-block-padding-y); border-radius: var(--form-input-block-border-radius); border-width: var(--form-input-block-border-width); background-color: var(--color-form-input-block-default-background); border-color: var(--color-form-input-block-defaul…`<br>+ 4 more rules            |
| `form-select`             | `& { --tw-shadow-color: var(--color-form-input-block-default-shadow); --tw-shadow: var(--tw-shadow-color); width: 100%; outline: none; border-style: solid; padding-inline: var(--form-input-block-padding-x); padding-block: var(--form-input-block-padding-y); border-radius: var(--form-input-block-border-radius); border-width: var(--form-input-block-border-width); background-color: var(--color-form-input-block-default-background); border-color: var(--color-form-input-block-defaul…`<br>+ 12 more rules           |
| `form-textarea`           | `& { --tw-shadow-color: var(--color-form-input-block-default-shadow); --tw-shadow: var(--tw-shadow-color); width: 100%; outline: none; border-style: solid; padding-inline: var(--form-input-block-padding-x); padding-block: var(--form-input-block-padding-y); border-radius: var(--form-input-block-border-radius); border-width: var(--form-input-block-border-width); background-color: var(--color-form-input-block-default-background); border-color: var(--color-form-input-block-defaul…`<br>+ 5 more rules            |
| `search-field-input`      | `& { --tw-shadow-color: var(--color-form-input-block-default-shadow); --tw-shadow: var(--tw-shadow-color); width: 100%; outline: none; border-style: solid; padding-inline: var(--form-input-block-padding-x); padding-block: var(--form-input-block-padding-y); border-radius: var(--form-input-block-border-radius); border-width: var(--form-input-block-border-width); background-color: var(--color-form-input-block-default-background); border-color: var(--color-form-input-block-defaul…`<br>+ 9 more rules            |
| `is-invalid`              | `:is(.form-select:user-invalid ~ .form-select-icon, .form-select.is-invalid ~ .form-select-icon) { color: var(--color-form-input-block-invalid-icon); background-color: var(--color-form-input-block-invalid-icon); }`<br>`:is(.form-group:has(:user-invalid) .form-invalid-message, .form-group:has(.is-invalid) .form-invalid-message) { display: block; }`                                                                                                                                                                   |
| `form-select-wrapper`     | `position: relative;`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `form-select-icon`        | `:is(.form-select ~ .form-select-icon) { pointer-events: none; position: absolute; top: 50%; transform: translateY(-50%); object-fit: contain; object-position: center; overflow: visible; line-height: 100%; right: var(--form-input-block-padding-x); width: var(--form-input-block-icon-size); height: var(--form-input-block-icon-size); font-size: var(--form-input-block-icon-size); color: var(--color-form-input-block-default-icon); }`<br>+ 3 more rules                                                              |
| `search-field`            | `position: relative;`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `search-field-icon`       | `:is(.search-field-input ~ .search-field-icon) { pointer-events: none; position: absolute; top: 50%; transform: translateY(-50%); object-fit: contain; object-position: center; overflow: visible; line-height: 100%; right: var(--form-input-block-padding-x); width: var(--form-input-block-icon-size); height: var(--form-input-block-icon-size); font-size: var(--form-input-block-icon-size); color: var(--color-form-input-block-default-icon); }`<br>+ 2 more rules                                                      |
| `form-inline-input-group` | `& > :not([hidden]) ~ :not([hidden]) { margin-top: var(--form-input-inline-space-y); }`                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `form-radio`              | `& { position: relative; user-select: none; }`<br>`& input[type="radio"], .form-radio  input[type="checkbox"], .form-radio:before { content: ""; position: absolute; left: 0; top: calc(var(--typography-form-inline-line-height) / 2); transform: translateY(-50%); width: var(--form-input-inline-input-size); height: var(--form-input-inline-input-size); }`<br>`& input[type="radio"], .form-radio  input[type="checkbox"] { opacity: 0; cursor: pointer; }`<br>`&:has(:checked):after { opacity: 1; }`<br>+ 13 more rules |
| `form-checkbox`           | `& { position: relative; user-select: none; }`<br>`& input[type="radio"], .form-checkbox  input[type="checkbox"], .form-checkbox:before { content: ""; position: absolute; left: 0; top: calc(var(--typography-form-inline-line-height) / 2); transform: translateY(-50%); width: var(--form-input-inline-input-size); height: var(--form-input-inline-input-size); }`<br>`& input[type="radio"], .form-checkbox  input[type="checkbox"] { opacity: 0; cursor: pointer; }`<br>+ 14 more rules                                   |
| `form-invalid-message`    | `& { display: none; color: var(--color-form-invalid-message); }`<br>`:is(.form-group:has(:user-invalid) .form-invalid-message, .form-group:has(.is-invalid) .form-invalid-message) { display: block; }`                                                                                                                                                                                                                                                                                                                         |
| `form-file-input`         | `& { cursor: pointer; padding: 0; height: calc(var(--form-input-block-padding-y) * 2 + var(--typography-form-block-line-height)); display: flex; align-items: center; }`<br>+ 2 more rules                                                                                                                                                                                                                                                                                                                                      |

<!-- /generated -->

## Next.js

`@systhemaui/next` re-exports `FormRadio` and `FormCheckbox` from `@systhemaui/react` unchanged.

## Accessibility

- The native input stays in the page (transparent, on top of the box), so keyboard focus, `Space` and screen-reader states work as usual.
- Name a group of radios or checkboxes: wrap it in a `role="radiogroup"` or `role="group"` element with `aria-labelledby`, or use `RadioField` / `CheckboxField`, which do it for you.
- A checkbox or radio has no visible focus style beyond what you add; the input is transparent, so style the box with a `:has(:focus-visible)` selector on `.form-checkbox` / `.form-radio` if your tokens need one.

## Related

- [Form fields](https://docs.systhema.app/nl/components/form-fields.md#radiofield-and-checkboxfield)
- [Form controls](https://docs.systhema.app/nl/components/form-controls.md)
