Skip to main content

Overview

NativeWind provides seamless dark mode support using the standard dark: variant from Tailwind CSS. It automatically responds to the system color scheme and provides utilities for manual control.

Basic dark mode

Use the dark: variant to apply styles when dark mode is active:
import { View, Text } from 'react-native';

export default function App() {
  return (
    <View className="bg-white dark:bg-gray-900">
      <Text className="text-gray-900 dark:text-white">
        This text adapts to the color scheme
      </Text>
    </View>
  );
}
The dark: variant automatically applies when the system is set to dark mode. No additional configuration required.

Configuration

Enable automatic color scheme

Configure your app to respond to the system color scheme by setting userInterfaceStyle in your app configuration:
app.config.ts
export default {
  name: "my-app",
  slug: "my-app",
  userInterfaceStyle: "automatic",
  // ... other config
};

Force light or dark mode

To force a specific mode, set userInterfaceStyle to "light" or "dark":
app.config.ts
export default {
  userInterfaceStyle: "light", // or "dark"
};

Using the color scheme hook

React Native provides the useColorScheme hook to detect and respond to color scheme changes:
import { useColorScheme, View, Text, Appearance } from 'react-native';

export default function App() {
  const colorScheme = useColorScheme();
  
  return (
    <View className="flex-1 justify-center items-center bg-white dark:bg-gray-900">
      <Text className="text-gray-900 dark:text-white text-xl">
        Current theme: {colorScheme}
      </Text>
    </View>
  );
}

Manual color scheme control

You can manually set the color scheme using React Native’s Appearance API:
import { Appearance, Pressable, Text } from 'react-native';

export function ThemeToggle() {
  const toggleColorScheme = () => {
    const current = Appearance.getColorScheme();
    Appearance.setColorScheme(current === 'dark' ? 'light' : 'dark');
  };
  
  return (
    <Pressable 
      onPress={toggleColorScheme}
      className="px-6 py-3 bg-gray-200 dark:bg-gray-700 rounded-lg"
    >
      <Text className="text-gray-900 dark:text-white font-semibold">
        Toggle Theme
      </Text>
    </Pressable>
  );
}
Calling Appearance.setColorScheme(null) returns control to the system color scheme.

Dark mode design patterns

Text and backgrounds

Create proper contrast in both light and dark modes:
<View className="bg-white dark:bg-gray-900 p-6 rounded-xl">
  <Text className="text-gray-900 dark:text-white text-2xl font-bold mb-2">
    Card Title
  </Text>
  <Text className="text-gray-600 dark:text-gray-300">
    Supporting text that remains readable in both modes
  </Text>
</View>

Borders and dividers

Adjust border colors for visibility in dark mode:
<View className="border-b border-gray-200 dark:border-gray-700 py-4">
  <Text className="text-gray-900 dark:text-white">List Item</Text>
</View>

Shadows and elevation

Shadows behave differently in dark mode. Consider using elevation on Android:
<View className="bg-white dark:bg-gray-800 shadow-lg dark:shadow-gray-900/50 rounded-xl p-6">
  <Text className="text-gray-900 dark:text-white">
    Card with adaptive shadow
  </Text>
</View>

Interactive elements

Ensure buttons and interactive elements have proper contrast:
<Pressable className="bg-blue-500 dark:bg-blue-600 active:bg-blue-600 dark:active:bg-blue-700 px-6 py-3 rounded-lg">
  <Text className="text-white font-semibold text-center">
    Primary Action
  </Text>
</Pressable>

Creating a dark mode color palette

Define a consistent color palette for both light and dark modes:
global.css
@theme {
  /* Light mode colors */
  --color-background: #ffffff;
  --color-surface: #f9fafb;
  --color-border: #e5e7eb;
  --color-text-primary: #111827;
  --color-text-secondary: #6b7280;
  
  /* Dark mode colors */
  --color-background-dark: #111827;
  --color-surface-dark: #1f2937;
  --color-border-dark: #374151;
  --color-text-primary-dark: #f9fafb;
  --color-text-secondary-dark: #9ca3af;
}
Use these in your components:
<View className="bg-gray-50 dark:bg-gray-900">
  <View className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 p-4 rounded-lg">
    <Text className="text-gray-900 dark:text-gray-50 text-lg font-bold">
      Themed Card
    </Text>
    <Text className="text-gray-600 dark:text-gray-300 mt-2">
      Content with consistent theming
    </Text>
  </View>
</View>

Testing dark mode

Test your dark mode implementation across different scenarios:
1

Test system theme switching

Change your device’s system theme and verify your app responds correctly:
  • iOS: Settings > Display & Brightness > Appearance
  • Android: Settings > Display > Dark theme
  • Simulator: Device menu > Appearance
2

Test manual theme controls

If you provide manual theme switching, verify it works independently of system settings:
import { Appearance } from 'react-native';

// Test in your component
Appearance.setColorScheme('dark');
Appearance.setColorScheme('light');
Appearance.setColorScheme(null); // System
3

Verify contrast ratios

Check that all text meets WCAG accessibility guidelines:
  • Normal text: 4.5:1 contrast ratio minimum
  • Large text (18pt+): 3:1 contrast ratio minimum
  • UI components: 3:1 contrast ratio minimum
4

Test edge cases

Check these scenarios:
  • App launch in dark mode
  • Theme switching while app is in background
  • Image and icon visibility in both modes
  • Loading and error states

Best practices

Don’t treat dark mode as an afterthought. Design and implement both color schemes together:
// Good: Both modes considered
<View className="bg-white dark:bg-gray-900">
  <Text className="text-gray-900 dark:text-white">Text</Text>
</View>

// Avoid: Only light mode
<View className="bg-white">
  <Text className="text-black">Text</Text>
</View>
Use color meaningfully and consistently across both themes:
// Success states
<Text className="text-green-600 dark:text-green-400">Success</Text>

// Error states
<Text className="text-red-600 dark:text-red-400">Error</Text>

// Warning states
<Text className="text-yellow-600 dark:text-yellow-400">Warning</Text>
Don’t just invert colors. Adjust brightness levels for optimal readability:
// Good: Adjusted brightness
<View className="bg-gray-100 dark:bg-gray-800">
  <Text className="text-gray-900 dark:text-gray-100">
    Readable in both modes
  </Text>
</View>

// Avoid: Simple inversion can lack contrast
<View className="bg-gray-100 dark:bg-gray-900">
  <Text className="text-gray-900 dark:text-gray-100">
    Too much contrast in dark mode
  </Text>
</View>
Different people perceive dark mode differently. Gather feedback and iterate:
  • Some users prefer darker backgrounds (#000 vs #111)
  • Text contrast preferences vary
  • Color perception changes in dark mode
Adjust images and illustrations for dark mode when needed:
<Image
  source={colorScheme === 'dark' ? darkLogo : lightLogo}
  className="w-32 h-32"
/>

{/* Or use tint for icons */}
<Image
  source={icon}
  className="tint-gray-900 dark:tint-white"
/>
Pure black backgrounds (#000000) can cause eye strain and make text harder to read. Consider using dark grays like bg-gray-900 (#111827) instead.

Accessibility considerations

Always provide users the option to choose their preferred theme, even if it differs from their system setting. Some users have specific accessibility needs that require a particular color scheme.
Ensure your dark mode implementation is accessible:
  • Maintain sufficient color contrast in both modes
  • Test with screen readers
  • Ensure focus indicators are visible
  • Don’t rely on color alone to convey information
  • Test with color blindness simulators

Next steps

Theming

Learn more about creating custom themes

CSS variables

Use CSS variables for dynamic theming

Styling

Master the fundamentals of styling with NativeWind

Responsive design

Create layouts that adapt to different screens