Dead simple theming and dark mode in React Native
Tags: reactnative, theming, darkmode, javascript
Published: 2020-01-18 20:20:29

With the release of iOS 13, both Android and iOS now support dark mode. Having support for dark mode in your app is not just a nice extra, it is a core requirement if you want your app to fit in with the OS.

But at time of writing, there is no official way for React Native apps to support dark mode. In my search for a simple clean way to implement theming, I decided to write a small library for this: [react-native-themed-stylesheets](https://www.npmjs.com/package/react-native-themed-styles).

It is dead simple to use, because it builds on existing structures such as StyleSheet and hooks. It also does not impose any structure on your theme, which means you can use it not only for light/dark mode, but also for spacing, fonts, other colours, or whatever you dream up.

(TL;DR If you just want to see the complete code, scroll down to the end of this article)

# Defining your themes

The first thing you want to do is define and register your themes. I this example I'm just going to use a light and dark theme. First, we define our two themes, which we then pass to the `registerThemes ` function:

```typescript
// themes.ts

import { registerThemes } from "react-native-themed-stylesheets"

const light = { backgroundColor: "white", textColor: "black" }
const dark = { backgroundColor: "black", textColor: "white" }

const styleSheetFactory = registerThemes(
  { light, dark },
  () => "light"
)

export { styleSheetFactory }
```

This will return a factory function that you can use to create themed stylesheets. The `registerThemes ` function takes a second argument which is a callback that returns the name of the default theme. In this case we make it just return "light", which means that our app will default to a light theme.

# Creating stylesheets from your themes

Now that we have our stylesheet factory, we can use it to create a themed stylesheet. This factory function behaves almost the same as `StyleSheet.create`, with the exception that your theme is passed as a parameter to the callback function.

In the following snippet, we create two styles: container and text. For both styles, we refer to a variable that we defined in our theme:

```typescript
// my-component.tsx

import { styleSheetFactory } from "./themes"

const styles = styleSheetFactory(theme => ({
  container: {
    backgroundColor: theme.backgroundColor,
    flex: 1
  },
  text: {
    color: theme.textColor
  }
}))
```

# Applying stylesheets to your components

Finally, we must apply our styles to our components. For this we use the `useTheme` hook. It takes the themed stylesheet we just created, and optionally a name of a theme to use. It will then compute the component styles with that theme applied:

```typescript
// my-component.tsx

import { useTheme } from "react-native-themed-stylesheets"

// const styles = styleSheetFactory(...)

const MyComponent = () => {
  const [styles] = useTheme(styles, "dark")

  return (
    <View style={styles.container}>
      <Text style={styles.text}>Hello there</Text>
    </View>
  )
}
```

# Switching theme based on the OS appearance

In the example above, we manually told the `useTheme` hook to apply the "dark" theme. Instead of specifying this yourself, you usually want this to automatically mirror the OS theme. Fortunately this is very easy to do using the `react-native-appearance package`.

In this snippet, we retrieve the OS theme using `useColorScheme()`, and then return the appropriate application theme. If for whatever reason the OS theme is not "light" or "dark", we default to using the light theme. So even if in the future a "pink" theme will be supported on the OS level, our app won't break but gracefully degrade.

```typescript
// themes.ts

import { useColorScheme } from "react-native-appearance"
import { registerThemes } from "react-native-themed-styles"

const styleSheetFactory = registerThemes({ light, dark }, () => {
  const colorScheme = useColorScheme()
  return ["light", "dark"].includes(colorScheme) ? colorScheme : "light"
})
```


# That's it!

I hope you liked this short introduction to theming in React Native. If you want to try out the package, you can find it at GitHub or NPM.

{% github wvteijlingen/react-native-themed-styles no-readme %}

## Complete code

```typescript
import { registerThemes, useTheme } from "react-native-themed-stylesheets"
import { useColorScheme } from "react-native-appearance"

// 1. Register your themes
const styleSheetFactory = registerThemes({
  light: { backgroundColor: "white", textColor: "black", image: require("./light.png") },
  dark: { backgroundColor: "black", textColor: "white", image: require("./dark.png") }
}, () => {
  const colorScheme = useColorScheme()
  return ["light", "dark"].includes(colorScheme) ? colorScheme : "light"
})

// 2. Create a stylesheet
const styles = styleSheetFactory(theme => ({
  container: {
    backgroundColor: theme.backgroundColor,
    flex: 1
  },
  text: {
    color: theme.textColor
  }
}))

// 3. Apply the styles
const MyComponent = () => {
  const [styles, theme, themeName] = useTheme(styles)

  return (
    <View style={styles.container}>
      <Text style={styles.text}>{`You are viewing the ${themeName} theme`}</Text>
      <Image source={theme.image} />
    </View>
  )
}
```