# Field: Checkbox (category: toggle)

URL: https://docs.forms.saastro.io/docs/checkbox

<FieldPreview name="checkbox" description="Interactive demo of checkbox field" />

## Overview

The checkbox field is a boolean toggle for accepting terms, toggling options, or confirming choices. For multiple checkboxes that share a common label, see [Checkbox Group](/docs/checkbox-group).

## Usage

### Basic Checkbox

```tsx

const config = FormBuilder.create('signup')
  .addField('newsletter', (f) =>
    f
      .type('checkbox')
      .label('Subscribe to newsletter')
      .helperText('Get weekly updates and tips')
      .optional(),
  )
  .addStep('main', ['newsletter'])
  .build();
```

### Terms Acceptance

Use `mustBeTrue()` to require the checkbox to be checked:

```tsx
.addField('terms', (f) =>
  f.type('checkbox')
    .label('I agree to the Terms of Service and Privacy Policy')
    .mustBeTrue('You must accept the terms to continue')
)
```

If you omit the custom message, the default error is `"You must accept this"`.

### With Description

```tsx
.addField('marketing', (f) =>
  f.type('checkbox')
    .label('Marketing emails')
    .helperText('Receive promotional content, product updates, and special offers. You can unsubscribe at any time.')
    .optional()
)
```

### JSON Configuration

```json
{
  "type": "checkbox",
  "label": "I agree to the Terms of Service",
  "schema": {
    "mustBeTrue": true,
    "mustBeTrueMessage": "You must accept the terms"
  }
}
```

A shorter JSON form also works: a top-level `"required": true` with no `"schema"` key requires the checkbox to be checked, with the default error message `"You must accept this"`:

```json
{
  "type": "checkbox",
  "label": "I agree to the Terms of Service",
  "required": true
}
```

## Props

| Prop         | Type                                                  | Default | Description                         |
| ------------ | ----------------------------------------------------- | ------- | ----------------------------------- |
| `type`       | `'checkbox'`                                          | -       | Field type (required)               |
| `label`      | `string`                                              | -       | Label shown next to checkbox        |
| `helperText` | `string`                                              | -       | Description text below the checkbox |
| `columns`    | `Partial<Record<Breakpoint, number>>`                 | -       | Grid columns by breakpoint          |
| `disabled`   | `boolean \| function \| ConditionGroup`               | `false` | Disable the checkbox                |
| `hidden`     | `boolean \| function \| ConditionGroup \| Responsive` | `false` | Hide the field                      |

## Validation

### Optional

```tsx
.addField('newsletter', (f) =>
  f.type('checkbox')
    .label('Subscribe')
    .optional() // Initial value is false; both checked and unchecked are valid
)
```

### Required to be True

```tsx
.addField('terms', (f) =>
  f.type('checkbox')
    .label('Accept terms')
    .mustBeTrue('You must accept the terms')
)
```

Note: with the builder, use `mustBeTrue()` — not `required()` — to force the box to be checked. `required()` on a checkbox only validates that the value is a boolean, so an unchecked box (`false`) still passes. Without a custom message, `mustBeTrue()` shows `"You must accept this"`.

### With Zod

```tsx

.addField('terms', (f) =>
  f.type('checkbox')
    .label('Accept terms')
    .schema(z.literal(true, { message: 'You must accept the terms' }))
)
```

## Conditional Logic

### Show checkbox based on another field

```tsx
.addField('premium', (f) =>
  f.type('checkbox')
    .label('Upgrade to Premium')
    .hidden({
      conditions: [{ field: 'plan', operator: 'equals', value: 'free' }],
      operator: 'AND',
    })
)
```

`hidden()` takes a `ConditionGroup` — an object with a `conditions` array and an `'AND' | 'OR'` operator — not a bare condition.

### Disable based on condition

```tsx
.addField('confirm', (f) =>
  f.type('checkbox')
    .label('Confirm changes')
    .disabled((values) => !values.hasChanges)
)
```

## Styling

### Custom Classes

```tsx
.addField('terms', (f) =>
  f.type('checkbox')
    .label('Accept terms')
    .mustBeTrue()
    .classNames({
      wrapper: 'bg-muted/30 p-3 rounded-md border',
      input: 'border-primary data-[state=checked]:bg-primary',
      label: 'font-medium',
      error: 'text-destructive text-sm',
    })
)
```

### Responsive Layout

```tsx
.addField('newsletter', (f) =>
  f.type('checkbox')
    .label('Subscribe to newsletter')
    .optional()
    .columns({ default: 12, md: 6 })
)
```

Per-field column spans apply only when the form uses manual layout mode (`.layout('manual')` with the builder, or `"layout": { "mode": "manual" }` in JSON) — in `'auto'` mode they are ignored. See the [Layout guide](/docs/layout).

### JSON Styling

```json
{
  "type": "checkbox",
  "label": "Accept terms",
  "schema": { "mustBeTrue": true },
  "wrapper_className": "bg-muted/30 p-3 rounded-md",
  "label_className": "font-medium",
  "columns": { "default": 12 }
}
```

## Related Fields

- [Switch](/docs/switch) - Toggle switch (more prominent visual)
- [Checkbox Group](/docs/checkbox-group) - Multiple related checkboxes
- [Button Checkbox](/docs/button-checkbox) - Checkboxes styled as buttons

## Accessibility

The checkbox control is rendered by the `Checkbox` component you inject (via the `components` prop or `ComponentProvider`), using the `checked` / `onCheckedChange` contract. Accessibility characteristics therefore come from your component library:

- With Radix-based components (e.g. shadcn/ui), you get keyboard activation (Space), focus-visible states, and screen reader announcements out of the box
- The `label` prop renders an associated label next to the control
- `helperText` renders description text, and validation errors render below the field
