React theming with emotion
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 following packages:
emotionfor the base css in js functionalityreact-emotionfor generating "styled components"emotion-themablefor theming support
npm install emotion react-emotion emotion-themable
Emotion also supports a babel preset that will do some of the transformations at build time.
This will make the final bundle size smaller, however it does not work with create-react-app so I decided not to use it here.
You can read more about it in the Emotion 8 launch blog post.
High level picture
For this page we will need 4 components:
- the
Skyfor the background - the
CelestialObjectfor the sun or the moon - a
Titleto show some instructions - an
Appto 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
themeprop
<Sky theme={dayTheme}/>
- Using a
ThemeProvider
<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
stateof whatthemeis currently active - toggles the
statewhen the sun/moon is clicked - uses the
ThemeProviderto make sure the theme is passed to all children
import React from 'react';
import { render } from 'react-dom';
import { ThemeProvider } from 'emotion-theming';
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.
This is almost identical to styled-components syntax, however with emotion you can also choose to use object literals if you prefer (like glamorous does).
Notice how we use styled('div') instead of styled.div. This is only becasue we don't have the babel preset active. If we did, styled.div would also be supported.
import styled from 'react-emotion'
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.
Emotion support SCSS-style nesting, as you can see with the :hover attribute used below.
import styled from 'react-emotion'
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.
import { injectGlobal } from 'react-emotion';
injectGlobal`
html, body, #root {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
`
That's it!
You can edit with this code live on CodeSandbox: