Edit this page
Learn how to implement a fully customizable checkbox in your Expo project.
The expo-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.
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.
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
role="checkbox"
aria-checked={checked}
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,
},
});
icons.expo.fyi is a great resource for finding all the icons available in the
@expo/vector-icons
package.
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:
import { useState } from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
function MyCheckbox({ onChange, checked }) {
return (
<Pressable
role="checkbox"
aria-checked={checked}
style={[styles.checkboxBase, checked && styles.checkboxChecked]}
onPress={onChange}>
{checked && <Ionicons name="checkmark" size={24} color="white" />}
</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 onChange={() => setChecked(!checked)} checked={checked} />
<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,
},
});
This pattern is referred to as a controlled component.
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:
import { useState } from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
function MyCheckbox({
checked,
onChange,
buttonStyle = {},
activeButtonStyle = {},
inactiveButtonStyle = {},
activeIconProps = {},
inactiveIconProps = {},
}) {
const iconProps = checked ? activeIconProps : inactiveIconProps;
return (
<Pressable
role="checkbox"
aria-checked={checked}
style={[
buttonStyle,
checked
? activeButtonStyle
: inactiveButtonStyle,
]}
onPress={() => onChange(!checked)}>
{checked && (
<Ionicons
name="checkmark"
size={24}
color="white"
{...iconProps}
/>
)}
</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}
buttonStyle={styles.checkboxBase}
activeButtonStyle={styles.checkboxChecked}
/>
<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,
},
});
This checkbox ticks all the boxes of what it should be. It toggles between checked
states, can be controlled, and the styles are fully customizable.