How Production Apps Handle Dark Mode in React Native

How Production Apps Handle Dark Mode in React Native

Written by
Written by

Soumya Ranjan K.

Post Date
Post Date

Nov 12, 2025

shares

1_sisE3_T7zoFdzEBGUfBlDQ

In this guide, we’ll break down how to build a production-ready dark mode system in React Native — from detecting system themes and structuring tokens, to handling transitions, accessibility, and real-world optimizations.

Why Dark Mode Matters

Production-grade dark mode should also ensure:

Detecting System Theme

React Native provides a built-in hook called useColorScheme() to detect whether the system is in light or dark mode.

import { useColorScheme } from 'react-native'; const scheme = useColorScheme(); // 'light' or 'dark'

This gives you effortless access to the OS-level theme.

Theme Setup: Design Tokens Approach

Instead of storing only colors, a scalable theme system should include tokens:

export const LightTheme = { background: '#FFFFFF', text: '#000000', primary: '#1e90ff', spacing: { sm: 8, md: 16, lg: 24 }, radius: { sm: 6, md: 12 }, shadow: 'rgba(0,0,0,0.1)', };
export const DarkTheme = { background: '#000000', text: '#FFFFFF', primary: '#1e90ff', spacing: { sm: 8, md: 16, lg: 24 }, radius: { sm: 6, md: 12 }, shadow: 'rgba(255,255,255,0.1)', };

You can expand these with spacing, borderRadius, typography, gradients, etc.

Managing Theme Preference (Redux/Zustand)

It’s best practice to allow users to override system theme manually. For this, maintain theme state using Redux/ Zustand.
Example using Redux Toolkit:
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; export type ThemeType = 'light' | 'dark' | 'device'; interface ThemeState { theme: ThemeType; } const initialState: ThemeState = { theme: 'device' }; const ThemeSlice = createSlice({ name: 'theme', initialState, reducers: { setTheme(state, action: PayloadAction<ThemeType>) { state.theme = action.payload; }, }, }); export const { setTheme } = ThemeSlice.actions; export default ThemeSlice.reducer;

Theme Hook (React + System + Persistence)

import { useSelector } from 'react-redux'; import { useColorScheme } from 'react-native'; import { DarkTheme, LightTheme } from '../theme/appColorTheme'; import { ThemeState } from '../store/ThemeSlice'; export const useAppTheme = () => { const { theme } = useSelector(ThemeState); const systemTheme = useColorScheme(); const resolvedTheme = theme === 'device' ? systemTheme : theme; return resolvedTheme === 'dark' ? DarkTheme : LightTheme; };

Applying the Theme in Components

You can apply theme colors in two ways.

Option A: Inline Styles (Best for small apps)

const MyComponent = () => { // Extract the theme from hook and apply const theme = useAppTheme(); return ( <View style={{ backgroundColor: theme.background }}> <Text style={{ color: theme.text }}>Hello</Text> </View> ); };

Option B: Dynamic Styles (Best for scaling)

const createStyles = (theme) => ({ container: { backgroundColor: theme.background, padding: 10, }, text: { color: theme.text, }, }); const MyComponent = () => { const theme = useAppTheme(); const styles = useMemo(() => createStyles(theme), [theme]); return ( <View style={styles.container}> <Text style={styles.text}>Hello</Text> </View> ); };

This ensures styles re-render efficiently only when the theme changes.

Production Realities: Where Dark Mode Gets Hard

1. Theme-Aware Icons and Images

Icons often look washed out in dark mode.

Solutions:

<Image source={icon} style={{ tintColor: theme.text }} />

2. Smooth Theme Transitions

Sudden theme switching looks harsh.

Use Reanimated:

const progress = useSharedValue(0); useEffect(() => { progress.value = withTiming(theme === 'dark' ? 1 : 0, { duration: 300 }); }, [theme]);

Reanimated lets you blend between two colors:

const animatedStyle = useAnimatedStyle(() => { return { backgroundColor: interpolateColor( progress.value, [0, 1], [LightTheme.background, DarkTheme.background] ), }; });

Now your UI will gracefully fade between light and dark backgrounds.

You can interpolate:

3. Accessibility-Aware Theme Adjustments

Accessibility is often overlooked in dark mode implementations. But users with visual impairments may have specific system preferences enabled.

React Native provides APIs to detect these settings.

Some users may require:

Dark mode must adapt to these preferences, or it becomes unusable for some users.

Some users experience motion sensitivity, dizziness, vertigo, discomfort with excessive animation

They enable Reduce Motion in their OS settings.

It can detected by:

const reduceMotion = await accessibilityInfo.isReduceMotionEnabled();

So for that case, disable theme transition animations, disable fancy transitions (opacity, scaling, parallax), remove large motion effects;

Finally, use instant transitions instead

This ensures your app is inclusive.Finally, use instant transitions instead

In Summary

Use : 

Theme changes are global → optimize carefully.

By this point, you’ve gone beyond simple theming — you’ve built a foundation that handles nuance, motion, and real-world constraints. What’s left is perspective: understanding what dark mode really means in a production app.

Conclusion

Dark mode is more than an aesthetic toggle — it’s a test of your architecture and attention to detail. A proper implementation touches almost every layer of your app: design tokens, state management, asset handling, and user accessibility. When all of it clicks, the difference is subtle but unmistakable — your app just feels premium.

If your dark mode survives these conditions without breaking:

— then congratulations, you’ve built more than a feature.

You’ve built a design system that scales, adapts, and respects your users.