Shipnative
ShipnativeShipnative
Sign in
GuideSeptember 2026 · 8 min read

React Native Checkbox: Your Three Options (2026)

React Native does not ship a checkbox. It used to — CheckBox was in core, then it was extracted, then the extraction was deprecated — which is why searching for it turns up three generations of answers, two of which no longer install. Here is the 2026 state of it: three real options, when each is right, and the two details that decide whether your checkbox is usable by an actual thumb.

Pick one in ten seconds

OptionPlatformsStyling controlNative controlUse it for
expo-checkboxiOS, Android, webColour onlyNo — drawn by the libraryAlmost everything
@react-native-community/checkboxiOS, AndroidLimited, platform-specific propsYes on AndroidWhen Android must look like Android
Pressable + iconEverywhereTotalNoCustom shapes, indeterminate, animation

One more option people reach for and shouldn’t: a Switch. It is in core and it is tempting because it exists, but a switch means “this setting takes effect now” and a checkbox means “this will be included when you submit.” Using a switch inside a form makes people wonder whether they still need to press Save.

Option 1 — expo-checkbox, done properly

npx expo install expo-checkbox
import { useState } from 'react';
import { Pressable, StyleSheet, Text } from 'react-native';
import Checkbox from 'expo-checkbox';

export default function TermsRow() {
  const [accepted, setAccepted] = useState(false);

  return (
    // the WHOLE row is the target, not the 20pt box
    <Pressable
      style={styles.row}
      onPress={() => setAccepted((v) => !v)}
      accessibilityRole="checkbox"
      accessibilityState={{ checked: accepted }}
      accessibilityLabel="I accept the terms of service"
      hitSlop={8}
    >
      <Checkbox
        value={accepted}
        onValueChange={setAccepted}
        color={accepted ? '#fb923c' : undefined}
        pointerEvents="none"   // let the Pressable own the tap
      />
      <Text style={styles.label}>I accept the terms of service</Text>
    </Pressable>
  );
}

const styles = StyleSheet.create({
  row: {
    flexDirection: 'row',
    alignItems: 'center',
    gap: 12,
    minHeight: 44,       // Apple's minimum touch target
    paddingVertical: 8,
  },
  label: { color: 'rgba(255,255,255,0.85)', fontSize: 15, flexShrink: 1 },
});

The pointerEvents="none" on the checkbox is the part people leave out. Without it you have two overlapping touch targets, and a tap that lands on the box fires both onValueChange and the wrapper’s onPress — the value flips twice and the checkbox appears not to work at all. Let one component own the gesture.

Option 2 — roll your own

Once your design has a rounded square, a brand tick, or a third indeterminate state for a “select all” header, the library stops helping. A custom checkbox is about twenty lines and gives you every property you need.

import { Pressable, Text, View } from 'react-native';
import { Ionicons } from '@expo/vector-icons';

type Props = {
  label: string;
  checked: boolean;
  indeterminate?: boolean;
  onChange: (next: boolean) => void;
};

export function Checkbox({ label, checked, indeterminate, onChange }: Props) {
  const on = checked || indeterminate;
  return (
    <Pressable
      onPress={() => onChange(!checked)}
      accessibilityRole="checkbox"
      accessibilityState={{ checked: indeterminate ? 'mixed' : checked }}
      accessibilityLabel={label}
      style={{ flexDirection: 'row', alignItems: 'center', gap: 12, minHeight: 44 }}
      hitSlop={8}
    >
      <View
        style={{
          width: 22, height: 22, borderRadius: 6,
          borderWidth: on ? 0 : 1.5,
          borderColor: 'rgba(255,255,255,0.3)',
          backgroundColor: on ? '#fb923c' : 'transparent',
          alignItems: 'center', justifyContent: 'center',
        }}
      >
        {on && (
          <Ionicons
            name={indeterminate ? 'remove' : 'checkmark'}
            size={15}
            color="#1c1c1c"
          />
        )}
      </View>
      <Text style={{ color: 'rgba(255,255,255,0.85)', fontSize: 15, flexShrink: 1 }}>
        {label}
      </Text>
    </Pressable>
  );
}

accessibilityState={{ checked: 'mixed' }} is the correct value for indeterminate, and it is the one thing you cannot express with any of the libraries. If you have a select-all header, that alone is the reason to write your own.

Wiring it into a form

register() is a DOM API and there is no DOM here, so it does nothing for a checkbox. Every non-text React Native input goes through Controller:

import { Controller, useForm } from 'react-hook-form';

const { control, handleSubmit, formState: { errors } } = useForm({
  defaultValues: { acceptedTerms: false },
});

<Controller
  control={control}
  name="acceptedTerms"
  rules={{ required: 'You must accept the terms to continue' }}
  render={({ field: { value, onChange } }) => (
    <Checkbox
      label="I accept the terms of service"
      checked={value}
      onChange={onChange}
    />
  )}
/>
{errors.acceptedTerms && (
  <Text style={{ color: '#f87171', fontSize: 13 }}>
    {errors.acceptedTerms.message}
  </Text>
)}

Note required works on a boolean the way you’d hope — false fails validation. The full pattern is in the form validation guide.

The traps, in the order you’ll hit them

  1. CheckBox is not exported from react-native. You’re following a pre-2021 answer. It was removed.
  2. Tapping does nothing. The value is controlled and nothing writes it back, or the box and its wrapper both handle the tap and cancel each other out.
  3. It’s hard to hit. A 20pt box is under half the 44pt minimum. Make the row the target and add hitSlop.
  4. It disappears in dark mode. expo-checkbox defaults to a light-scheme border. Set color explicitly for both states.
  5. A list of checkboxes re-renders on every tap.You’re keeping the checked set in one parent state object and passing a new callback to each row. Memoize the row and pass a stable handler — see the FlatList guide.
  6. The label wraps off-screen. A long label in a row needs flexShrink: 1, otherwise it pushes past the edge instead of wrapping.

The shortcut: generate the form, keep the taste

Touch targets, accessibility roles, the Controller wiring, the two-tap bug — all of it is mechanical, and all of it is identical in every app that has a signup screen. What isn’t mechanical is what you’re asking people to agree to and how much friction that’s worth.

Describe the screen in ShipNative — “a signup form with email, password, and a required terms checkbox” — and it generates the form, the validation, and accessible controls, then runs it on your phone so you can check the tap targets with a real thumb.

Build it free

Describe your app in one sentence and have it running on your phone in minutes at shipnative.dev. No credit card, full React Native source export whenever you want it.

Frequently Asked Questions

Does React Native have a built-in Checkbox?

No. There was a CheckBox component in React Native core years ago, but it was extracted and then deprecated, and it is Android-only in every version you might still find referenced. In 2026 your options are expo-checkbox, @react-native-community/checkbox, or a Pressable you style yourself.

What is the best checkbox library for React Native?

expo-checkbox for most apps — it is tiny, works on iOS, Android, and web, and needs no native linking in an Expo project. Use @react-native-community/checkbox only if you specifically want the platform-native control on Android. Build your own when the design has a custom shape, an indeterminate state, or an animation, which is more often than people expect.

Why does my checkbox not respond to taps?

Two usual causes. The checkbox is a controlled component and you never update the state, so it re-renders with the same value and looks dead. Or the touch target is only as large as the 20-by-20 box, which is well under the 44-point minimum, so half of real taps miss it. Wrap the box and its label in one Pressable.

How do I make a checkbox accessible in React Native?

Set accessibilityRole="checkbox" and accessibilityState={{ checked }} on the pressable wrapper, and give it an accessibilityLabel matching the visible text. Screen readers then announce it as a checkbox with its state rather than as an unlabelled button, and the state change is announced on tap.

How do I use a checkbox with react-hook-form?

Checkboxes are not standard DOM inputs, so register() does not work. Wrap it in a Controller and map the render props: pass field.value to the checked prop and field.onChange to the toggle handler. This is the same Controller pattern every non-text React Native input needs.

→

React Native Form Validation

The Controller pattern this checkbox needs to join a form.

Read guide →
→

React Native Dropdown & Select

The other input RN core leaves you to solve yourself.

Read guide →

Ship a real React Native app today

Describe, preview, and export Expo code — free to start.

Build with ShipNative →
ShipNative logoShipnative

Build mobile apps with AI. Describe, preview, and ship to iOS & Android in minutes.

Features

Text to App AIApp Generator from ScreenshotPRD to Mobile App

Tools

All free toolsApp Cost CalculatorApp Name GeneratorApp Store Keyword ToolReact Native Components

Blog

All blog postsHow to Build an App Without CodingBest AI Tools for Real Mobile AppsExpo EAS App Store ChecklistLovable, Cursor & v0 for MobileBest AI App Builders in 2026React Native AI App Builder Guide

Legal

FAQTerms of ServicePrivacy Policy

© 2026 ShipNative. All rights reserved.