RORK LABJP
ENGINE — Rork Max generates code on top of Claude Code and Claude Opus 4.6, which is worth knowing when you tune how specific your prompts areSPLIT — Rork Max is Apple-only. If you also need Android, the original Rork is the one that generates cross-platform apps with React NativeDEVICE — Rork Max targets iPhone, iPad, Apple Watch, and Vision Pro, reaching territory React Native struggles to coverCREDIT — Billing runs on credits: one credit per AI interaction, reset on the 1st of each month with nothing carried overPLAN — Free gives 35 credits a month (5 per day); Junior is $25/mo, Senior $100/mo, and Rork Max $200/mo, with Senior the usual pick for MVP workFUND — Rork raised $2.8M from a16z and now draws over 743,000 monthly visitsENGINE — Rork Max generates code on top of Claude Code and Claude Opus 4.6, which is worth knowing when you tune how specific your prompts areSPLIT — Rork Max is Apple-only. If you also need Android, the original Rork is the one that generates cross-platform apps with React NativeDEVICE — Rork Max targets iPhone, iPad, Apple Watch, and Vision Pro, reaching territory React Native struggles to coverCREDIT — Billing runs on credits: one credit per AI interaction, reset on the 1st of each month with nothing carried overPLAN — Free gives 35 credits a month (5 per day); Junior is $25/mo, Senior $100/mo, and Rork Max $200/mo, with Senior the usual pick for MVP workFUND — Rork raised $2.8M from a16z and now draws over 743,000 monthly visits
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-max40themingdark-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-07-13
Your Detox and Maestro E2E Suite Was All Green — but the Retries Were Hiding a Flaky Test That Crashed in Production
Your E2E suite is green, yet production still crashes. The culprit: auto-retries quietly swallowing flaky failures. Field notes on measuring per-test flake rate, quarantining, and keeping only real failures as release gates.
Dev Tools2026-07-01
Rork Max Cloud Compilation — Shipping Native Apps Without Owning a Mac
How Rork Max's cloud compilation lets you build, device-test, and publish native iOS/iPadOS apps without a local Mac — including how to triage failed builds and when you should still keep a real Mac around.
Dev Tools2026-06-17
Checking Age Without Collecting Birthdays — Wiring the Declared Age Range API into a Rork App
How to use the iOS 26 Declared Age Range API to receive an age band without ever storing a birthdate, with both the Rork Max native Swift path and the standard Rork (Expo) native-module bridge, plus where to draw the responsibility boundary.
📚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 →