Checkbox Interactions - The beauty of Layout Animations
Have you ever wondered how to create those satisfying checkbox animations that make your app feel more polished and engaging? In this tutorial, we'll explore how to build a beautiful checkbox component that not only looks great but also provides delightful feedback through smooth animations.
Source Code
Here you can find the final source code 👇
Animate with Reanimated is a youtube series where I try to build from scratch simple and instructive animations.
YouTube Tutorial
If you're more of a visual learner, check out the YouTube tutorial:
Project Setup
Let's set up our project with the necessary tools:
npx expo install react-native-reanimated @expo/vector-icons react-native-safe-area-context colorOur project follows a simple structure:
src/ ├── components/checkbox.tsx # Our checkbox component ├── constants/index.ts # Colors and data configuration ├── hooks/useCuisines.ts # State management logic └── App.tsx # Main layout and compositionPart 1: Building the Basic Checkbox
Let's start by creating a simple, functional checkbox component without animations. This will serve as our foundation.
First, let's define our constants in constants/index.ts:
export const ACTIVE_COLOR = '#EF8E52';export const INACTIVE_COLOR = '#B3B1B4';Next, let's create our state management hook in hooks/useCuisines.ts:
import { useCallback, useState } from 'react';
const Cuisines = new Array(20).fill('Italian').map((cuisine, i) => ({ id: i, name: cuisine, selected: false,}));
export const useCuisines = () => { const [cuisines, setCuisines] = useState(Cuisines);
const toggleCuisine = useCallback((id: number) => { setCuisines((prevCuisines) => { return prevCuisines.map((cuisine) => { if (cuisine.id === id) { return { ...cuisine, selected: !cuisine.selected }; } return cuisine; }); }); }, []);
return { cuisines, toggleCuisine };};Now, let's create our basic checkbox component in components/checkbox.tsx:
import { StyleSheet, View, Text, TouchableOpacity } from 'react-native';import { AntDesign } from '@expo/vector-icons';import { ACTIVE_COLOR, INACTIVE_COLOR } from '../constants';
type CheckboxProps = { label: string; checked: boolean; onPress: () => void;};
export const Checkbox: React.FC<CheckboxProps> = ({ label, checked, onPress,}) => { return ( <TouchableOpacity style={[ styles.container, { backgroundColor: checked ? 'rgba(239, 142, 82, 0.1)' : 'transparent', borderColor: checked ? ACTIVE_COLOR : INACTIVE_COLOR, }, ]} onPress={onPress} > <Text style={[ styles.label, { color: checked ? ACTIVE_COLOR : INACTIVE_COLOR }, ]} > {label} </Text> {checked && ( <View style={styles.iconContainer}> <AntDesign name="checkcircle" size={20} color={ACTIVE_COLOR} /> </View> )} </TouchableOpacity> );};
const styles = StyleSheet.create({ container: { paddingVertical: 12, paddingHorizontal: 20, borderWidth: 1, borderRadius: 32, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', }, label: { fontSize: 18, fontFamily: 'SF-Pro-Rounded-Bold', }, iconContainer: { marginLeft: 8, justifyContent: 'center', alignItems: 'center', height: 20, width: 20, },});Here's what we achieved so far 👇
It looks a bit too static, doesn't it? Let's add some animations to it.
Part 2: Adding the animations
Now that we have our basic checkbox working, let's enhance it with smooth animations using Reanimated. We'll transform our component to include:
- Smooth color transitions
- Layout animations for the check icon
- Container width adjustments
- Fluid text color changes
Here's our enhanced animated checkbox:
import { StyleSheet } from 'react-native';import Color from 'color';import { AntDesign } from '@expo/vector-icons';import Animated, { FadeIn, FadeOut, LinearTransition, useAnimatedStyle, withTiming,} from 'react-native-reanimated';import { ACTIVE_COLOR, INACTIVE_COLOR } from '../constants';
type CheckboxProps = { label: string; checked: boolean; onPress: () => void;};
const TimingConfig = { duration: 150,};
export const Checkbox: React.FC<CheckboxProps> = ({ label, checked, onPress,}) => { const fadedActiveColor = Color(ACTIVE_COLOR).alpha(0.1).toString();
const rContainerStyle = useAnimatedStyle(() => { return { backgroundColor: withTiming( checked ? fadedActiveColor : 'transparent', TimingConfig ), borderColor: withTiming( checked ? ACTIVE_COLOR : INACTIVE_COLOR, TimingConfig ), paddingLeft: 20, paddingRight: !checked ? 20 : 14, }; }, [checked]);
const rTextStyle = useAnimatedStyle(() => { return { color: withTiming(checked ? ACTIVE_COLOR : INACTIVE_COLOR, TimingConfig), }; }, [checked]);
return ( <Animated.View layout={LinearTransition.springify().mass(0.8)} style={[styles.container, rContainerStyle]} onTouchEnd={onPress} > <Animated.Text style={[styles.label, rTextStyle]}>{label}</Animated.Text> {checked && ( <Animated.View entering={FadeIn.duration(350)} exiting={FadeOut} style={styles.iconContainer} > <AntDesign name="checkcircle" size={20} color={ACTIVE_COLOR} /> </Animated.View> )} </Animated.View> );};
const styles = StyleSheet.create({ container: { paddingVertical: 12, borderWidth: 1, borderRadius: 32, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', }, label: { fontSize: 18, fontFamily: 'SF-Pro-Rounded-Bold', }, iconContainer: { marginLeft: 8, justifyContent: 'center', alignItems: 'center', height: 20, width: 20, },});Let's break down the key animation elements:
- Layout Animation:
This is where the magic really happens.
layout={LinearTransition.springify().mass(0.8)}This creates a spring-based layout animation with a heavier mass for smoother transitions.
- Icon Animation:
<Animated.View entering={FadeIn.duration(350)} exiting={FadeOut}>The check icon smoothly fades in over 350ms and fades out with default timing.
- Color and Padding Animations:
const rContainerStyle = useAnimatedStyle(() => ({ backgroundColor: withTiming(checked ? fadedActiveColor : 'transparent'), borderColor: withTiming(checked ? ACTIVE_COLOR : INACTIVE_COLOR), paddingRight: !checked ? 20 : 14,}));Final Thoughts: the magic of Layout Animations
What makes this animation special is how Layout Animations handle all the complex calculations for us:
- Smart Width Adjustment: The container width smoothly adjusts when the check icon appears/disappears
- Automatic Reflow: Other checkboxes in the same row smoothly reposition themselves
- Multiple Synchronized Animations:
- Background color fades in/out
- Border color transitions
- Text color changes
- Check icon fades in/out
- Container padding adjusts
The beauty? Everything is handled automatically. We're just trusting Reanimated to do the right thing. And that trust is well worth it.