# Checkbox

> A control that allows the user to toggle between checked and not checked states.

**BNA UI** — a React Native / Expo component library.
These components render through `react-native`, not the DOM: there are no HTML
elements, no Tailwind classes and no Radix primitives. Source is copied into your
project and imported through `@/components/ui/*`, `@/components/charts/*`,
`@/hooks/*` and `@/theme/*`. Colours come from the `useColor` hook rather than
hardcoded hex; sizing tokens (`HEIGHT`, `FONT_SIZE`, `BORDER_RADIUS`, `CORNERS`)
come from `@/theme/globals`.

- Docs: https://ui.ahmedbna.com/docs/components/checkbox
- Markdown: https://ui.ahmedbna.com/docs/components/checkbox.md
- Structured JSON (props, usage, source, examples): https://ui.ahmedbna.com/r/ai/checkbox.json
- Install payload (source plus every file it imports): https://ui.ahmedbna.com/r/checkbox.json
- Install: `npx bna-ui add checkbox`
- npm dependencies: `expo-haptics`, `lucide-react-native`
- Registry dependencies: `mode-provider`, `useColorScheme`, `colors`, `useColor`, `useHaptics`, `globals`, `text`, `view`
- Preview recording: https://demo.ahmedbna.com/0104-checkbox-demo.MP4

---

**Example:** A basic checkbox with label

```tsx
// components/demo/checkbox/checkbox-demo.tsx
import { Checkbox } from '@/components/ui/checkbox';
import React, { useState } from 'react';

export function CheckboxDemo() {
  const [checked, setChecked] = useState(false);

  return (
    <Checkbox
      checked={checked}
      onCheckedChange={setChecked}
      label='Accept terms and conditions'
    />
  );
}
```

## Installation

### CLI

```bash
npx bna-ui add checkbox
```

### Manual

**1.** Install the following dependencies:

```bash
npx expo install lucide-react-native
```

**2.** Copy and paste the following code into your project.

```tsx
// components/ui/checkbox.tsx
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import { useColor } from '@/hooks/useColor';
import { useHaptics } from '@/hooks/useHaptics';
import { BORDER_RADIUS } from '@/theme/globals';
import { Check } from 'lucide-react-native';
import React from 'react';
import { TextStyle, TouchableOpacity } from 'react-native';

interface CheckboxProps {
  checked: boolean;
  label?: string;
  error?: string;
  disabled?: boolean;
  labelStyle?: TextStyle;
  onCheckedChange: (checked: boolean) => void;
  accessibilityLabel?: string;
  haptic?: boolean;
}

export function Checkbox({
  checked,
  error,
  disabled = false,
  label,
  labelStyle,
  onCheckedChange,
  accessibilityLabel,
  haptic = true,
}: CheckboxProps) {
  const primary = useColor('primary');
  const primaryForegroundColor = useColor('primaryForeground');
  const danger = useColor('red');
  const borderColor = useColor('border');
  const feedback = useHaptics(haptic);

  const handlePress = () => {
    if (disabled) return;
    feedback(checked ? 'toggle-off' : 'toggle-on');
    onCheckedChange(!checked);
  };

  return (
    <TouchableOpacity
      style={{
        flexDirection: 'row',
        alignItems: 'center',
        opacity: disabled ? 0.5 : 1,
        paddingVertical: 4,
      }}
      onPress={handlePress}
      disabled={disabled}
      hitSlop={{ top: 9, bottom: 9, left: 9, right: 9 }}
      accessibilityRole='checkbox'
      accessibilityState={{ checked, disabled }}
      accessibilityLabel={accessibilityLabel ?? label}
    >
      <View
        style={{
          width: BORDER_RADIUS,
          height: BORDER_RADIUS,
          borderRadius: BORDER_RADIUS,
          borderWidth: 1.5,
          borderColor: checked ? primary : borderColor,
          backgroundColor: checked ? primary : 'transparent',
          alignItems: 'center',
          justifyContent: 'center',
          marginRight: label ? 8 : 0,
        }}
      >
        {checked && (
          <Check
            size={16}
            color={primaryForegroundColor}
            strokeWidth={3}
            strokeLinecap='round'
          />
        )}
      </View>
      {label && (
        <Text
          variant='caption'
          numberOfLines={1}
          ellipsizeMode='tail'
          style={[
            {
              color: error ? danger : primary,
            },
            labelStyle,
          ]}
          pointerEvents='none'
        >
          {label}
        </Text>
      )}
    </TouchableOpacity>
  );
}
```

**3.** Update the import paths to match your project setup.

## Usage

```tsx
import { Checkbox } from '@/components/ui/checkbox';
```

```tsx
const [checked, setChecked] = useState(false);

<Checkbox
  checked={checked}
  onCheckedChange={setChecked}
  label='Accept terms and conditions'
/>;
```

## Examples

#### Default

**Example:** A basic checkbox with label

```tsx
// components/demo/checkbox/checkbox-demo.tsx
import { Checkbox } from '@/components/ui/checkbox';
import React, { useState } from 'react';

export function CheckboxDemo() {
  const [checked, setChecked] = useState(false);

  return (
    <Checkbox
      checked={checked}
      onCheckedChange={setChecked}
      label='Accept terms and conditions'
    />
  );
}
```

#### Different States

**Example:** Checkboxes in different states: unchecked, checked, and disabled

```tsx
// components/demo/checkbox/checkbox-states.tsx
import { Checkbox } from '@/components/ui/checkbox';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useState } from 'react';

export function CheckboxStates() {
  const [checked1, setChecked1] = useState(false);
  const [checked2, setChecked2] = useState(true);
  const [checked3, setChecked3] = useState(false);

  return (
    <View style={{ gap: 16 }}>
      <Text variant='subtitle' style={{ marginBottom: 8 }}>
        Different States
      </Text>

      <Checkbox
        checked={checked1}
        onCheckedChange={setChecked1}
        label='Unchecked'
      />

      <Checkbox
        checked={checked2}
        onCheckedChange={setChecked2}
        label='Checked'
      />

      <Checkbox
        checked={checked3}
        onCheckedChange={setChecked3}
        label='Disabled'
        disabled
      />
    </View>
  );
}
```

#### Without Label

**Example:** A checkbox without a label

```tsx
// components/demo/checkbox/checkbox-without-label.tsx
import { Checkbox } from '@/components/ui/checkbox';
import React, { useState } from 'react';

export function CheckboxWithoutLabel() {
  const [checked, setChecked] = useState(false);

  return <Checkbox checked={checked} onCheckedChange={setChecked} />;
}
```

#### With Error State

**Example:** A checkbox with error styling and message

```tsx
// components/demo/checkbox/checkbox-with-error.tsx
import { Checkbox } from '@/components/ui/checkbox';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useState } from 'react';

export function CheckboxWithError() {
  const [checked, setChecked] = useState(false);

  return (
    <View style={{ gap: 8 }}>
      <Checkbox
        checked={checked}
        onCheckedChange={setChecked}
        label='I agree to the terms'
        error='You must accept the terms to continue'
      />
      {!checked && (
        <Text variant='caption' style={{ color: 'red', marginLeft: 28 }}>
          You must accept the terms to continue
        </Text>
      )}
    </View>
  );
}
```

#### Custom Styling

**Example:** Checkboxes with custom label styling

```tsx
// components/demo/checkbox/checkbox-custom-styling.tsx
import { Checkbox } from '@/components/ui/checkbox';
import { View } from '@/components/ui/view';
import React, { useState } from 'react';

export function CheckboxCustomStyling() {
  const [checked1, setChecked1] = useState(false);
  const [checked2, setChecked2] = useState(true);

  return (
    <View style={{ gap: 16 }}>
      <Checkbox
        checked={checked1}
        onCheckedChange={setChecked1}
        label='Custom styled checkbox'
        labelStyle={{
          fontSize: 18,
          fontWeight: '600',
          color: '#6366f1',
        }}
      />

      <Checkbox
        checked={checked2}
        onCheckedChange={setChecked2}
        label='Another custom style'
        labelStyle={{
          fontSize: 16,
          fontStyle: 'italic',
          color: '#10b981',
        }}
      />
    </View>
  );
}
```

#### Checkbox Group

**Example:** Multiple checkboxes working together as a group

```tsx
// components/demo/checkbox/checkbox-group.tsx
import { Checkbox } from '@/components/ui/checkbox';
import { Text } from '@/components/ui/text';
import { View } from '@/components/ui/view';
import React, { useState } from 'react';

export function CheckboxGroup() {
  const [selectedItems, setSelectedItems] = useState<string[]>([]);

  const items = [
    { id: 'notifications', label: 'Email notifications' },
    { id: 'marketing', label: 'Marketing emails' },
    { id: 'updates', label: 'Product updates' },
    { id: 'newsletter', label: 'Weekly newsletter' },
  ];

  const handleItemChange = (itemId: string, checked: boolean) => {
    if (checked) {
      setSelectedItems((prev) => [...prev, itemId]);
    } else {
      setSelectedItems((prev) => prev.filter((id) => id !== itemId));
    }
  };

  return (
    <View style={{ gap: 16 }}>
      <Text variant='subtitle'>Subscription Preferences</Text>

      <View style={{ gap: 12 }}>
        {items.map((item) => (
          <Checkbox
            key={item.id}
            checked={selectedItems.includes(item.id)}
            onCheckedChange={(checked) => handleItemChange(item.id, checked)}
            label={item.label}
          />
        ))}
      </View>

      <Text variant='caption' style={{ marginTop: 8 }}>
        Selected: {selectedItems.length} item(s)
      </Text>
    </View>
  );
}
```

## API Reference

### Checkbox

A checkbox component that allows users to select or deselect an option. Uses `checked`/`onCheckedChange` (ARIA checkbox semantics) rather than `radio`'s `value`/`onValueChange` or `toggle`'s `pressed`/`onPressedChange` — each naming convention matches its own control's interaction model and is intentional, not an inconsistency.

| Prop                 | Type                         | Default | Description                                                               |
| -------------------- | ---------------------------- | ------- | ------------------------------------------------------------------------- |
| `haptic`             | `boolean`                    | `true`  | Whether to trigger haptic feedback when the checkbox is toggled.          |
| `checked`            | `boolean`                    | -       | Whether the checkbox is checked.                                          |
| `onCheckedChange`    | `(checked: boolean) => void` | -       | Callback function called when the checked state changes.                  |
| `label`              | `string`                     | -       | Optional label text to display next to the checkbox.                      |
| `error`              | `string`                     | -       | Error message to display (affects styling).                               |
| `disabled`           | `boolean`                    | `false` | Whether the checkbox is disabled.                                         |
| `labelStyle`         | `TextStyle`                  | -       | Additional styles to apply to the label text.                             |
| `accessibilityLabel` | `string`                     | -       | Accessibility label for screen readers. Defaults to `label` when omitted. |

## Accessibility

The Checkbox component is built with accessibility in mind:

- Uses TouchableOpacity for proper touch handling
- Supports disabled state with reduced opacity
- Label text is properly associated with the checkbox
- Provides visual feedback for checked/unchecked states
- Uses semantic color theming for different states
- Supports custom styling while maintaining accessibility

## Theming

The component uses the following theme colors:

- `primary`: Color for checked state border and background
- `primaryForeground`: Color for the check icon
- `border`: Color for unchecked state border
- `red`: Color for error state styling

These colors are automatically resolved using the `useColor` hook to support light and dark themes.
