Gatsby Change Color Mode With Theme UI

May 17th 2020

Theme UI is a JavaScript library for creating constraint-based user interfaces. It has a number of advantages over vanilla CSS because of the dynamic JavaScript layer that allows intelligent component styling. The Theme UI library also comes with some React hooks that makes theming in React a breeze. Today we’re going to walk through dynamically changing the color mode of your theme on the fly by clicking a button so cycle through all of your theme’s color modes.

Gatsby Plugin Theme UI

Theme UI requires a custom theme object that will be passed in as the value to the <ThemeProvider> which wraps your gatsby project. Fortunately, there is a gatsby plugin gatsby-plugin-theme-ui that handles the context and theme provider for us. Once installed all we have to do is supply an exported object in our ./src/gatsby-plugin-theme-ui/index.js file and the component shadowing will use our theme object that we export as the context value for the <ThemeProvider> component. We can first install the plugin:

yarn add gatsby-plugin-theme-ui

Once installed we need to tell gatsby to use this plugin by opening up our gatsby-config.js file and adding:

module.exports = {
  ...
  plugins: [
    `gatsby-plugin-theme-ui`
  ]
  ...
}

Now we can also create a file located at ./src/gatsby-plugin-theme-ui called index.js and inside we can export an object:

export default {
  fonts: {
    body: 'system-ui, sans-serif',
    heading: '"Avenir Next", sans-serif',
    monospace: 'Menlo, monospace',
  },
  colors: {
    text: '#000',
    background: '#fff',
    primary: '#33e',
  },
}

This will export our theme object so our gatsby site will use this object as our theme specification values in our project. You can learn more about theme specification for declaring your theme object here: https://theme-ui.com/theme-spec.

Theme UI Color Modes

Theme UI allows for Color Modes. Color modes can be used to create a user-configurable dark mode or any number of other color modes. You can create a color mode by adding a modes object to our colors property of our theme definition. In our example let’s add a dark mode which is the inverse of the “default” light mode:

export default {
  fonts: {
    body: 'system-ui, sans-serif',
    heading: '"Avenir Next", sans-serif',
    monospace: 'Menlo, monospace',
  },
  colors: {
    text: '#000',
    background: '#fff',
    primary: '#33e',
    modes: {
      dark: {
        text: '#fff',
        background: '#000',
        primary: '#cc1'
      }
    }
  },
}

We can also set the default name of our initial color mode so we can reference it in our setColorMode hook which we will define later. We can set the default name by adding initialColorModeName property to our theme definition:

export default {
  initialColorModeName: 'light',
  fonts: {
    body: 'system-ui, sans-serif',
    heading: '"Avenir Next", sans-serif',
    monospace: 'Menlo, monospace',
  },
  colors: {
    text: '#000',
    background: '#fff',
    primary: '#33e',
    modes: {
      dark: {
        text: '#fff',
        background: '#000',
        primary: '#cc1'
      }
    }
  },
}

Now we essentially have “two” color modes in our theme: a light color mode and a dark color mode. We can toggle between these two modes by opening our layout.js file and importing the useColorMode hook from the theme-ui library. Initializing the hook we will obtain the current colorMode which will default to the initialColorModeName that we set in our ./src/gatsby-plugin-theme-ui/index.js file which evaluates to light. We can toggle the color mode between our default light mode and our dark mode by running a ternary conditional:

const nextColorMode = colorMode === 'light' ? 'dark' : 'light'

We can use the nextColorMode variable and the setColorMode hook to toggle between the color modes:

/**
 * Layout component that queries for data
 * with Gatsby's useStaticQuery component
 *
 * See: https://www.gatsbyjs.org/docs/use-static-query/
 */

import React from "react"
import PropTypes from "prop-types"
import { useStaticQuery, graphql } from "gatsby"
import { useColorMode } from 'theme-ui'

import Header from "./header"

const Layout = ({ children }) => {
  const data = useStaticQuery(graphql`
    query SiteTitleQuery {
      site {
        siteMetadata {
          title
        }
      }
    }
  `)
  const [colorMode, setColorMode] = useColorMode()

  const nextColorMode = colorMode === 'light' ? 'dark' : 'light'

  return (
    <>
      <h3>Color Mode is: {colorMode}</h3>
      <button onClick={ e => {
        setColorMode(nextColorMode)
      }}>
        Change color mode
      </button>
      <Header siteTitle={data.site.siteMetadata.title} />
      <div>
        <main>{children}</main>
        <footer>
          © {new Date().getFullYear()}, Built with
          {` `}
          <a href="https://www.gatsbyjs.org">Gatsby</a>
        </footer>
      </div>
    </>
  )
}

Layout.propTypes = {
  children: PropTypes.node.isRequired,
}

export default Layout

Adding More Color Modes

We can add more theme color modes to our ./src/gatsby-plugin-theme-ui/index.js file:

export default {
  initialColorModeName: 'light',
  fonts: {
    body: 'system-ui, sans-serif',
    heading: '"Avenir Next", sans-serif',
    monospace: 'Menlo, monospace',
  },
  colors: {
    text: '#000',
    background: '#fff',
    primary: '#33e',
    modes: {
      dark: {
        text: '#fff',
        background: '#000',
        primary: '#cc1'
      },
      tomato: {
        text: '#565656',
        background: 'tomato',
        primary: 'tomato',
      },
      deep: {
        text: 'hsl(210, 50%, 96%)',
        background: 'hsl(230, 25%, 18%)',
        primary: 'hsl(260, 100%, 80%)',
      },
    }
  },
}

Now we have four color modes: our default mode which is the initialColorModeName set to light and the three others defined in our modes object: dark, tomato and deep. Now we can get all color modes by desctructuring the initialColorModeName from the useTheme hook and destructuring all of the modes and storing all of the mode keys in a variable called modeKeys making sure we add in our initialColorModeName as the first key to our array. Next, we can iterate over the array in a <select> element creating <option> elements for each key in our array. Finally, we can bind the onChange handler of our <select> to invoke the setColorMode function provided by the useColorMode hook:

/**
 * Layout component that queries for data
 * with Gatsby's useStaticQuery component
 *
 * See: https://www.gatsbyjs.org/docs/use-static-query/
 */

import React from "react"
import PropTypes from "prop-types"
import { useStaticQuery, graphql } from "gatsby"
import { useThemeUI, useColorMode } from 'theme-ui'

import Header from "./header"

const Layout = ({ children }) => {
  const data = useStaticQuery(graphql`
    query SiteTitleQuery {
      site {
        siteMetadata {
          title
        }
      }
    }
  `)
  const [colorMode, setColorMode] = useColorMode()

  const { theme: { initialColorModeName, colors: { modes } } } = useThemeUI()
  const modeKeys = Object.keys(modes)
  const allModes = [initialColorModeName, ...modeKeys]

  return (
    <>
      <h3>Color Mode is: {colorMode}</h3>
      <select onChange={ e => {
        setColorMode(e.target.value)
      }} value={colorMode}>
        { allModes.map(mode => (
          <option value={mode} key={mode}>{ mode }</option>
        ))}
      </select> 
      <Header siteTitle={data.site.siteMetadata.title} />
      <div>
        <main>{children}</main>
        <footer>
          © {new Date().getFullYear()}, Built with
          {` `}
          <a href="https://www.gatsbyjs.org">Gatsby</a>
        </footer>
      </div>
    </>
  )
}

Layout.propTypes = {
  children: PropTypes.node.isRequired,
}

export default Layout

Result of Multiple Color Modes

We have successfully created a “color mode picker” that toggles between all of the color modes defined in our theme object definition in ./src/gatsby-plugin-theme-ui/index.js file. We can extend this example by adding more themes! Hopefully this helps put a creative spin on your next Gatsby project!