This post spins off of the previous post's interest in extending themes. Because I ended up reworking both how garden handles themes (both their generation and passing them to different theme providers), I spun this off into its own post.

color tokens and generating themes

To start, the minimum number of color tokens needed for garden is two: text and background color. Rather than go monochromatic, we also want a primary color. These should live in an easily configurable place and all components necessary for garden to work should only use these colors.

We'll add a theme object to config.js. It will expect at least a default object with keys corresponding to the theme object, so we'll give it a color object with the necessary colors. We define our themes this way so that, in the future, we can continue extending the theme with other design tokens besides colors. We can also provide config any number of other objects to generate themes. Here's an example with a default and a dark theme:

...
theme: {
default: {
colors: {
text: '#2d2a24',
background: '#fdfdfd',
primary: '#b7e2d8'
}
},
dark: {
colors: {
text: '#ececec',
background: '#2c2c2c',
primary: '#888'
}
}
}

This would be really easy to accomplish with color modes, but we have to also pass the theme to the styled components provider. We'll handle this very similarly to how theme-ui handles it, though.

Next, we need to make sure the theme object supports color overrides. Garden does this using a function called genTheme that takes colors as an argument and returns a theme object.

function genTheme({ colors } = { colors: defaultColors }) {
...theme
};

Note here that I also have a set of default color values. You could run the function without any arguments and still get a perfectly good theme object.

toggle between modes

Heavily influenced by theme-ui's color modes documentation, garden takes the themes specified in the config and cycles through them. At the App.js level, we generate an object that contains a list of themes defined in the config, and an array of theme objects generated by those themes. Here, I'm calling them 'modes' just to differentiate them (in name alone) from themes.

const { themes } = siteConfig;
const modeList = {
modes: Object.keys(themes),
modeThemes: Object.keys(themes).map(theme => {
return genTheme({ ...themes[theme] });
})
};

In the functional component, we use a hook to setup the default state, and a toggle function to cycle among nodes.

const [currentMode, setMode] = useState('default');
const { modes, modeThemes } = modeList;
const toggleMode = () => {
const index = modes.indexOf(currentMode);
const next = modes[(index + 1) % modes.length];
setMode(next);
};

Wrapping the whole of the app in a context provider allows us to pass the down the mode state and the toggle function.

<ThemeContext.Provider
value={{
currentMode,
toggleMode
}}
>
{...app}
</ThemeContext.Provider>

As an aside, I ended up liking this as a feature rather than the usual sun/moon icon for a few reasons:

  1. more than one theme
  2. clear what theme is on (rather than relying on iconography)
  3. no need for svg imports

resources

up next