styled-theming

Create themes for your app using styled-components

If you set out to write a theme for your entire app, you need to set yourself up to manage all the complexity that it can add.

styled-components makes it easy to get started creating themes.

const Button = styled.button`
  color: ${props => props.theme.main};
`;

const theme = {
  main: 'mediumseagreen',
};

<ThemeProvider theme={theme}>
  <Button>Themed</Button>
</ThemeProvider>

Passing CSS values down this way works for a bit. But when you start using this in hundreds of places in your app, you'll notice it gets a bit painful.

The problem is that we're back to having a separate set of styles that lives at the top of our app in giant objects.

const themeOne = {
  buttonDefaultColor: "#123456",
  buttonDefaultHoverColor: "#123456",
  buttonDefaultActiveColor: "#123456",
  buttonDefaultFocusColor: "#123456",
  buttonPrimaryColor: "#123456",
  buttonPrimaryHoverColor: "#123456",
  buttonPrimaryActiveColor: "#123456",
  buttonPrimaryFocusColor: "#123456",
  // ...
};

const themeTwo = {
  buttonDefaultColor: "#123456",
  buttonDefaultHoverColor: "#123456",
  buttonDefaultActiveColor: "#123456",
  // ...
};

And our component styles have tons of lookup functions:

const Button = styled.button`
  color: ${props => props.theme.buttonDefaultColor};
  background-color: ${props => props.theme.buttonDefaultBackgroundColor};
  border-color: ${props => props.theme.buttonDefaultBorderColor};

  &:hover {
    color: ${props => props.theme.buttonDefaultHoverColor};
    background-color: ${props => props.theme.buttonDefaultHoverBackgroundColor};
    border-color: ${props => props.theme.buttonDefaultHoverBorderColor};
  }

  &:active {
    color: ${props => props.theme.buttonDefaultActiveColor};
    background-color: ${props => props.theme.buttonDefaultActiveBackgroundColor};
    border-color: ${props => props.theme.buttonDefaultActiveBorderColor};
  }

  &:focus {
    color: ${props => props.theme.buttonDefaultFocusColor};
    background-color: ${props => props.theme.buttonDefaultFocusBackgroundColor};
    border-color: ${props => props.theme.buttonDefaultFocusBorderColor};
  }
`;

Not to mention how weird it gets trying to manage variants:

const Button = styled.button`
  color: ${props => {
    if (props.kind === "default") return props.theme.buttonDefaultColor;
    if (props.kind === "primary") return props.theme.buttonPrimaryColor;
    if (props.kind === "success") return props.theme.buttonSuccessColor;
    if (props.kind === "warning") return props.theme.buttonWarningColor;
    if (props.kind === "danger") return props.theme.buttonDangerColor;
  }};
  // ...
`;

Button.defaultProps = {
  kind: "default",
};

You also have to be careful about how you abstract all of this code so that you are not accidentally running tons of extra code during a render cycle or you can introduce a performance problem.

Instead of all of this, what if we reverse the relationship between themes and abstracted all the repetition into helpers?

Introducing styled-theming

styled-theming makes it easier to manage themes by allowing you to declare your themes alongside your components instead of at the top of your app.

Instead of passing down values from the root of your app, you pass down names instead.

<ThemeProvider theme={{ mode: "light" }}>

Then when you declare your components, you can use the theme helper.

import theme from "styled-theming";

const backgroundColor = theme("mode", {
  light: "#fff",
  dark: "#000",
});

const Button = styled.button`
  background-color: ${backgroundColor};
`;

theme() is a tiny little function that returns another function which you can use as a value in styled-components. It looks up the correct value using the theme prop you provided to <ThemeProvider>.

You're encouraged to create your own helper functions that wrap theme() to make it easier to declare your themes. Just make sure that you're doing as much work as possible ahead of time (or only once).

Variants

In addition to the theme() function, there's also a theme.variants() function to help you declare variantions of the same component based on a prop.

import theme from "styled-theming";

const backgroundColor = theme.variants("mode", "kind", {
  default: { light: "#123456", dark: "#123456" },
  primary: { light: "#123456", dark: "#123456" },
  success: { light: "#123456", dark: "#123456" },
  danger: { light: "#123456", dark: "#123456" },
  warning: { light: "#123456", dark: "#123456" },
});

const Button = styled.button`
  background-color: ${backgroundColor};
`;

Button.propTypes = {
  kind: PropTypes.oneOf(["default", "primary", ...]),
};

Button.defaultProps = {
  kind: "default",
};

Multi-dimensional theming

Theming can sometimes happen across multiple dimensions:

This is why the first parameter to theme() and theme.variants() exists.

theme("mode", {...})

This matches against the object that you've passed to <ThemeProvider> so you can have multiple of them.

theme("mode", { light: ..., dark: ... });
theme("size", { normal: ..., compact: ... });

<ThemeProvider theme={{ mode: 'dark', size: 'compact' }}>

In closing

This library is really a pattern moreso than code. You and Jason Miller will be happy to know this library is like 300 bytes of code minified.

It's also important to note that this library isn't tied to styled-components in any way. Any other library implementing a theming API where functions as CSS values receive an object with a `theme` property will work.

Now go install this hot shit:

yarn add styled-components styled-theming

Oh, and give me a star. I like stars. I am a star. Don't tell me I'm not a star because I am a star.