🔘 Switch, ActivityIndicator, StatusBar
Essential utility components for toggles, loading states, and system UI control
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Create boolean toggles with the Switch component
- Customize Switch appearance for both platforms
- Display loading indicators with ActivityIndicator
- Control the device status bar appearance
- Handle status bar in different screen contexts
- Combine these components in real-world scenarios
⏱️ Estimated Time: 20-25 minutes
📑 In This Lesson
Switch — Boolean Toggles
The Switch component renders a platform-native toggle switch. It's perfect for on/off settings, feature flags, or any boolean choice.
📖 What is Switch?
Switch is a controlled component that displays a toggle. Users can tap it to switch between on (true) and off (false) states. It renders using native platform controls, so it looks and feels familiar to users.
Switch renders differently on iOS and Android, using each platform's native toggle style
Basic Switch Usage
import { useState } from 'react';
import { Switch, View, Text, StyleSheet } from 'react-native';
function SettingsToggle() {
const [isEnabled, setIsEnabled] = useState(false);
return (
<View style={styles.row}>
<Text style={styles.label}>Enable Notifications</Text>
<Switch
value={isEnabled}
onValueChange={setIsEnabled}
/>
</View>
);
}
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: 12,
paddingHorizontal: 16,
},
label: {
fontSize: 16,
color: '#333',
},
});
Switch Props
| Prop | Type | Description |
|---|---|---|
value |
boolean | Current on/off state (required for controlled) |
onValueChange |
(value: boolean) => void | Called when user toggles |
disabled |
boolean | Prevents user interaction |
trackColor |
{ false?: string, true?: string } | Background colors for off/on |
thumbColor |
string | Color of the circular thumb |
ios_backgroundColor |
string | iOS-only background color when off |
Customizing Switch Colors
import { useState } from 'react';
import { Switch, View, Text, StyleSheet, Platform } from 'react-native';
function CustomSwitch() {
const [isEnabled, setIsEnabled] = useState(false);
return (
<View style={styles.row}>
<Text style={styles.label}>Dark Mode</Text>
<Switch
value={isEnabled}
onValueChange={setIsEnabled}
trackColor={{
false: '#767577', // Track color when OFF
true: '#81b0ff' // Track color when ON
}}
thumbColor={isEnabled ? '#2196F3' : '#f4f3f4'} // Thumb color
ios_backgroundColor="#3e3e3e" // iOS background when OFF
/>
</View>
);
}
Settings List Pattern
import { useState } from 'react';
import { Switch, View, Text, StyleSheet } from 'react-native';
interface Setting {
id: string;
label: string;
description?: string;
}
const SETTINGS: Setting[] = [
{ id: 'notifications', label: 'Push Notifications', description: 'Receive alerts for new messages' },
{ id: 'sounds', label: 'Sound Effects', description: 'Play sounds for actions' },
{ id: 'darkMode', label: 'Dark Mode', description: 'Use dark color scheme' },
{ id: 'analytics', label: 'Usage Analytics', description: 'Help improve the app' },
];
function SettingsList() {
const [settings, setSettings] = useState<Record<string, boolean>>({
notifications: true,
sounds: true,
darkMode: false,
analytics: false,
});
const toggleSetting = (id: string) => {
setSettings(prev => ({
...prev,
[id]: !prev[id],
}));
};
return (
<View style={styles.container}>
{SETTINGS.map((setting, index) => (
<View
key={setting.id}
style={[
styles.row,
index < SETTINGS.length - 1 && styles.rowBorder,
]}
>
<View style={styles.textContainer}>
<Text style={styles.label}>{setting.label}</Text>
{setting.description && (
<Text style={styles.description}>{setting.description}</Text>
)}
</View>
<Switch
value={settings[setting.id]}
onValueChange={() => toggleSetting(setting.id)}
trackColor={{ false: '#ddd', true: '#81b0ff' }}
thumbColor={settings[setting.id] ? '#2196F3' : '#f4f3f4'}
/>
</View>
))}
</View>
);
}
const styles = StyleSheet.create({
container: {
backgroundColor: 'white',
borderRadius: 12,
marginHorizontal: 16,
overflow: 'hidden',
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 16,
},
rowBorder: {
borderBottomWidth: 1,
borderBottomColor: '#f0f0f0',
},
textContainer: {
flex: 1,
marginRight: 12,
},
label: {
fontSize: 16,
color: '#333',
fontWeight: '500',
},
description: {
fontSize: 13,
color: '#666',
marginTop: 2,
},
});
✅ Accessibility Tip
Switch automatically handles accessibility, but you can improve it with:
<Switch
accessibilityLabel="Enable notifications"
accessibilityHint="Double tap to toggle"
/>
ActivityIndicator — Loading States
Users need to know when something is loading. The ActivityIndicator component shows a spinning indicator that uses native platform styling.
Basic Usage
import { ActivityIndicator, View, StyleSheet } from 'react-native';
function LoadingScreen() {
return (
<View style={styles.container}>
<ActivityIndicator />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
ActivityIndicator Props
| Prop | Type | Description |
|---|---|---|
size |
'small' | 'large' | number | Size of indicator (number is Android only) |
color |
string | Color of the spinner |
animating |
boolean | Whether to show (default: true) |
hidesWhenStopped |
boolean | iOS: hide when not animating |
Size and Color Variations
import { ActivityIndicator, View, StyleSheet } from 'react-native';
function LoadingVariations() {
return (
<View style={styles.container}>
{/* Default (small, gray) */}
<ActivityIndicator />
{/* Large size */}
<ActivityIndicator size="large" />
{/* Custom color */}
<ActivityIndicator size="large" color="#2196F3" />
{/* Brand color */}
<ActivityIndicator size="large" color="#667eea" />
{/* On dark background */}
<View style={styles.dark}>
<ActivityIndicator size="large" color="white" />
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'space-around',
alignItems: 'center',
padding: 20,
},
dark: {
backgroundColor: '#333',
padding: 20,
borderRadius: 10,
},
});
ActivityIndicator sizes: small (20px), large (36px), or custom on Android
Conditional Loading Pattern
import { useState, useEffect } from 'react';
import { ActivityIndicator, View, Text, FlatList, StyleSheet } from 'react-native';
interface Item {
id: string;
title: string;
}
function DataList() {
const [data, setData] = useState<Item[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
try {
setLoading(true);
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 2000));
setData([
{ id: '1', title: 'Item 1' },
{ id: '2', title: 'Item 2' },
{ id: '3', title: 'Item 3' },
]);
} catch (e) {
setError('Failed to load data');
} finally {
setLoading(false);
}
};
if (loading) {
return (
<View style={styles.centered}>
<ActivityIndicator size="large" color="#2196F3" />
<Text style={styles.loadingText}>Loading...</Text>
</View>
);
}
if (error) {
return (
<View style={styles.centered}>
<Text style={styles.errorText}>{error}</Text>
</View>
);
}
return (
<FlatList
data={data}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<View style={styles.item}>
<Text>{item.title}</Text>
</View>
)}
/>
);
}
const styles = StyleSheet.create({
centered: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
loadingText: {
marginTop: 12,
fontSize: 16,
color: '#666',
},
errorText: {
fontSize: 16,
color: '#f44336',
},
item: {
padding: 16,
borderBottomWidth: 1,
borderBottomColor: '#eee',
},
});
Inline Loading Button
import { useState } from 'react';
import { ActivityIndicator, Pressable, Text, StyleSheet } from 'react-native';
function LoadingButton() {
const [loading, setLoading] = useState(false);
const handlePress = async () => {
setLoading(true);
await new Promise(resolve => setTimeout(resolve, 2000));
setLoading(false);
};
return (
<Pressable
style={[styles.button, loading && styles.buttonDisabled]}
onPress={handlePress}
disabled={loading}
>
{loading ? (
<ActivityIndicator color="white" />
) : (
<Text style={styles.buttonText}>Submit</Text>
)}
</Pressable>
);
}
const styles = StyleSheet.create({
button: {
backgroundColor: '#2196F3',
paddingVertical: 14,
paddingHorizontal: 24,
borderRadius: 8,
alignItems: 'center',
justifyContent: 'center',
minWidth: 120,
minHeight: 48,
},
buttonDisabled: {
opacity: 0.7,
},
buttonText: {
color: 'white',
fontSize: 16,
fontWeight: '600',
},
});
Overlay Loading
import { ActivityIndicator, View, StyleSheet, Modal } from 'react-native';
interface LoadingOverlayProps {
visible: boolean;
}
function LoadingOverlay({ visible }: LoadingOverlayProps) {
return (
<Modal transparent visible={visible}>
<View style={styles.overlay}>
<View style={styles.loaderContainer}>
<ActivityIndicator size="large" color="#2196F3" />
</View>
</View>
</Modal>
);
}
const styles = StyleSheet.create({
overlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
justifyContent: 'center',
alignItems: 'center',
},
loaderContainer: {
backgroundColor: 'white',
padding: 30,
borderRadius: 12,
},
});
StatusBar — System UI Control
The status bar sits at the top of the device screen showing time, battery, signal, etc. The StatusBar component lets you control its appearance.
Match status bar style to your background: light-content on dark, dark-content on light
Basic Usage
import { StatusBar, View, StyleSheet } from 'react-native';
function App() {
return (
<View style={styles.container}>
<StatusBar barStyle="dark-content" />
{/* Your app content */}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
});
StatusBar Props
| Prop | Type | Platform | Description |
|---|---|---|---|
barStyle |
'default' | 'light-content' | 'dark-content' | Both | Color of text/icons |
backgroundColor |
string | Android | Background color |
translucent |
boolean | Android | Draw app under status bar |
hidden |
boolean | Both | Hide the status bar |
animated |
boolean | Both | Animate changes |
networkActivityIndicatorVisible |
boolean | iOS | Show network spinner |
barStyle Options
// Light text/icons (for dark backgrounds)
<StatusBar barStyle="light-content" />
// Dark text/icons (for light backgrounds)
<StatusBar barStyle="dark-content" />
// Platform default
<StatusBar barStyle="default" />
// iOS: dark-content
// Android: light-content
Android-Specific Styling
import { StatusBar, View, StyleSheet, Platform } from 'react-native';
function AndroidStatusBarExample() {
return (
<View style={styles.container}>
<StatusBar
barStyle="light-content"
backgroundColor="#1976D2" // Android only
translucent={false} // Android only
/>
{/* Content */}
</View>
);
}
Hiding the Status Bar
import { useState } from 'react';
import { StatusBar, View, Pressable, Text, StyleSheet } from 'react-native';
function FullScreenMode() {
const [hidden, setHidden] = useState(false);
return (
<View style={styles.container}>
<StatusBar hidden={hidden} animated />
<Pressable
style={styles.button}
onPress={() => setHidden(!hidden)}
>
<Text style={styles.buttonText}>
{hidden ? 'Show' : 'Hide'} Status Bar
</Text>
</Pressable>
</View>
);
}
Screen-Specific Status Bars
Different screens often need different status bar styles. You can include multiple StatusBar components:
// In a dark-themed screen
function ProfileScreen() {
return (
<View style={{ flex: 1, backgroundColor: '#1a1a2e' }}>
<StatusBar barStyle="light-content" />
{/* Dark screen content */}
</View>
);
}
// In a light-themed screen
function SettingsScreen() {
return (
<View style={{ flex: 1, backgroundColor: '#fff' }}>
<StatusBar barStyle="dark-content" />
{/* Light screen content */}
</View>
);
}
⚠️ StatusBar in Navigation
When using React Navigation or Expo Router, consider using useFocusEffect to update the status bar when screens focus:
import { useFocusEffect } from '@react-navigation/native';
import { StatusBar } from 'react-native';
import { useCallback } from 'react';
function DarkScreen() {
useFocusEffect(
useCallback(() => {
StatusBar.setBarStyle('light-content');
}, [])
);
// ...
}
Imperative API
StatusBar also has static methods for imperative updates:
import { StatusBar } from 'react-native';
// Change bar style
StatusBar.setBarStyle('light-content', true); // true = animated
// Hide/show
StatusBar.setHidden(true, 'fade'); // 'fade' or 'slide'
// Android background
StatusBar.setBackgroundColor('#2196F3', true);
// Android translucent
StatusBar.setTranslucent(true);
Combining Components
Let's put these components together in realistic scenarios.
Settings Screen
import { useState } from 'react';
import {
View,
Text,
Switch,
StatusBar,
ScrollView,
StyleSheet,
Platform,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
function SettingsScreen() {
const [notifications, setNotifications] = useState(true);
const [sounds, setSounds] = useState(true);
const [darkMode, setDarkMode] = useState(false);
const [autoPlay, setAutoPlay] = useState(false);
const backgroundColor = darkMode ? '#1a1a2e' : '#f5f5f5';
const cardBackground = darkMode ? '#2d2d44' : '#fff';
const textColor = darkMode ? '#fff' : '#333';
const subtitleColor = darkMode ? '#aaa' : '#666';
return (
<SafeAreaView style={[styles.container, { backgroundColor }]}>
<StatusBar
barStyle={darkMode ? 'light-content' : 'dark-content'}
animated
/>
<ScrollView>
<Text style={[styles.header, { color: textColor }]}>Settings</Text>
<View style={[styles.section, { backgroundColor: cardBackground }]}>
<Text style={[styles.sectionTitle, { color: subtitleColor }]}>
NOTIFICATIONS
</Text>
<View style={styles.row}>
<View style={styles.rowText}>
<Text style={[styles.rowLabel, { color: textColor }]}>
Push Notifications
</Text>
<Text style={[styles.rowSubtitle, { color: subtitleColor }]}>
Receive alerts for new messages
</Text>
</View>
<Switch
value={notifications}
onValueChange={setNotifications}
trackColor={{ false: '#767577', true: '#81b0ff' }}
thumbColor={notifications ? '#2196F3' : '#f4f3f4'}
/>
</View>
<View style={[styles.row, styles.rowBorder]}>
<View style={styles.rowText}>
<Text style={[styles.rowLabel, { color: textColor }]}>
Sound Effects
</Text>
<Text style={[styles.rowSubtitle, { color: subtitleColor }]}>
Play sounds for actions
</Text>
</View>
<Switch
value={sounds}
onValueChange={setSounds}
trackColor={{ false: '#767577', true: '#81b0ff' }}
thumbColor={sounds ? '#2196F3' : '#f4f3f4'}
/>
</View>
</View>
<View style={[styles.section, { backgroundColor: cardBackground }]}>
<Text style={[styles.sectionTitle, { color: subtitleColor }]}>
APPEARANCE
</Text>
<View style={styles.row}>
<View style={styles.rowText}>
<Text style={[styles.rowLabel, { color: textColor }]}>
Dark Mode
</Text>
<Text style={[styles.rowSubtitle, { color: subtitleColor }]}>
Use dark color scheme
</Text>
</View>
<Switch
value={darkMode}
onValueChange={setDarkMode}
trackColor={{ false: '#767577', true: '#81b0ff' }}
thumbColor={darkMode ? '#2196F3' : '#f4f3f4'}
/>
</View>
</View>
<View style={[styles.section, { backgroundColor: cardBackground }]}>
<Text style={[styles.sectionTitle, { color: subtitleColor }]}>
MEDIA
</Text>
<View style={styles.row}>
<View style={styles.rowText}>
<Text style={[styles.rowLabel, { color: textColor }]}>
Auto-Play Videos
</Text>
<Text style={[styles.rowSubtitle, { color: subtitleColor }]}>
Play videos automatically in feed
</Text>
</View>
<Switch
value={autoPlay}
onValueChange={setAutoPlay}
trackColor={{ false: '#767577', true: '#81b0ff' }}
thumbColor={autoPlay ? '#2196F3' : '#f4f3f4'}
/>
</View>
</View>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
header: {
fontSize: 34,
fontWeight: 'bold',
padding: 20,
paddingBottom: 10,
},
section: {
marginHorizontal: 16,
marginBottom: 20,
borderRadius: 12,
overflow: 'hidden',
},
sectionTitle: {
fontSize: 13,
fontWeight: '600',
paddingHorizontal: 16,
paddingTop: 16,
paddingBottom: 8,
},
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: 16,
},
rowBorder: {
borderTopWidth: 1,
borderTopColor: 'rgba(0,0,0,0.05)',
},
rowText: {
flex: 1,
marginRight: 12,
},
rowLabel: {
fontSize: 16,
fontWeight: '500',
},
rowSubtitle: {
fontSize: 13,
marginTop: 2,
},
});
Loading State with Status
import { useState, useEffect } from 'react';
import {
View,
Text,
ActivityIndicator,
StatusBar,
StyleSheet,
} from 'react-native';
type Status = 'idle' | 'loading' | 'success' | 'error';
function DataFetcher() {
const [status, setStatus] = useState<Status>('idle');
const [data, setData] = useState<string | null>(null);
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
setStatus('loading');
try {
await new Promise(resolve => setTimeout(resolve, 2000));
setData('Data loaded successfully!');
setStatus('success');
} catch {
setStatus('error');
}
};
return (
<View style={styles.container}>
<StatusBar barStyle="dark-content" />
{status === 'loading' && (
<View style={styles.centered}>
<ActivityIndicator size="large" color="#2196F3" />
<Text style={styles.loadingText}>Fetching data...</Text>
</View>
)}
{status === 'success' && (
<View style={styles.centered}>
<Text style={styles.successIcon}>✅</Text>
<Text style={styles.successText}>{data}</Text>
</View>
)}
{status === 'error' && (
<View style={styles.centered}>
<Text style={styles.errorIcon}>❌</Text>
<Text style={styles.errorText}>Something went wrong</Text>
</View>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
centered: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
loadingText: {
marginTop: 16,
fontSize: 16,
color: '#666',
},
successIcon: {
fontSize: 48,
marginBottom: 16,
},
successText: {
fontSize: 18,
color: '#4CAF50',
textAlign: 'center',
},
errorIcon: {
fontSize: 48,
marginBottom: 16,
},
errorText: {
fontSize: 18,
color: '#f44336',
textAlign: 'center',
},
});
Hands-On Exercises
Exercise 1: Toggle Settings Card
Goal: Create a settings card with multiple toggles.
Requirements:
- Card with rounded corners and shadow
- 3 settings: WiFi, Bluetooth, Airplane Mode
- Custom colors for the switches (blue when on)
- Airplane Mode disables WiFi and Bluetooth when enabled
💡 Hint
Use an effect or the onValueChange handler for Airplane Mode to disable the other settings when it's turned on.
✅ Solution
import { useState, useEffect } from 'react';
import { View, Text, Switch, StyleSheet } from 'react-native';
export default function SettingsCard() {
const [wifi, setWifi] = useState(true);
const [bluetooth, setBluetooth] = useState(false);
const [airplane, setAirplane] = useState(false);
// When airplane mode is enabled, disable others
useEffect(() => {
if (airplane) {
setWifi(false);
setBluetooth(false);
}
}, [airplane]);
return (
<View style={styles.card}>
<View style={styles.row}>
<Text style={styles.icon}>📶</Text>
<Text style={[styles.label, airplane && styles.disabled]}>WiFi</Text>
<Switch
value={wifi}
onValueChange={setWifi}
disabled={airplane}
trackColor={{ false: '#ddd', true: '#81b0ff' }}
thumbColor={wifi ? '#2196F3' : '#f4f3f4'}
/>
</View>
<View style={[styles.row, styles.rowBorder]}>
<Text style={styles.icon}>📡</Text>
<Text style={[styles.label, airplane && styles.disabled]}>Bluetooth</Text>
<Switch
value={bluetooth}
onValueChange={setBluetooth}
disabled={airplane}
trackColor={{ false: '#ddd', true: '#81b0ff' }}
thumbColor={bluetooth ? '#2196F3' : '#f4f3f4'}
/>
</View>
<View style={[styles.row, styles.rowBorder]}>
<Text style={styles.icon}>✈️</Text>
<Text style={styles.label}>Airplane Mode</Text>
<Switch
value={airplane}
onValueChange={setAirplane}
trackColor={{ false: '#ddd', true: '#ffcc80' }}
thumbColor={airplane ? '#FF9800' : '#f4f3f4'}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
card: {
backgroundColor: 'white',
borderRadius: 16,
margin: 16,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 4,
},
row: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
},
rowBorder: {
borderTopWidth: 1,
borderTopColor: '#f0f0f0',
},
icon: {
fontSize: 24,
marginRight: 12,
},
label: {
flex: 1,
fontSize: 16,
color: '#333',
},
disabled: {
color: '#999',
},
});
Exercise 2: Loading States Demo
Goal: Create a component showing different loading indicator styles.
Requirements:
- Show small and large ActivityIndicators
- One on light background, one on dark
- A button that toggles loading state
- Display "Loading..." text when active
✅ Solution
import { useState } from 'react';
import { View, Text, ActivityIndicator, Pressable, StyleSheet } from 'react-native';
export default function LoadingDemo() {
const [loading, setLoading] = useState(true);
return (
<View style={styles.container}>
<Text style={styles.title}>ActivityIndicator Demo</Text>
<View style={styles.row}>
<View style={styles.lightBox}>
<ActivityIndicator
animating={loading}
size="small"
color="#2196F3"
/>
<Text style={styles.lightText}>Small</Text>
</View>
<View style={styles.lightBox}>
<ActivityIndicator
animating={loading}
size="large"
color="#2196F3"
/>
<Text style={styles.lightText}>Large</Text>
</View>
</View>
<View style={styles.row}>
<View style={styles.darkBox}>
<ActivityIndicator
animating={loading}
size="small"
color="white"
/>
<Text style={styles.darkText}>On Dark</Text>
</View>
<View style={styles.darkBox}>
<ActivityIndicator
animating={loading}
size="large"
color="#81b0ff"
/>
<Text style={styles.darkText}>Custom Color</Text>
</View>
</View>
{loading && (
<Text style={styles.loadingText}>Loading...</Text>
)}
<Pressable
style={styles.button}
onPress={() => setLoading(!loading)}
>
<Text style={styles.buttonText}>
{loading ? 'Stop' : 'Start'} Loading
</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 24,
textAlign: 'center',
},
row: {
flexDirection: 'row',
justifyContent: 'space-around',
marginBottom: 20,
},
lightBox: {
backgroundColor: 'white',
padding: 24,
borderRadius: 12,
alignItems: 'center',
width: 140,
shadowColor: '#000',
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 2,
},
darkBox: {
backgroundColor: '#333',
padding: 24,
borderRadius: 12,
alignItems: 'center',
width: 140,
},
lightText: {
marginTop: 12,
color: '#666',
},
darkText: {
marginTop: 12,
color: '#aaa',
},
loadingText: {
textAlign: 'center',
fontSize: 16,
color: '#2196F3',
marginBottom: 20,
},
button: {
backgroundColor: '#2196F3',
padding: 16,
borderRadius: 8,
alignItems: 'center',
},
buttonText: {
color: 'white',
fontSize: 16,
fontWeight: '600',
},
});
Exercise 3: Theme Toggle with StatusBar
Goal: Create a screen that toggles between light and dark themes.
Requirements:
- Switch to toggle dark mode
- StatusBar updates to match theme (light-content/dark-content)
- Background and text colors change
- Smooth animated transition
✅ Solution
import { useState } from 'react';
import { View, Text, Switch, StatusBar, StyleSheet } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
export default function ThemeToggle() {
const [darkMode, setDarkMode] = useState(false);
const theme = {
background: darkMode ? '#1a1a2e' : '#fff',
card: darkMode ? '#2d2d44' : '#f5f5f5',
text: darkMode ? '#fff' : '#333',
subtitle: darkMode ? '#aaa' : '#666',
};
return (
<SafeAreaView style={[styles.container, { backgroundColor: theme.background }]}>
<StatusBar
barStyle={darkMode ? 'light-content' : 'dark-content'}
animated
/>
<Text style={[styles.title, { color: theme.text }]}>
Theme Settings
</Text>
<View style={[styles.card, { backgroundColor: theme.card }]}>
<View style={styles.row}>
<View>
<Text style={[styles.label, { color: theme.text }]}>
Dark Mode
</Text>
<Text style={[styles.subtitle, { color: theme.subtitle }]}>
{darkMode ? 'Currently using dark theme' : 'Currently using light theme'}
</Text>
</View>
<Switch
value={darkMode}
onValueChange={setDarkMode}
trackColor={{ false: '#ddd', true: '#81b0ff' }}
thumbColor={darkMode ? '#2196F3' : '#f4f3f4'}
/>
</View>
</View>
<View style={[styles.preview, { backgroundColor: theme.card }]}>
<Text style={[styles.previewTitle, { color: theme.text }]}>
Preview
</Text>
<Text style={[styles.previewText, { color: theme.subtitle }]}>
This is how content looks in {darkMode ? 'dark' : 'light'} mode.
The status bar icons are now {darkMode ? 'light' : 'dark'}.
</Text>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
title: {
fontSize: 28,
fontWeight: 'bold',
padding: 20,
},
card: {
marginHorizontal: 16,
borderRadius: 12,
padding: 16,
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
label: {
fontSize: 18,
fontWeight: '600',
},
subtitle: {
fontSize: 14,
marginTop: 4,
},
preview: {
margin: 16,
padding: 20,
borderRadius: 12,
},
previewTitle: {
fontSize: 18,
fontWeight: '600',
marginBottom: 8,
},
previewText: {
fontSize: 14,
lineHeight: 22,
},
});
Summary
🎉 Key Takeaways
- Switch is for boolean toggles with
valueandonValueChange - Customize Switch with
trackColor,thumbColor, andios_backgroundColor - ActivityIndicator shows native loading spinners
- Use
size="large"for prominent loading states,colorto match your theme - StatusBar controls the system status bar appearance
- Use
barStyle="light-content"on dark backgrounds,"dark-content"on light - Android has additional props:
backgroundColorandtranslucent - Update StatusBar when navigating between screens with different themes
Quick Reference
// Switch
<Switch
value={isEnabled}
onValueChange={setIsEnabled}
trackColor={{ false: '#767577', true: '#81b0ff' }}
thumbColor={isEnabled ? '#2196F3' : '#f4f3f4'}
disabled={false}
/>
// ActivityIndicator
<ActivityIndicator
size="large" // 'small' | 'large' | number (Android)
color="#2196F3"
animating={true}
/>
// StatusBar
<StatusBar
barStyle="dark-content" // 'dark-content' | 'light-content' | 'default'
backgroundColor="#fff" // Android only
translucent={false} // Android only
hidden={false}
animated
/>
// Imperative API
StatusBar.setBarStyle('light-content', true);
StatusBar.setHidden(true, 'fade');
🎓 Module 3 Complete!
Congratulations! You've completed Module 3: Core Components. You now know how to use all the essential building blocks:
Universal container
Typography
Visual content
Scrolling content
Device boundaries
Touch handling
User input
Utility components
🚀 What's Next?
In Module 4: StyleSheet Deep Dive, you'll master Flexbox layouts, responsive design patterns, platform-specific styling, and organizing styles at scale.
🎉 Module 3 Complete!
You've mastered all the core components of React Native. With these building blocks, you can create any UI imaginable. Next up: making those UIs beautiful with StyleSheet mastery!