React theming with glamorous
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:
glamor
for the base css in js functionalityglamorous
for creating "styled components"
npm install glamorous glamor
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
<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
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 'glamorous';
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:
glamorous
supports passing both an object of static properties and a function to its factory- the function receives the
props
as an arguments, which we destructure here to get just thetheme
- the syntax uses object literals, so we need to camelCase the CSS properties (
background-color
->backgroundColor
)
import glamorous from 'glamorous';
const Sky = glamorous.div({
height: '100%',
width: '100%'
}, ({ theme }) => ({
backgroundColor: 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 glamor
syntax for :hover
, passing it as an object property.
import glamorous from 'glamorous';
const CelestialObject = glamorous.div({
height: '250px',
width: '250px',
borderRadius: '100%',
padding: '20px',
margin: 'auto',
position: 'absolute',
top: '0',
left: 0,
bottom: 0,
right: 0
}, ({ theme }) => ({
backgroundColor: theme.celestialObjectColor,
border: `10px solid ${theme.celestialObjectBorderColor}`,
':hover': {
border: `20px solid ${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.
Notice how we don't even use glamorous
here - we just include glamor
.
import { css } from 'glamor';
css.global('html, body, #root', {
width: '100%',
height: '100%',
padding: 0,
margin: 0
});
That's it!
You can edit with this code live on CodeSandbox: