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

Vinicius Monteiro Dias
4 min readSep 23, 2021

Originally posted on Dev.to, check it out here

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

styled-components

Add styled-components to the project:

yarn add styled-components

And your types on the development mode:

yarn add @types/styled-components -d

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)’,
};

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);

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;

Contexts 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;

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;

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

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;
}
`;

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};
}

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;

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

Result:

gif in white and dark mode

— — —

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!

--

--

Vinicius Monteiro Dias

I am a front-end developer that focuses on HTML, CSS and JavaScript as this specifically applies to the UI.