---
title: Implement a checkbox
description: Learn how to implement a fully customizable checkbox in your Expo project.
---

import { SnackInline } from '~/ui/components/Snippet';

The [`expo-checkbox`](https://docs.expo.dev/versions/latest/sdk/checkbox/) package provides a quick implementation of a checkbox that you can directly use in your project. However, to have full customization, and control over the look and feel of the checkbox, this page goes in-depth on how to implement the component from scratch.

## Understanding the checkbox

A checkbox is a button that exists in one of two states — it is checked or it isn't. This makes it a perfect candidate for the `useState()` hook. Our first iteration will render a button that toggles between checked and unchecked states. When the checkbox is checked, we'll render a checkmark icon in the center of the button.

> You can find more information about using icons in your Expo project in our [Icons guide](/guides/icons).

<SnackInline dependencies={['@expo/vector-icons']}>

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

function MyCheckbox() {
  const [checked, setChecked] = useState(false);
  return (
    <Pressable
      style={[styles.checkboxBase, checked && styles.checkboxChecked]}
      onPress={() => setChecked(!checked)}>
      {checked && <Ionicons name="checkmark" size={24} color="white" />}
    </Pressable>
  );
}

export default function App() {
  return (
    <View style={styles.appContainer}>
      <Text style={styles.appTitle}>Checkbox Example</Text>
      <View style={styles.checkboxContainer}>
        <MyCheckbox />
        <Text style={styles.checkboxLabel}>{`⬅️ Click!`}</Text>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  checkboxBase: {
    width: 24,
    height: 24,
    justifyContent: 'center',
    alignItems: 'center',
    borderRadius: 4,
    borderWidth: 2,
    borderColor: 'coral',
    backgroundColor: 'transparent',
  },
  checkboxChecked: {
    backgroundColor: 'coral',
  },
  appContainer: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  appTitle: {
    marginVertical: 16,
    fontWeight: 'bold',
    fontSize: 24,
  },
  checkboxContainer: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  checkboxLabel: {
    marginLeft: 8,
    fontWeight: 500,
    fontSize: 18,
  },
});
```

</SnackInline>

> [icons.expo.fyi](https://icons.expo.fyi) is a great resource for finding all of the icons available in the `@expo/vector-icons` package.

## Controlling the checkbox

This checkbox isn't useful in this state because the `checked` value is accessible only from within the component — more often than not you'll want to control the checkbox from outside. This is achievable by defining `checked` and `onChange` as props that are passed into the checkbox:

<SnackInline dependencies={['@expo/vector-icons']}>

{/* prettier-ignore */}
```jsx
import { useState } from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import { Ionicons } from '@expo/vector-icons';

function MyCheckbox({ /* @info Define checked and onChange as props instead of state */onChange, checked/* @end */ }) {
  return (
    <Pressable
      style={[styles.checkboxBase, checked && styles.checkboxChecked]}
      onPress={onChange}>
      {checked && <Ionicons name="checkmark" size={24} color="white" />}
    </Pressable>
  );
}

export default function App() {
  /* @info Move the checked and setChecked values outside of the checkbox component */
  const [checked, setChecked] = useState(false);
  /* @end */
  return (
    <View style={styles.appContainer}>
      <Text style={styles.appTitle}>Checkbox Example</Text>
      <View style={styles.checkboxContainer}>
        /* @info Pass the checked and onChange props to the checkbox */
        <MyCheckbox onChange={() => setChecked(!checked)} checked={checked} />
        /* @end */
        <Text style={styles.checkboxLabel}>{`⬅️ Click!`}</Text>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  checkboxBase: {
    width: 24,
    height: 24,
    justifyContent: 'center',
    alignItems: 'center',
    borderRadius: 4,
    borderWidth: 2,
    borderColor: 'coral',
    backgroundColor: 'transparent',
  },
  checkboxChecked: {
    backgroundColor: 'coral',
  },
  appContainer: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  appTitle: {
    marginVertical: 16,
    fontWeight: 'bold',
    fontSize: 24,
  },
  checkboxContainer: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  checkboxLabel: {
    marginLeft: 8,
    fontWeight: 500,
    fontSize: 18,
  },
});
```

</SnackInline>

> This pattern is referred to as a [controlled component](https://reactjs.org/docs/forms.html#controlled-components).

## Extending the interface

It's common enough to need to render different styles when the checkmark is `checked` and when it is not. Let's add this to the checkbox's props and make it more reusable:

<SnackInline dependencies={['@expo/vector-icons']}>

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

function MyCheckbox({
  checked,
  onChange,
  /* @info Add style and icon props to make the checkbox reusable throughout your codebase */
  buttonStyle = {},
  activeButtonStyle = {},
  inactiveButtonStyle = {},
  activeIconProps = {},
  inactiveIconProps = {},
  /* @end */
}) {
  /* @info Set icon props based on the checked value */
  const iconProps = checked ? activeIconProps : inactiveIconProps;
  /* @end */
  return (
    <Pressable
      style={[
        buttonStyle,
        /* @info Pass the active / inactive style props to the button based on the current checked value */ checked
          ? activeButtonStyle
          : inactiveButtonStyle,
        /* @end */
      ]}
      onPress={() => onChange(!checked)}>
      {checked && (
        <Ionicons
          name="checkmark"
          size={24}
          color="white"
          /* @info Pass along any custom icon properties to the Icon component */
          {...iconProps}
          /* @end */
        />
      )}
    </Pressable>
  );
}

export default function App() {
  const [checked, setChecked] = useState(false);
  return (
    <View style={styles.appContainer}>
      <Text style={styles.appTitle}>Checkbox Example</Text>
      <View style={styles.checkboxContainer}>
        <MyCheckbox
          checked={checked}
          onChange={setChecked}
          /* @info Pass in base and active styles for the checkbox */
          buttonStyle={styles.checkboxBase}
          activeButtonStyle={styles.checkboxChecked}
          /* @end */
        />
        <Text style={styles.checkboxLabel}>{`⬅️ Click!`}</Text>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  checkboxBase: {
    width: 24,
    height: 24,
    justifyContent: 'center',
    alignItems: 'center',
    borderRadius: 4,
    borderWidth: 2,
    borderColor: 'coral',
    backgroundColor: 'transparent',
  },
  checkboxChecked: {
    backgroundColor: 'coral',
  },
  appContainer: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  appTitle: {
    marginVertical: 16,
    fontWeight: 'bold',
    fontSize: 24,
  },
  checkboxContainer: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  checkboxLabel: {
    marginLeft: 8,
    fontWeight: 500,
    fontSize: 18,
  },
});
```

</SnackInline>

This checkbox ticks all of the boxes of what it should be. It toggles between `checked` states, can be controlled, and the styles are fully customizable.
