How to create a Theme in React/Typescript (Context API) with styled-components
Tags: react, typescript, tutorial, javascript
Published: 2021-09-22 17:33:24

Hello everyone, in this super fast tutorial, I'll teach you how to create a theme in a React/Typescript application with [styled-components](https://styled-components.com/), let's go?

## **Create a new project**
- Create project with create-react-app:

```bash
yarn create react-app *your-application-name* --template=typescript
```

## **styled-components**

- Add styled-components to the project:
```bash
yarn add styled-components
```
- And your types on the development mode:
```bash
yarn add @types/styled-components -d
```


## Create theme variables and ThemeProps interface:

`/src/styles/themes.ts`
```javascript
export interface ThemeProps {
  background: string;
  text: string;
}

export const darkTheme: ThemeProps = {
  background: 'var(--dark-background)',
  text: 'var(--dark-text)',
};

export const lightTheme: ThemeProps = {
  background: 'var(--light-background)',
  text: 'var(--light-text)',
};
```

## Create a global styles with `createGlobalStyle` from styled-components and set the theme variables:

`/src/styles/global.ts`: 

```javascript
import { createGlobalStyle, withTheme } from 'styled-components';
import { ThemeProps } from './themes';

type GlobalThemeProps = {
  theme: ThemeProps;
};

const globalStyle = createGlobalStyle`
  :root {
    //dark-mode
    --dark-background: #1A1B27;
    --dark-text: #F5F5F7;

    //light-mode
    --light-background: #f2f2f2;
    --light-text: #2E0509;

  }

  * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    outline: 0;
  }

  body  {
    -webkit-font-smoothing: antialiased;
    height: 100vh;
    width: 50vw;
    margin: 0 auto;
    background-color: ${({ theme }: GlobalThemeProps) => theme.background};
    display: flex;
    justify-content: center;
    align-items: center;
  }

  h1 {
    font-size: 3.375rem;
    color: ${({ theme }: GlobalThemeProps) => theme.text};
  }

`;

export default withTheme(globalStyle);
```

In the h1 and body styles we can already see an example of applying the themes, but we still need to create the context and hook function. 

## Create a Theme context:

`/src/contexts/ThemeContext/index.tsx`: 

```javascript
import React from 'react';
import { ThemeProvider } from 'styled-components';
import { useThemeMode } from '../../hooks/useThemeMode';
import { lightTheme, darkTheme } from '../../styles/themes';

const ThemeContext: React.FC = ({ children }) => {
  const { theme } = useThemeMode();

  const themeMode = theme === 'dark' ? darkTheme : lightTheme;

  return <ThemeProvider theme={themeMode}>{children}</ThemeProvider>;
};

export default ThemeContext;
```
[Context](https://reactjs.org/docs/context.html) they are ways to save the value of states outside the component's scope. 

## Create a hook function to switch the theme:

`/src/hooks/useThemeMode.ts`: 

```javascript
import { useEffect, useState } from 'react';

export const useThemeMode = () => {
  const [theme, setTheme] = useState('dark');

  const setMode = (mode: string) => {
    window.localStorage.setItem('theme', mode);
    setTheme(mode);
  };

  const themeToggler = () => (theme === 'dark' ? setMode('light') : setMode('dark'));

  useEffect(() => {
    const localTheme = window.localStorage.getItem('theme');
    localTheme && setTheme(localTheme);
  }, []);

  return { theme, themeToggler };
};

export default useThemeMode;
```
Here we are creating a theme state, fetching its initial value from the browser's storage and changing its value when the `setMode` function is called. 


## Create a TogglerButton component to use hook function and switch the theme when clicked:

`/src/components/TogglerButton/index.tsx`: 
```javascript
import { HiMoon } from 'react-icons/hi';
import { FaSun } from 'react-icons/fa';

import * as S from './styles';

interface ThemeTogglerProps {
  themeToggler: () => void;
}

function TogglerButton({ themeToggler }: ThemeTogglerProps) {
  return (
    <S.Container>
      <label htmlFor="checkbox" className="switch">
        <input
          id="checkbox"
          type="checkbox"
          onClick={themeToggler}
          onChange={() => false}
          checked={window.localStorage.getItem('theme') === 'light'}
        />
        <S.Icons className="slider round">
          {window.localStorage.getItem('theme') !== 'light' ? (
            <>
              <HiMoon style={{ marginLeft: '6.3px', height: '10px' }} />
            </>
          ) : (
            <>
              <FaSun size={0} style={{ marginLeft: '41px', height: '10px' }} />
            </>
          )}
        </S.Icons>
      </label>
    </S.Container>
  );
}

export default TogglerButton;

```

- When creating this component we use an outside library for the icons, so we need to install that too, it's called [React Icons](https://react-icons.github.io/react-icons/):
```bash
yarn add react-icons
```

## And create the styles to TogglerButton:

`/src/components/TogglerButton/styles.ts`: 
```css
import styled from 'styled-components';

export const Container = styled.div`
  .switch {
    position: relative;
    display: inline-block;
    width: 4rem;
    height: 1.5rem;
  }

  .switch input {
    opacity: 0;
    width: 0;
    height: 0;
  }

  .slider {
    position: absolute;
    cursor: pointer;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: ${({ theme }) => theme.background};
    -webkit-transition: 0.2s;
    transition: 0.2s;
    box-shadow: 0 0 2px ${({ theme }) => theme.text};
  }

  .slider:before {
    position: absolute;
    content: '';
    height: 14px;
    width: 14px;
    left: 7px;
    bottom: 5px;
    background-color: ${({ theme }) => theme.background};
    -webkit-transition: 0.2s;
    transition: 0.2s;
  }

  input:checked + .slider {
    background-color: ${({ theme }) => theme.background};
  }

  input:checked + .slider:before {
    -webkit-transform: translateX(35px);
    -ms-transform: translateX(35px);
    transform: translateX(35px);
  }

  /* Rounded sliders */
  .slider.round {
    border-radius: 34px;
  }

  .slider.round:before {
    border-radius: 50%;
  }
`;

export const Icons = styled.span`
  width: 100%;
  display: flex;
  justify-content: space-between;
  top: 25%;
  align-items: center;

  svg {
    color: ${({ theme }) => theme.text};
    z-index: 11;
  }
`;
```

Here in this style we can see the theme usage in some properties.

Like in this code snippet below:
```css
.slider {
     position: absolute;
     cursor: pointer;
     top: 0;
     left: 0;
     right: 0;
     bottom: 0;
     background-color: ${({ theme }) => theme.background};
     -webkit-transition: 0.2s;
     transition: 0.2s;
     box-shadow: 0 0 2px ${({ theme }) => theme.text};
}
```

We are changing the background-color according to the theme's background variable. 

## And finally, we need to add the Context, ThemeProvider, GlobalStyle and ThemeToggler components to App.tsx: 

`/src/App.tsx`: 
```javascript
import { ThemeProvider } from 'styled-components';
import TogglerButton from './components/TogglerButton';
import GlobalStyle from './styles/global';
import ThemeContext from './contexts/ThemeContext';
import { lightTheme, darkTheme } from './styles/themes';
import useThemeMode from './hooks/useThemeMode';

function App() {
  const { theme, themeToggler } = useThemeMode();
  const themeMode = theme === 'light' ? lightTheme : darkTheme;

  return (
    <ThemeContext>
      <ThemeProvider theme={themeMode}>
        <GlobalStyle />
        <header>
          <TogglerButton themeToggler={themeToggler} />
        </header>
        <h1>{theme}</h1>
      </ThemeProvider>
    </ThemeContext>
  );
}

export default App;
```

Run `yarn` and then `yarn start` in your terminal and it's done! 

Result:
<img width="100%" style="width:100%" src="https://media3.giphy.com/media/bbeo2AFuBRAZoFproe/giphy.gif?cid=790b7611a8e01a2adc58a0cc776a910ba59eb3e443b0ae5e&rid=giphy.gif&ct=g">

------

if you want to add more colors, you need to set it in the `global.ts` file and then reference it to a variable in the `themes.ts` file.

The goal with this article was to make a more direct tutorial, but any questions just send there in the comments that, I'll be answering. If you need more references, I have some examples of usage in repositories on my [Github](https://github.com/ViniciusmDias).
That's it for today, guys, I hope you enjoyed the article and that it can help you and your team in some way.

Enjoy!