RORK LABJP
RORK-VS-MAX — Standard Rork ships cross-platform iOS/Android via Expo (React Native); Rork Max builds native Swift across the Apple ecosystemRORK-MAX — Rork Max ($200/mo) covers iPhone, iPad, Watch, TV, Vision Pro, and iMessage, plus AR/LiDAR, Metal, Live Activities, and Core MLPUBLISH — Rork compiles on cloud Macs, taking you from a shareable test link to publishing on both stores; free to start, paid from $25ANDROID17 — Android 17 is expected to reach Pixel first this summer; large-screen resizability is becoming importantIOS27 — iOS 27 is expected this fall; with Siri's model revamp ahead, it's worth checking your app nowWORKFLOW — For solo devs, validate fast with Rork first, then consider Rork Max when you need Apple-only native capabilitiesRORK-VS-MAX — Standard Rork ships cross-platform iOS/Android via Expo (React Native); Rork Max builds native Swift across the Apple ecosystemRORK-MAX — Rork Max ($200/mo) covers iPhone, iPad, Watch, TV, Vision Pro, and iMessage, plus AR/LiDAR, Metal, Live Activities, and Core MLPUBLISH — Rork compiles on cloud Macs, taking you from a shareable test link to publishing on both stores; free to start, paid from $25ANDROID17 — Android 17 is expected to reach Pixel first this summer; large-screen resizability is becoming importantIOS27 — iOS 27 is expected this fall; with Siri's model revamp ahead, it's worth checking your app nowWORKFLOW — For solo devs, validate fast with Rork first, then consider Rork Max when you need Apple-only native capabilities
Articles/Dev Tools
Dev Tools/2026-03-14Beginner

Rork Max Dark Mode & Theming Guide — Customizing Your App's Appearance

Complete dark mode, light mode, and custom theming implementation. Design token management and modern UI patterns for Rork Max.

rork-max34themingdark-modedesign-tokensui-design3

Dark Mode & Theming in Rork Max

Dark mode has evolved from a nice-to-have feature to an essential requirement. Users expect apps to respect their system preferences, reduce eye strain during evening use, and conserve battery on OLED displays. Creating a themeable app demonstrates attention to user experience and accessibility.

Organizing Design Tokens

Color Palette and Design System

The foundation of effective theming is centralizing design tokens—colors, spacing, typography, and other design values.

// theme/tokens.js
const lightTheme = {
  colors: {
    primary: '#0066CC',
    secondary: '#FF6B6B',
    background: '#FFFFFF',
    surface: '#F5F5F5',
    text: '#222222',
    textSecondary: '#666666',
    border: '#DDDDDD',
    success: '#22C55E',
    warning: '#FBBF24',
    error: '#EF4444',
  },
  spacing: {
    xs: 4,
    sm: 8,
    md: 16,
    lg: 24,
    xl: 32,
  },
  fontSize: {
    sm: 12,
    base: 14,
    lg: 16,
    xl: 20,
    '2xl': 24,
  },
};
 
const darkTheme = {
  colors: {
    primary: '#4A9EFF',
    secondary: '#FF8A9B',
    background: '#1A1A1A',
    surface: '#2D2D2D',
    text: '#FFFFFF',
    textSecondary: '#CCCCCC',
    border: '#404040',
    success: '#4ADE80',
    warning: '#FACC15',
    error: '#F87171',
  },
  spacing: lightTheme.spacing,
  fontSize: lightTheme.fontSize,
};
 
export { lightTheme, darkTheme };

Centralizing tokens eliminates duplication and makes theme switching effortless.

Managing Global Theme State

Context API for Theme Distribution

React Context allows you to make the current theme accessible throughout your app without prop drilling.

import { createContext, useState, useContext, useEffect } from 'react';
import { useColorScheme } from 'react-native';
import { lightTheme, darkTheme } from './tokens';
 
const ThemeContext = createContext();
 
export function ThemeProvider({ children }) {
  const systemColorScheme = useColorScheme();
  const [isDarkMode, setIsDarkMode] = useState(
    systemColorScheme === 'dark'
  );
 
  // Sync with system preferences
  useEffect(() => {
    if (systemColorScheme) {
      setIsDarkMode(systemColorScheme === 'dark');
      console.log(`System theme detected: ${systemColorScheme}`);
      // Expected output: System theme detected: dark
    }
  }, [systemColorScheme]);
 
  const theme = isDarkMode ? darkTheme : lightTheme;
 
  const toggleTheme = () => {
    setIsDarkMode(!isDarkMode);
    console.log(`Theme toggled to: ${!isDarkMode ? 'dark' : 'light'}`);
    // Expected output: Theme toggled to: dark
  };
 
  const value = {
    isDarkMode,
    theme,
    toggleTheme,
  };
 
  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}
 
export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

Applying Themes to Components

Using Theme Values in Components

Components consume theme values from the context hook, automatically updating when the theme changes.

import { useTheme } from './ThemeProvider';
import { View, Text, StyleSheet } from 'react-native';
 
export function ThemedCard({ title, description }) {
  const { theme } = useTheme();
 
  const styles = StyleSheet.create({
    container: {
      backgroundColor: theme.colors.surface,
      borderColor: theme.colors.border,
      borderWidth: 1,
      borderRadius: 8,
      padding: theme.spacing.md,
      marginBottom: theme.spacing.md,
    },
    title: {
      color: theme.colors.text,
      fontSize: theme.fontSize.lg,
      fontWeight: 'bold',
      marginBottom: theme.spacing.sm,
    },
    description: {
      color: theme.colors.textSecondary,
      fontSize: theme.fontSize.base,
      lineHeight: 20,
    },
  });
 
  return (
    <View style={styles.container}>
      <Text style={styles.title}>{title}</Text>
      <Text style={styles.description}>{description}</Text>
    </View>
  );
}

This pattern ensures components always use the correct colors for the current theme.

Persisting User Preferences

Saving Theme Selection

Users expect their theme choice to persist across app sessions. Store preferences locally.

import AsyncStorage from '@react-native-async-storage/async-storage';
import { useEffect } from 'react';
 
const THEME_PREFERENCE_KEY = '@rork_theme_preference';
 
export function usePersistentTheme() {
  const { isDarkMode, toggleTheme } = useTheme();
 
  // Load saved preference on startup
  useEffect(() => {
    loadThemePreference();
  }, []);
 
  // Save whenever theme changes
  useEffect(() => {
    saveThemePreference(isDarkMode);
  }, [isDarkMode]);
 
  const loadThemePreference = async () => {
    try {
      const savedTheme = await AsyncStorage.getItem(THEME_PREFERENCE_KEY);
      if (savedTheme !== null) {
        const isDark = savedTheme === 'dark';
        if (isDark !== isDarkMode) {
          toggleTheme();
        }
        console.log(`Loaded saved theme: ${savedTheme}`);
        // Expected output: Loaded saved theme: dark
      }
    } catch (error) {
      console.error('Theme load error:', error);
    }
  };
 
  const saveThemePreference = async (isDark) => {
    try {
      const themeValue = isDark ? 'dark' : 'light';
      await AsyncStorage.setItem(THEME_PREFERENCE_KEY, themeValue);
    } catch (error) {
      console.error('Theme save error:', error);
    }
  };
}

Dark Mode Best Practices

Accessibility and Contrast

Sufficient color contrast is critical for readability and accessibility compliance.

// Ensure adequate contrast ratios
const theme = {
  colors: {
    // Light mode
    lightText: '#222222',        // Against #FFFFFF: 17:1 ratio (WCAG AAA)
    lightBg: '#FFFFFF',
 
    // Dark mode
    darkText: '#FFFFFF',         // Against #1A1A1A: 16:1 ratio (WCAG AAA)
    darkBg: '#1A1A1A',
  },
};
 
export function AccessibleText({ children, isDark }) {
  const color = isDark ? theme.colors.darkText : theme.colors.lightText;
 
  return (
    <Text style={{ color }}>
      {children}
    </Text>
  );
}

WCAG 2.1 guidelines recommend a minimum 4.5:1 contrast ratio for normal text. Aim for 7:1 when possible.

Building Custom Themes

User-Defined Theme Creation

Enable advanced users to create personalized themes.

import { useState } from 'react';
 
export function useCustomTheme() {
  const [customThemes, setCustomThemes] = useState([]);
 
  const createCustomTheme = (name, colors) => {
    const newTheme = {
      id: Date.now(),
      name,
      colors: {
        primary: colors.primary,
        secondary: colors.secondary,
        background: colors.background,
        surface: colors.surface,
        text: colors.text,
        textSecondary: colors.textSecondary,
      },
    };
 
    setCustomThemes([...customThemes, newTheme]);
    console.log(`Custom theme created: ${name}`);
    // Expected output: Custom theme created: MyCyan
 
    return newTheme;
  };
 
  const deleteCustomTheme = (themeId) => {
    setCustomThemes(customThemes.filter(t => t.id !== themeId));
  };
 
  return {
    customThemes,
    createCustomTheme,
    deleteCustomTheme,
  };
}
 
export function CustomThemeEditor() {
  const [themeName, setThemeName] = useState('');
  const [primaryColor, setPrimaryColor] = useState('#0066CC');
  const { createCustomTheme } = useCustomTheme();
 
  const handleCreate = () => {
    if (themeName) {
      createCustomTheme(themeName, {
        primary: primaryColor,
        secondary: '#FF6B6B',
        background: '#FFFFFF',
        surface: '#F5F5F5',
        text: '#222222',
        textSecondary: '#666666',
      });
      setThemeName('');
    }
  };
 
  return (
    <div style={{ padding: '20px' }}>
      <input
        type="text"
        value={themeName}
        onChange={(e) => setThemeName(e.target.value)}
        placeholder="Theme name"
      />
      <input
        type="color"
        value={primaryColor}
        onChange={(e) => setPrimaryColor(e.target.value)}
      />
      <button onClick={handleCreate}>Create Theme</button>
    </div>
  );
}

Integration with Other Features

Applying consistent theming across camera and gallery screens is covered in Rork Max Camera & Gallery Guide.

For comprehensive design system alignment with design tools, see Rork Max Figma Integration Guide. Learn how to animate theme transitions smoothly in Rork Max Animations Guide.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Dev Tools2026-05-25
Implementation Notes: Adding StoreKit 2 In-App Purchases to a Rork iOS App
Notes from grafting StoreKit 2 in-app purchases onto Swift/SwiftUI code generated by Rork, drawing on the StoreKit 1-to-2 migration done across a 50M-cumulative-download wallpaper-app portfolio. Covers ProductID design, transaction verification, paywall UI, and production gotchas.
Dev Tools2026-05-23
Clearing the Red Errors in Xcode After Rork Max Generates Swift Code: A Priority Order
A priority order I use to clear the wall of red errors that Rork Max-generated Swift projects show on first open in Xcode, drawn from ten-plus years of indie iOS work.
Dev Tools2026-05-22
Designing an Observability Stack for Rork Max — Unifying Sentry, Crashlytics, and Cloudflare Logs from a Solo Developer's View
A practical observability stack design for apps shipped with Rork Max, covering Sentry, Crashlytics, and Cloudflare Logs role separation, scenario-based incident tracing routes, and how a solo developer can sustain it over years.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →