How to create a Theme in React/Typescript (Context API) with styled-components
Vinicius Dias
Posted on September 22, 2021
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);
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;
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;
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!
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!
Posted on September 22, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
September 22, 2021