Build Themed Components in React Native
Tags: reactnative, typescript
Published: 2022-06-27 15:01:04

---
title: Build Themed Components in React Native
published: true
description: This tutorial will show you how you can build and use themed components in your React Native apps.
tags: reactnative, typescript
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r3lz753ikn2yzcfevfu0.jpg
canonical_url: https://www.reactnativeschool.com/build-themed-components-in-react-native
---


Building a multi-theme app doesn't have to be complicated, so long as you take advantage of custom components. In this tutorial we'll cover how to create your own versions of core React Native components that are automatically themed to use the right colors based on the user's preferred color scheme.

Take a look at the code for the `StopWatch` screen below. There's nothing unique to supporting multiple themes in it, but it fully supports multiple themes thanks to the custom components we'll create.

```tsx
// screens/StopWatch.tsx

import { StyleSheet } from "react-native"

import { Text, View, StatusBar, SafeAreaView } from "components/themed"
import { CircleButton } from "components/buttons"
import { useStopWatch } from "hooks/useStopWatch"
import { LapList } from "components/lists"

const StopWatch = () => {
  const {
    time,
    isRunning,
    start,
    stop,
    reset,
    lap,
    laps,
    currentLapTime,
    hasStarted,
    slowestLapTime,
    fastestLapTime,
  } = useStopWatch()

  return (
    <SafeAreaView style={{ flex: 1 }}>
      <StatusBar />
      <View style={styles.container}>
        <Text style={styles.timeText}>{time}</Text>

        <View style={styles.row}>
          <CircleButton
            onPress={() => {
              isRunning ? lap() : reset()
            }}
          >
            {isRunning ? "Lap" : "Reset"}
          </CircleButton>
          <CircleButton
            onPress={() => {
              isRunning ? stop() : start()
            }}
            color={isRunning ? "red" : "green"}
          >
            {isRunning ? "Stop" : "Start"}
          </CircleButton>
        </View>

        <LapList
          hasStarted={hasStarted}
          currentLapTime={currentLapTime}
          laps={laps}
          fastestLapTime={fastestLapTime}
          slowestLapTime={slowestLapTime}
        />
      </View>
    </SafeAreaView>
  )
}

const styles = StyleSheet.create({
  container: { flex: 1, alignItems: "center" },
  timeText: {
    fontSize: 60,
    fontWeight: "300",
    marginTop: 100,
    fontVariant: ["tabular-nums"], // fixed width character
  },
  row: {
    flexDirection: "row",
    width: "100%",
    justifyContent: "space-between",
    paddingHorizontal: 20,
    marginTop: 100,
  },
})

export default StopWatch
```

![Example of light and dark theme](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6xt6cmv0oll2522krrgj.png)
 

## The useThemeColors Hook

At the core of our themed components is the `useThemeColors` hook. This hook does the heavy lifting for our theme based logic.

We built a version of this hook in [a recent tutorial](https://www.reactnativeschool.com/how-to-detect-user-color-preference-in-react-native). I would recommend you read it to get a better understanding of how to implement it and the "why" behind it.

From this hook we return an object with two pieces of data in it:

1. The colors for the currently active theme. This is standardized between "light" and "dark". It could also expand to other themes as well.
2. `isDark` will be used to determine the color of things like the `StatusBar`. We can't change the actual text color but we can determine if the text should be light or dark.

```tsx
// hooks/useThemeColors.tsx

import Colors from "constants/Colors"
import { useColorScheme } from "hooks/useColorScheme"

export function useThemeColors() {
  const theme = useColorScheme()

  return {
    isDark: theme === "dark",
    colors: Colors[theme],
  }
}
```

## The View Components

The goal of our custom `View` component is to serve as a building block that functions just like a normal `View`. All we do is set the background color and then forward any user defined props onto the underlying `View`.

This allows us to use the custom `View` just like a normal `View`, including overriding our default background color. The same goes for the `SafeAreaView`.

```tsx
// components/themed/View.tsx

import { View as DefaultView, ViewProps } from "react-native"
import { SafeAreaView as DefaultSafeAreaView } from "react-native-safe-area-context"

import { useThemeColors } from "hooks/useThemeColors"

export function View(props: ViewProps) {
  const { style, ...otherProps } = props
  const { colors } = useThemeColors()

  return (
    <DefaultView
      style={[{ backgroundColor: colors.background }, style]}
      {...otherProps}
    />
  )
}

export const SafeAreaView = (props: ViewProps) => {
  const { style, ...otherProps } = props
  const { colors } = useThemeColors()

  return (
    <DefaultSafeAreaView
      style={[{ backgroundColor: colors.background }, style]}
      {...otherProps}
    />
  )
}
```

_[Learn how to set up path alias' like you see used throughout this post](https://www.reactnativeschool.com/how-to-setup-path-alias-in-a-react-native-typescript-app)_

## The Text Component

Just like the `View` component all the `Text` component does is set the text color based on the theme. Other options could be to set a default font family, add different text types ("title", "subtitle", etc). Even if you're not supporting multiple themes creating your own `Text` component is a great practice so you don't need to update styles all over your app when a font family changes.

```tsx
// components/themed/Text.tsx

import { Text as DefaultText, TextProps } from "react-native"

import { useThemeColors } from "hooks/useThemeColors"

export const Text = (props: TextProps) => {
  const { style, ...otherProps } = props
  const { colors } = useThemeColors()

  return <DefaultText style={[{ color: colors.text }, style]} {...otherProps} />
}
```

## The StatusBar Component

This one is unique because we can only customize parts of it. Therefore we use the `isDark` data to determine whether we should use the light text or dark text. We can also set the background color of the StatusBar on Android.

The benefit of using this approach is that if we choose to add more themes (as [the example repo this tutorial is based on has](https://github.com/ReactNativeSchool/expo-clock-app/blob/main/src/constants/Colors.ts)) we can define if a theme is light or dark in one place.

```tsx
// components/themed/StatusBar.tsx

import { StatusBar as DefaultStatusBar, StatusBarProps } from "react-native"

import { useThemeColors } from "hooks/useThemeColors"

export const StatusBar = (props: StatusBarProps) => {
  const { isDark, colors } = useThemeColors()

  const barStyle = isDark ? "light-content" : "dark-content"

  return (
    <DefaultStatusBar
      barStyle={barStyle}
      backgroundColor={colors.background}
      {...props}
    />
  )
}
```

## CHALLENGE

There are more default UI elements you can theme. With what's been discussed here, how would you do it?

- The different buttons. Notice they have a prop of `color` but that color stated may not be the same for each theme.
- The tab bar from React Navigation. The background color and the icons are customized based on the theme.

The [answers exist in the repo](https://github.com/ReactNativeSchool/expo-clock-app) but I'd encourage you to think about how you'd do it before digging into the repo to find how I did it.