DEV Community

Spencer Carli
Spencer Carli

Posted on • Originally published at reactnativeschool.com

How to Detect User Color Preference in React Native

React Native has two ways for you to determine a user's appearance preferences:

  • The Appearance API which lets you get their current color scheme
  • The useColorScheme hook which provides an up-to-date color scheme (it will automatically update as the user's preferences change)

This post was originally posted on React Native School. Visit React Native School to access 170+ tutorials and 18 courses!

Today we'll be leveraging the useColorScheme hook.

We'll take a look at the following screen which shows black text and a white background - a light color scheme.

import React from "react"
import { View, Text, ViewStyle, TextStyle, StyleSheet } from "react-native"

const App = () => {
  const viewStyles: ViewStyle[] = [
    styles.container,
    { backgroundColor: "white" },
  ]
  const textStyles: TextStyle[] = [styles.text, { color: "black" }]

  return (
    <View style={viewStyles}>
      <Text style={textStyles}>Hello, world!</Text>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
  text: {
    fontWeight: "bold",
    fontSize: 20,
  },
})

export default App
Enter fullscreen mode Exit fullscreen mode

We can use the useColorScheme hook and determine the user's preferred color scheme, changing our colors based on it.

import React from "react"
import {
  View,
  Text,
  ViewStyle,
  TextStyle,
  StyleSheet,
  useColorScheme,
} from "react-native"

const App = () => {
  const colorScheme = useColorScheme()
  const isLightTheme = colorScheme === "light"

  const viewStyles: ViewStyle[] = [
    styles.container,
    { backgroundColor: isLightTheme ? "white" : "black" },
  ]
  const textStyles: TextStyle[] = [
    styles.text,
    { color: isLightTheme ? "black" : "white" },
  ]

  return (
    <View style={viewStyles}>
      <Text style={textStyles}>Hello, world!</Text>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
  text: {
    fontWeight: "bold",
    fontSize: 20,
  },
})

export default App
Enter fullscreen mode Exit fullscreen mode

NOTE: If you have the chrome debugger enabled useColorScheme will always return 'light'.

Improving Reusability with useColorScheme

Currently our component needs to know the color theme to determine what color to use. We can put our app colors into a Colors object where we define colors based on the theme.

Then, in the component, we can just say we want to use colors.background or colors.text. Something descriptive.

import React from "react"
import {
  View,
  Text,
  ViewStyle,
  TextStyle,
  StyleSheet,
  useColorScheme,
} from "react-native"

const Colors = {
  light: {
    background: "white",
    text: "black",
  },
  dark: {
    background: "black",
    text: "white",
  },
}

const App = () => {
  const colorScheme = useColorScheme()
  const colors = Colors[colorScheme]

  const viewStyles: ViewStyle[] = [
    styles.container,
    { backgroundColor: colors.background },
  ]
  const textStyles: TextStyle[] = [styles.text, { color: colors.text }]

  return (
    <View style={viewStyles}>
      <Text style={textStyles}>Hello, world!</Text>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
  text: {
    fontWeight: "bold",
    fontSize: 20,
  },
})

export default App
Enter fullscreen mode Exit fullscreen mode

TypeScript Tip: Dealing with useColorScheme null Type

You may be seeing a warning when trying to access Colors[colorScheme] because colorScheme may be null...

This is a nice tip I saw in an Expo Template on how to fix this.

We can create a custom useColorScheme hook that changes the return type to not be null via NonNullable.

// hooks/ColorSchemeName.ts

import {
  ColorSchemeName,
  useColorScheme as _useColorScheme,
} from "react-native"

// The useColorScheme value is always either light or dark, but the built-in
// type suggests that it can be null. This will not happen in practice, so this
// makes it a bit easier to work with.
export default function useColorScheme(): NonNullable<ColorSchemeName> {
  return _useColorScheme() as NonNullable<ColorSchemeName>
}
Enter fullscreen mode Exit fullscreen mode

Creating your own useColorScheme hook is also nice if you want to "fake" a different color scheme/quickly change through them. Rather than returning the result of _useColorScheme you can just return dark or light.

Custom useThemeColors Hook

Finally, in an effort to write even more reusable code, we can simplify our logic further and create a useThemeColors hook to return just the colors of the current theme without the component having to be aware of what the current theme is.

// hooks/useThemeColors.ts
import useColorScheme from "hooks/useColorScheme"

const Colors = {
  light: {
    background: "white",
    text: "black",
  },
  dark: {
    background: "black",
    text: "white",
  },
}

const useThemeColors = () => {
  const colorScheme = useColorScheme()
  const colors = Colors[colorScheme]

  return colors
}

export default useThemeColors
Enter fullscreen mode Exit fullscreen mode

Further reading: how to set up path alias', like shown above, in a React Native + TypeScript project.

Now your App.tsx can be as simple as

import React from "react"
import { View, Text, ViewStyle, TextStyle, StyleSheet } from "react-native"

import useThemeColors from "hooks/useThemeColors"

const App = () => {
  const colors = useThemeColors()

  const viewStyles: ViewStyle[] = [
    styles.container,
    { backgroundColor: colors.background },
  ]
  const textStyles: TextStyle[] = [styles.text, { color: colors.text }]

  return (
    <View style={viewStyles}>
      <Text style={textStyles}>Hello, world!</Text>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
  text: {
    fontWeight: "bold",
    fontSize: 20,
  },
})

export default App
Enter fullscreen mode Exit fullscreen mode

Now within our component we don't have to do anything to figure out the current theme or which colors to use for that them. The useThemeColors hook encapsulates all of that and returns a color object from which we can choose the right color for our UI elements.

Expanded Example

You can find an expanded version of this code in a more real-world example in React Native School's Clock example app.

Multi-theme Clock

Top comments (0)