Software Engineering & Digital Products for Global Enterprises since 2006
CMMi Level 3SOC 2ISO 27001
View all services
Staff Augmentation
Embed senior engineers in your team within weeks.
Dedicated Teams
A ring-fenced squad with PM, leads, and engineers.
Build-Operate-Transfer
We hire, run, and transfer the team to you.
Contract-to-Hire
Try the talent. Convert when you're ready.
ForceHQ
Skill testing, interviews and ranking — powered by AI.
RoboRingo
Build, deploy and monitor voice agents without code.
MailGovern
Policy, retention and compliance for enterprise email.
Vishing
Test and train staff against AI-driven voice attacks.
CyberForceHQ
Continuous, adaptive security training for every team.
IDS Load Balancer
Built for Multi Instance InDesign Server, to distribute jobs.
AutoVAPT.ai
AI agent for continuous, automated vulnerability and penetration testing.
Salesforce + InDesign Connector
Bridge Salesforce data into InDesign to design print catalogues at scale.
View all solutions
Banking, Financial Services & Insurance
Cloud, digital and legacy modernisation across financial entities.
Healthcare
Clinical platforms, patient engagement, and connected medical devices.
Pharma & Life Sciences
Trial systems, regulatory data, and field-force enablement.
Professional Services & Education
Workflow automation, learning platforms, and consulting tooling.
Media & Entertainment
AI video processing, OTT platforms, and content workflows.
Technology & SaaS
Product engineering, integrations, and scale for tech companies.
Retail & eCommerce
Shopify, print catalogues, web-to-print, and order automation.
View all industries
Blog
Engineering notes, opinions, and field reports.
Case Studies
How clients shipped — outcomes, stack, lessons.
White Papers
Deep-dives on AI, talent models, and platforms.
Portfolio
Selected work across industries.
View all resources
About Us
Who we are, our story, and what drives us.
Co-Innovation
How we partner to build new products together.
Careers
Open roles and what it's like to work here.
News
Press, announcements, and industry updates.
Leadership
The people steering MetaDesign.
Locations
Gurugram, Brisbane, Detroit and beyond.
Contact Us
Talk to sales, hiring, or partnerships.
Request TalentStart a Project
Mobile Development

Implementing Dark Mode and Dynamic Theming in React Native

SS
Sukriti Srivastava
Technical Content Lead
December 2, 2024
10 min read
Implementing Dark Mode and Dynamic Theming in React Native — Mobile Development | MetaDesign Solutions

Why Dark Mode Is No Longer Optional in Mobile Apps

Dark mode has evolved from a niche preference into a mainstream user expectation. Apple and Google both made system-wide dark mode a flagship feature in iOS 13 and Android 10, and users overwhelmingly adopted it—studies indicate over 80% of smartphone users enable dark mode at least partially. Beyond aesthetics, dark mode delivers measurable benefits: reduced eye strain in low-light environments, significantly reduced battery consumption on OLED and AMOLED displays (where black pixels are physically turned off), and improved accessibility for users with light sensitivity. An app that ignores the system dark mode preference feels outdated and inconsiderate.

Detecting the System Theme with useColorScheme

React Native provides the built-in `useColorScheme()` hook that returns `"light"`, `"dark"`, or `null` based on the device's current system appearance setting. This hook automatically updates when the user toggles their system theme in device settings. The simplest dark mode implementation uses this hook to conditionally select styles: `const colorScheme = useColorScheme(); const backgroundColor = colorScheme === 'dark' ? '#121212' : '#FFFFFF';`. However, this basic approach quickly becomes unmaintainable as the number of themed components grows, which is why a centralized theming system is essential.

Building a Centralized Theme Provider with React Context

Create a ThemeContext that manages theme state globally and allows users to override the system default. Define a `ThemeProvider` component that wraps your app, maintaining an `isDark` state initialized from `useColorScheme()`. Expose a `toggleTheme` function and the current theme object (containing `colors.background`, `colors.text`, `colors.primary`, etc.) via `ThemeContext.Provider`. Any component in the tree can then call `const { theme, toggleTheme } = useContext(ThemeContext)` to access colors or render a toggle switch. Memoize the context value with `useMemo()` to prevent unnecessary re-renders when the theme hasn't changed.

Comprehensive Theming with React Native Paper

React Native Paper (by Callstack) is a Material Design component library with first-class dark mode support. It provides pre-built `DarkTheme` and `DefaultTheme` objects. Wrap your app with ``, and every Paper component (Button, Card, TextInput, Appbar, etc.) automatically renders with the correct theme colors. For brand customization, spread the base theme and override specific colors: `{ ...DarkTheme, colors: { ...DarkTheme.colors, primary: '#BB86FC', accent: '#03DAC6' } }`. This ensures your brand identity is preserved across both light and dark modes.

React Navigation has its own theming system that must be synchronized with your app's theme to avoid jarring visual inconsistencies. Pass your theme to ``. React Navigation's `DarkTheme` and `DefaultTheme` control the background color of screens, the color of header bars, the tab bar background, and the color of active/inactive tab icons. Map your custom theme colors to React Navigation's theme structure: `{ dark: isDark, colors: { primary, background, card, text, border, notification } }`. This ensures that screen transitions, headers, and tab bars all respect the current theme.

Transform Your Publishing Workflow

Our experts can help you build scalable, API-driven publishing systems tailored to your business.

Book a free consultation

Persisting User Theme Preference with AsyncStorage

A critical UX detail is remembering the user's theme choice across app sessions. Use `AsyncStorage` to save the preference: `AsyncStorage.setItem('theme_preference', isDark ? 'dark' : 'light')`. On app launch, read the stored value in a `useEffect` hook: `const stored = await AsyncStorage.getItem('theme_preference')`. If a stored preference exists, use it; otherwise, fall back to the system default from `useColorScheme()`. This three-tier priority system (user explicit choice > stored preference > system default) provides the most respectful and intuitive theming experience.

Performance Optimization: Avoiding Theme-Change Re-Render Cascades

When the theme changes, every component consuming the ThemeContext re-renders. In large apps, this can cause noticeable jank during the toggle animation. Mitigate this by memoizing the theme object in the Provider with `useMemo()`, using `React.memo()` on leaf components that accept theme colors as props, and splitting the theme context into separate `ThemeColorsContext` and `ThemeActionsContext` so that components only needing colors don't re-render when the `toggleTheme` function reference changes. For animated theme transitions, use `react-native-reanimated` to smoothly interpolate between light and dark color values over 300ms.

Testing Dark Mode Across Devices and Accessibility

Dark mode testing must go beyond "does it look dark?" Verify color contrast ratios meet WCAG AA standards (minimum 4.5:1 for normal text) in both light and dark modes using tools like the Accessibility Inspector on iOS and the Accessibility Scanner on Android. Test on both OLED (where true black saves battery) and LCD (where dark gray may be preferable) displays. Verify that images with transparent backgrounds render correctly against both light and dark surfaces. Test the full lifecycle: system theme change while app is backgrounded, user manual toggle, and cold start with a stored preference.

FAQ

Frequently Asked Questions

Common questions about this topic, answered by our engineering team.

Use the built-in useColorScheme() hook, which returns "light", "dark", or null based on the device's current system appearance setting. It automatically updates when the user toggles their system theme. Use this value to conditionally apply your dark or light theme throughout the app.

React Native Paper provides built-in DarkTheme and DefaultTheme with PaperProvider for comprehensive Material Design theming. It integrates with React Navigation's theme prop for consistent styling across screens, navigation headers, and tab bars.

Use AsyncStorage to save the user's choice: AsyncStorage.setItem("theme_preference", "dark"). On app launch, read the stored value and use it if present; otherwise fall back to the system default from useColorScheme(). This ensures the app respects the user's explicit preference.

Memoize the theme object in the Provider with useMemo(), use React.memo() on leaf components, and consider splitting the theme context into ThemeColorsContext and ThemeActionsContext. For smooth transitions, use react-native-reanimated to interpolate between color values.

Verify color contrast ratios meet WCAG AA standards (4.5:1 minimum) in both modes. Test on OLED and LCD displays. Check that images with transparent backgrounds render correctly against both surfaces. Use platform accessibility tools to audit contrast and readability.

Discussion

Join the Conversation

Ready when you are

Let's build something great together.

A 30-minute call with a principal engineer. We'll listen, sketch, and tell you whether we're the right partner — even if the answer is no.

Talk to a strategist
Need help with your project? Let's talk.
Book a call