DEV Community

Cover image for How to create a Theme in React/Typescript (Context API) with styled-components
Vinicius Dias
Vinicius Dias

Posted on

How to create a Theme in React/Typescript (Context API) with styled-components

Hello everyone, in this super fast tutorial, I'll teach you how to create a theme in a React/Typescript application with styled-components, let's go?

Create a new project

  • Create project with create-react-app:
yarn create react-app *your-application-name* --template=typescript
Enter fullscreen mode Exit fullscreen mode

styled-components

  • Add styled-components to the project:
yarn add styled-components
Enter fullscreen mode Exit fullscreen mode
  • And your types on the development mode:
yarn add @types/styled-components -d
Enter fullscreen mode Exit fullscreen mode

Create theme variables and ThemeProps interface:

/src/styles/themes.ts

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)',
};
Enter fullscreen mode Exit fullscreen mode

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

/src/styles/global.ts:

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);
Enter fullscreen mode Exit fullscreen mode

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:

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;
Enter fullscreen mode Exit fullscreen mode

Context 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:

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;
Enter fullscreen mode Exit fullscreen mode

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:

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;

Enter fullscreen mode Exit fullscreen mode
  • When creating this component we use an outside library for the icons, so we need to install that too, it's called React Icons:
yarn add react-icons
Enter fullscreen mode Exit fullscreen mode

And create the styles to TogglerButton:

/src/components/TogglerButton/styles.ts:

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;
  }
`;
Enter fullscreen mode Exit fullscreen mode

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

Like in this code snippet below:

.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};
}
Enter fullscreen mode Exit fullscreen mode

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:

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;
Enter fullscreen mode Exit fullscreen mode

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

Result:


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.
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!

Top comments (7)

Collapse
 
fclante profile image
Frey Clante

can you provide the full source code for this project?

Collapse
 
viniciusmdias profile image
Vinicius Dias

Sry bro, I don't have that code anymore.

Collapse
 
fclante profile image
Frey Clante

Thanks I solved it eventually 🙂

Collapse
 
fclante profile image
Frey Clante

also: can you explain how to set the theme on the windows.localstorage in the browser

Collapse
 
viniciusmdias profile image
Vinicius Dias

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

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

This happens when function themeToggler is called.
Put this function in the Switcher button.

Collapse
 
joshua372 profile image
Josue Muñoz

hi problem in contexts/ThemeContext/index.tsx
const ThemeContext: React.FC = ( { children })

error property 'children' does not exist on type {}

and App.tsx
return (

error Type ' children: Element ' has no properties in common with type 'IntrinsincAttributes'

What is the problem ?

Thank you

Collapse
 
guyeilon profile image
Guy Eilon

hi! why you wrap your app with 2 themeProviders?