Color Modes

While there are many different ways to handle theming in a web application, there are just as many ways to handle color schemes. A common feature of modern web applications is including an optional dark mode. Usually a dark mode feature includes changes to the colors of a site without changing other typographic or layout styles. This guide will walk through one approach (the one used on this site) that includes multiple color modes that can be changed by the end user.

Since the only styles that change between these color modes are the colors themselves, the different color palettes are stored in the theme.colors object. If you look at this demo theme file (opens in a new tab), you'll see that it includes a nested colors.modes object for the different color schemes. Each color mode object matches the same shape as the base default colors and uses a simple naming abstraction for setting colors for the text, background, links, and other styles. This demo themes colors object looks something like the following:

const colors = {
  text: '#000',
  background: '#fff',
  primary: '#00f',
  secondary: '#00a',
  gray: '#eee',
  lightgray: '#fafafa',
  modes: {
    dark: {
      text: '#fff',
      background: '#000',
      primary: '#0cf',
      secondary: '#f0e',
      gray: '#222',
      lightgray: '#111',
    },
    // other color modes...
  },
}

If you use my theme library you will get something that looks slightly different in terms of there will be multiple files with a main index.js file.

Using the theme colors

By default the base colors are picked up by other components using Styled System. For example, the root layout component uses Emotion's Global component to set text and background colors.

// example
<Global
  styles={css({
    body: {
      color: 'text',
      bg: 'background',
    },
  })}
/>

Adding color mode state

The root layout component also uses React state to cycle through the different color modes and creates a new theme object based on state. There are several different ways to store this state persistently, but this is outside of the scope of this guide. The following is an example of one way to set up the color mode state in your app.

import React, { useState } from 'react'
import merge from 'lodash.merge'
import get from 'lodash.get'
// the full theme object
import baseTheme from './theme'
 
// options for different color modes
const modes = [
  'light',
  'dark',
  // more than two modes can follow...
]
 
// merge the color mode with the base theme
// to create a new theme object
const getTheme = mode =>
  merge({}, baseTheme, {
    colors: get(baseTheme.colors.modes, mode, baseTheme.colors),
  })
 
export default props => {
  // state for changing modes dynamically
  const [mode, setMode] = useState(modes[0])
  const theme = getTheme(mode)
 
  return (
    <ThemeProvider theme={theme}>{/* application elements */}</ThemeProvider>
  )
}

Next you'll want to add the UI controls for changing between color modes. With this basic approach, you should be able to add as many different color modes to your site as you wish. Be sure that all components within your application are using color values from the theme (not hard-coded values) in order for this to work as expected.

There are other ways to achieve a similar effect – this just demonstrates one approach. For a different approach to persisting data, you may want to look into the prefers-color-scheme (opens in a new tab) media query, but it only handles binary light or dark modes. You might also want to look into CSS Custom Properties (opens in a new tab), which can be defined as inline styles, but be aware that they are not supported in IE11.