React theming with styled components
What we'll build
A day / night theme switcher based on Google Creative Lab's Night and Day project.
Packages required
This code sample is based on create-react-app
.
Once you have the base project scaffolded, install the styled-components
package:
npm install styled-components
High level picture
For this page we will need 4 components:
- the
Sky
for the background - the
CelestialObject
for the sun or the moon - a
Title
to show some instructions - an
App
to put it all together
The root component will basically be just:
<Sky>
<Title>{this.state.title}</Title>
<CelestialObject
onClick={() => this.handleClick()}>
</CelestialObject>
</Sky>
When the user will click the big circle, we will pass in a different theme to the components.
Applying the theme
There are two ways to pass a theme:
- Manually, using the
theme
prop - Using a
ThemeProvider
<Sky theme={dayTheme}/>
<ThemeProvider theme={dayTheme}>
<Sky/>
</ThemeProvider>
We will use the ThemeProvider
which will make the theme
property available to all components in our React app.
App component
The main App
component will handle several things
- keeps the
state
of whattheme
is currently active - toggles the
state
when the sun/moon is clicked - uses the
ThemeProvider
to make sure the theme is passed to all children
import React from 'react';
import { render } from 'react-dom';
import { ThemeProvider } from 'styled-components';
import './globalStyles';
import Sky from './Sky';
import CelestialObject from './CelestialObject';
import Title from './Title';
// Define our themes: one for day and one for night
const dayTheme = {
skyColor: '#37d8e6',
celestialObjectColor: '#ffdd00',
celestialObjectBorderColor: '#f1c40f'
};
const nightTheme = {
skyColor: '#2c3e50',
celestialObjectColor: '#bdc3c7',
celestialObjectBorderColor: '#eaeff2'
}
// Main app
class App extends React.Component {
constructor(props) {
super(props);
// Initial state: day time!
this.state = {
isDay: true,
theme: dayTheme,
title: 'Click the Sun to switch the theme'
};
}
handleClick() {
// Toggle day / night on click
const isDay = !this.state.isDay;
this.setState({
isDay: isDay,
theme: isDay ? dayTheme : nightTheme,
title: isDay ? 'Now click the Sun' : 'Now click the Moon'
});
}
render() {
// Wrap the entire content in a <ThemeProvider>
return <ThemeProvider theme={this.state.theme}>
<Sky>
<Title>{this.state.title}</Title>
<CelestialObject
onClick={() => this.handleClick()}>
</CelestialObject>
</Sky>
</ThemeProvider>
}
}
render(<App />, document.getElementById('root'));
Sky component
This basically just shows a light blue or dark blue background, based on the active theme.
Things to notice:
- we can access
skyColor
from the theme simply by readingprops.theme.skyColor
- the syntax is just plain CSS, mixed in with some javascript (see template literals)
import styled from 'styled-components';
const Sky = styled.div`
height: 100%;
width: 100%;
background-color: ${props => props.theme.skyColor}
`;
export default Sky;
CelestialObject component
This is our Sun and Moon! Actually, it's just a circle whose color and border change based on the theme.
Also, on hover
the border width changes.
This is achieved using the SCSS-style nested &:hover
selector.
import styled from 'styled-components';
const CelestialObject = styled.div`
height: 250px;
width: 250px;
border-radius: 100%;
padding: 20px;
margin: auto;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: ${props => props.theme.celestialObjectColor};
border: 10px solid ${props => props.theme.celestialObjectBorderColor};
&:hover {
border: 20px solid ${props => props.theme.celestialObjectBorderColor};
}
`;
export default CelestialObject;
Global styles
To make sure the sky fills the whole page, we need to set the width
and height
of the body
to 100%.
This is a great use case for applying global styles.
This is achieved using the injectGlobal
helper.
import { injectGlobal } from 'styled-components';
injectGlobal`
html, body, #root {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
`
That's it!
You can edit with this code live on CodeSandbox: