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 functionality
  • glamorous 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 what theme 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 the theme
  • 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: