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!
Top comments (7)
can you provide the full source code for this project?
Sry bro, I don't have that code anymore.
Thanks I solved it eventually 🙂
also: can you explain how to set the theme on the windows.localstorage in the browser
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.
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
hi! why you wrap your app with 2 themeProviders?