How to use Theme in styled-components w/ Typescript

Introduction

React has become one of the most popular libraries for building robust applications, and styled-components is a powerful tool that enables developers to write CSS code within their React components. However, when using TypeScript with React, it can be challenging to maintain type-safety and consistency throughout your application’s styles. Theming allows you to write less code and maintain consistency with styling. Let’s see how to do that using styled-components with React and Typescript.

Setup project

npm create vite@latest
npm i -D styled-components @types/styled-components

Here is an example folder structure:

|_ public
|_ src
  |_ styles
      |_ Theme.tsx
  |_ App.tsx
  |_ index.tsx
|_ package.json

Create Theme

Let’s go to Theme.tsx, import ThemeProvider and write type and theme provider.

import { ThemeProvider } from "styled-components"
 
type Theme {
  color: string;
  backgroundColor: string;
}
 
type ThemeProps {
  children: React.ReactNode;
}

Second step is creating light and dark theme objects.

const themeLight: Theme = {
  color: '#000',
  backgroundColor: '#fff'
}
 
const themeDark: Theme = {
  color: '#fff',
  backgroundColor: '#000'
}

Now we have to create our Theme Provider and now we can set theme to “light” for the sake of simplicity.

const Theme = ({children}: ThemeProps) => {
  const theme = "light";
 
  return (
    <ThemeProvider theme={theme === "light" ? themeLight : themeDark}>
     {children}
    </ThemeProvider>
  );
};
 
export default Theme;

We are using ThemeProvider from styled-components and setting attribute theme to our objects based on a condition.

In order to have access to the Theme everywhere in our codebase, we have to wrap our App with it. It’s a common React pattern to have a wrapper component, often it’s called AppWrapper, AppProviders or Providers.

import App from "./App"
import { Theme } from "./styles/Theme"
 
const AppProviders = () => {
  return (
    <Theme>
      <App />
    </Theme>
  )
}
 
export default AppProviders

Using Theme

Now we can access to our theme across whole application. Let’s give it a try in App.tsx and create Wrapper component.

import styled from "styled-components"
 
const Wrapper = styled.div`
height: 100vh;
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
background-color: ${props => props.theme.backgroundColor};
color: ${props => props.theme.color}
`
 
const App = () => {
 return (
    <Wrapper>
      <div>Hello World</div>
    </Wrapper>
  )
}

In order to toggle the theme we have to change theme variable in Theme.tsx which we hardcoded before to “light”. There are quite a few options to do that, in this post I’m going to create simple React context:

import { createContext, ReactNode, useMemo, useState, React } from "react";
 
export type ThemeContextType = {
  theme: string;
  changeTheme: () => void;
}
 
export const ThemeContext = createContext<TThemeContext | null>(null);
 
export const ThemeContextProvider = ({ children }: {children: React.ReactNode}) => { 
  const [theme, setTheme] = useState("light")
 
  const changeTheme = () => {
    setTheme (prev => prev
     "light" ? "dark" : "light")
  }
 
  const contextValues = useMemo (
    () => ({ theme, changeTheme }), [theme]
  );
 
  return (
    <ThemeContext.Provider value={contextValues}> 
      {children}
    </ThemeContext.Provider>
);
};

There we go, we have another provider in our codebase, let’s wrap our application by it in the AppProviders.

import App from "./App"
import { Theme } from "./styles/Theme"
import {ThemeContextProvider} from './context/ThemeContext"
 
const AppProviders = () => {
  return (
    <ThemeContextProvider>
      <Theme>
        <App />
      </Theme>
    </ThemeContextProvider>
  )
}
 
export default AppProviders

Whole application can consume the context so we can pass changeTheme function to a toggler which I created in App.tsx

import styled from "styled-components"
import {useContext} from "react"
import {ThemeContext, ThemeContextType} from "../context/ThemeContext.tsx"
 
const Wrapper = styled.div`
height: 100vh;
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
background-color: ${props => props.theme.backgroundColor};
color: ${props => props.theme.color}
`
 
const App = () => {
const {changeTheme} = useContext(ThemeContext) as ThemeContextType;
 return (
    <Wrapper>
      <div>Hello World</div>
      <button onClick={changeTheme}>Change Theme</button>
    </Wrapper>
  )
}

The last step is to update Theme.tsx file with theme state from the context.

const Theme = ({children}: ThemeProps) => {
  const { theme } = useContext(ThemeContext) as ThemeContextType
 
  return (
    <ThemeProvider theme={theme === "light" ? themeLight : themeDark}>
       {children}
    </ThemeProvider>
  );
};
 
export default Theme;

Now everything works and our theme is being changed!

Newsletter

If you enjoyed the read, you might consider checking out our FREE daily newsletter. We send short, 3 minute daily emails with 1 tip, 1 challenge and optionally news/tools from web development. Link ->

On this page