1--- 2title: 'Implementing a checkbox for Expo and React Native apps' 3--- 4 5import SnackInline from '~/components/plugins/SnackInline'; 6 7One fairly common component that is not offered out of the box by Expo is the mighty checkbox. There are several packages available on npm; however, it is simple enough to implement yourself, and by doing so you have full customization and control over the look and feel of your checkbox. 8 9## Understanding the checkbox 10 11A checkbox is basically 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. 12 13> You can find more information about using icons in your Expo project in our [Icons guide](/guides/icons/). 14 15<SnackInline> 16 17```jsx 18import React, { useState } from 'react'; 19import { Pressable, StyleSheet, Text, View } from 'react-native'; 20import Ionicons from '@expo/vector-icons'; 21 22function MyCheckbox() { 23 const [checked, onChange] = useState(false); 24 25 function onCheckmarkPress() { 26 onChange(!checked); 27 } 28 29 return ( 30 <Pressable 31 style={[styles.checkboxBase, checked && styles.checkboxChecked]} 32 onPress={onCheckmarkPress}> 33 {checked && <Ionicons name="checkmark" size={24} color="white" />} 34 </Pressable> 35 ); 36} 37 38export default function App() { 39 return ( 40 <View style={styles.appContainer}> 41 <Text style={styles.appTitle}>Checkbox Example</Text> 42 43 <View style={styles.checkboxContainer}> 44 <MyCheckbox /> 45 <Text style={styles.checkboxLabel}>{`⬅️ Click!`}</Text> 46 </View> 47 </View> 48 ); 49} 50 51const styles = StyleSheet.create({ 52 checkboxBase: { 53 width: 24, 54 height: 24, 55 justifyContent: 'center', 56 alignItems: 'center', 57 borderRadius: 4, 58 borderWidth: 2, 59 borderColor: 'coral', 60 backgroundColor: 'transparent', 61 }, 62 63 checkboxChecked: { 64 backgroundColor: 'coral', 65 }, 66 67 appContainer: { 68 flex: 1, 69 alignItems: 'center', 70 }, 71 72 appTitle: { 73 marginVertical: 16, 74 fontWeight: 'bold', 75 fontSize: 24, 76 }, 77 78 checkboxContainer: { 79 flexDirection: 'row', 80 alignItems: 'center', 81 }, 82 83 checkboxLabel: { 84 marginLeft: 8, 85 fontWeight: 500, 86 fontSize: 18, 87 }, 88}); 89``` 90 91</SnackInline> 92 93> Note: https://icons.expo.fyi is a great resource for finding all of the icons available in the @expo/vector-icons package. 94 95## Controlling the checkbox 96 97This 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: 98 99<SnackInline> 100 101```jsx 102import React, { useState } from 'react'; 103import { Pressable, StyleSheet, Text, View } from 'react-native'; 104import Ionicons from '@expo/vector-icons/Ionicons'; 105 106function MyCheckbox({ 107 /* @info Define checked and onChange as props instead of state */ checked, 108 onChange /* @end */, 109}) { 110 function onCheckmarkPress() { 111 onChange(!checked); 112 } 113 114 return ( 115 <Pressable 116 style={[styles.checkboxBase, checked && styles.checkboxChecked]} 117 onPress={onCheckmarkPress}> 118 {checked && <Ionicons name="checkmark" size={24} color="white" />} 119 </Pressable> 120 ); 121} 122 123function App() { 124 /* @info Move the checked and onChange values outside of the checkbox component */ 125 const [checked, onChange] = useState(false); 126 /* @end */ 127 128 return ( 129 <View style={styles.appContainer}> 130 <Text style={styles.appTitle}>Checkbox Example</Text> 131 132 <View style={styles.checkboxContainer}> 133 <MyCheckbox 134 /* @info Pass the checked and onChange props to the checkbox */ checked={checked} 135 onChange={onChange} /* @end */ 136 /> 137 <Text style={styles.checkboxLabel}>{`⬅️ Click!`}</Text> 138 </View> 139 </View> 140 ); 141} 142 143export default App; 144 145const styles = StyleSheet.create({ 146 checkboxBase: { 147 width: 24, 148 height: 24, 149 justifyContent: 'center', 150 alignItems: 'center', 151 borderRadius: 4, 152 borderWidth: 2, 153 borderColor: 'coral', 154 backgroundColor: 'transparent', 155 }, 156 157 checkboxChecked: { 158 backgroundColor: 'coral', 159 }, 160 161 appContainer: { 162 flex: 1, 163 alignItems: 'center', 164 }, 165 166 appTitle: { 167 marginVertical: 16, 168 fontWeight: 'bold', 169 fontSize: 24, 170 }, 171 172 checkboxContainer: { 173 flexDirection: 'row', 174 alignItems: 'center', 175 }, 176 177 checkboxLabel: { 178 marginLeft: 8, 179 fontWeight: 500, 180 fontSize: 18, 181 }, 182}); 183``` 184 185</SnackInline> 186 187> Note: This pattern is referred to as a "controlled component" — you can read more about them here: https://reactjs.org/docs/forms.html#controlled-components. 188 189## Extending the interface 190 191It'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: 192 193<SnackInline> 194 195```jsx 196import React, { useState } from 'react'; 197import { Pressable, StyleSheet, Text, View } from 'react-native'; 198import Ionicons from '@expo/vector-icons/Ionicons'; 199 200function MyCheckbox({ 201 checked, 202 onChange, 203 /* @info Add style and icon props to make the checkbox reusable throughout your codebase */ buttonStyle = {}, 204 activeButtonStyle = {}, 205 inactiveButtonStyle = {}, 206 activeIconProps = {}, 207 inactiveIconProps = {}, 208 /* @end */ 209}) { 210 function onCheckmarkPress() { 211 onChange(!checked); 212 } 213 214 /* @info Set icon props based on the checked value */ 215 const iconProps = checked ? activeIconProps : inactiveIconProps; /* @end */ 216 217 return ( 218 <Pressable 219 style={[ 220 buttonStyle, 221 /* @info Pass the active / inactive style props to the button based on the current checked value */ checked 222 ? activeButtonStyle 223 : inactiveButtonStyle, 224 /* @end */ 225 ]} 226 onPress={onCheckmarkPress}> 227 {checked && ( 228 <Ionicons 229 name="checkmark" 230 size={24} 231 color="white" 232 /* @info Pass along any custom icon properties to the Icon component */ 233 {...iconProps} 234 /* @end */ 235 /> 236 )} 237 </Pressable> 238 ); 239} 240 241function App() { 242 const [checked, onChange] = useState(false); 243 244 return ( 245 <View style={styles.appContainer}> 246 <Text style={styles.appTitle}>Checkbox Example</Text> 247 248 <View style={styles.checkboxContainer}> 249 <MyCheckbox 250 checked={checked} 251 onChange={onChange} 252 /* @info Pass in base and active styles for the checkbox */ 253 buttonStyle={styles.checkboxBase} 254 activeButtonStyle={styles.checkboxChecked} 255 /* @end */ 256 /> 257 <Text style={styles.checkboxLabel}>{`⬅️ Click!`}</Text> 258 </View> 259 </View> 260 ); 261} 262 263export default App; 264 265const styles = StyleSheet.create({ 266 checkboxBase: { 267 width: 24, 268 height: 24, 269 justifyContent: 'center', 270 alignItems: 'center', 271 borderRadius: 4, 272 borderWidth: 2, 273 borderColor: 'coral', 274 backgroundColor: 'transparent', 275 }, 276 277 checkboxChecked: { 278 backgroundColor: 'coral', 279 }, 280 281 appContainer: { 282 flex: 1, 283 alignItems: 'center', 284 }, 285 286 appTitle: { 287 marginVertical: 16, 288 fontWeight: 'bold', 289 fontSize: 24, 290 }, 291 292 checkboxContainer: { 293 flexDirection: 'row', 294 alignItems: 'center', 295 }, 296 297 checkboxLabel: { 298 marginLeft: 8, 299 fontWeight: 500, 300 fontSize: 18, 301 }, 302}); 303``` 304 305</SnackInline> 306 307Now this checkbox ticks all of the boxes of what it should be. It toggles between `checked` states, can be controlled, and its styles are fully customizable. 308