Theming with Styled Components

Posted on March 03, 2020

A guide on theming your web applications with Styled Components.

Why should you theme?

  • Themes help create an identity for your application. Themes can help abstract all usages of a particular color, length or shadow to a single place so that all use cases can follow it.
  • It’s easier to change. However solid your current app is, there is surely coming that dreaded moment when designer is going to swap those two colors around. If you have colors spread out all over your application, then you are going to have hell with it. If you think you can search and replace, do consider all ways in which colors can be represented in CSS.

How to Theme?

Styled Components comes in build in with a ThemeProvider to help you with this cause. Theme Provider is similar to a React Context Provider (in the sense that it is one). You have to wrap your content with a ThemeProvider and you can get started:

1import { ThemeProvider } from 'styled-components';
2
3function App() {
4 return (
5 <ThemeProvider theme={{}}>
6 <p>All the other stuff goes here...</p>
7 </ThemeProvider>
8 );
9}

Theme can be any simple POJO. Consider:

1const theme = {
2 colors: {
3 primary: `yellow`,
4 secondary: `red`,
5 }
6}
7return (
8 <ThemeProvider theme={theme}>
9 </ThemeProvider>
10);

How to access a theme?

A theme can be accessed in a styled component with props.theme usage. The only consideration being that where this Button is rendered should be wrapped somewhere in it’s parent with ThemeProvider that provides it’s theme.

const Button = styled(Button)`
background-color: ${props => props.theme.primary};
`;

But what if it is not wrapped with a ThemeProvider? If you believe in creating components that would work even without it’s context parent, then you would want to give it some theme as defaultProps.

1const Button = styled(Button)`
2 background-color: ${props => props.theme.colors.primary};
3`;
4
5Button.defaultProps = {
6 theme: {
7 colors: {
8 primary: 'transparent',
9 },
10 },
11};

Nesting Themes

Multiple Theme Providers can be nested within one another. A component will pick up the theme from nearest Theme Provider it is nested within.

1const Button = styled.button`
2 background-color: ${props => props.theme.colors.primary};
3`;
4const theme = {
5 colors: {
6 primary: `yellow`,
7 }
8}
9return (
10 <ThemeProvider theme={theme}>
11 <Button>Primary Button</Button>
12 <ThemeProvider theme={specialTheme}>
13 <Button>Special Button</Button>
14 </ThemeProvider>
15 </ThemeProvider>
16);

Styled Components packs another trick in it’s sleeve with nested Theme Providers. Styled Components delivers the current theme that it receives from it’s parent as an argument which you can use to manipulate or add values to the theme.

1import Navbar from "./Navbar";
2
3const theme = (currentTheme) => ({
4 ...currentTheme,
5 navbar: {
6 height: "6rem",
7 },
8});
9
10return (
11 <ThemeProvider theme={theme}>
12 <ThemeProvider theme={specialTheme}>
13 <Navbar />
14 </ThemeProvider>
15 </ThemeProvider>
16);

Variants

Variants are how we can create components that adapt based on props. You might have seen these in UI libraries:

<Button primary>Primary Button</Button>
<Button secondary>Secondary Button</Button>

Traditional Way

With styled-components, you can adapt based on props.

1const Button = styled.button`
2 ${props => props.primary && `
3 background-color: ${props.theme.colors.primary};
4 `}
5 ${props => props.secondary && `
6 background-color: ${props.theme.colors.secondary};
7 `}
8`;

Styled Theming

The traditional way of building variants are a pain on scale as you can imagine. Especially if you are building a design system.

Styled Components family has a library called styled theming. It has an easier API for creating and maintaining variant based styles. For eg. To create a button that would be different in light and dark mode:

1import styled, {ThemeProvider} from 'styled-components';
2import theme from 'styled-theming';
3
4const backgroundColor = theme('mode', {
5 light: '#f1c40f',
6 dark: '#f39c12',
7});
8
9const Button = styled.div`
10 background-color: ${backgroundColor};
11`;
12
13export default function App() {
14 return (
15 <ThemeProvider theme={{ mode: 'light' }}>
16 <Button>
17 Primary Button
18 </Button>
19 </ThemeProvider>
20 );
21}

Okay, but what if we need to create a secondary variant of this? That’s where the variants functions comes in to play.

1import styled, {ThemeProvider} from 'styled-components';
2import theme from 'styled-theming';
3
4const backgroundColor = theme('mode', 'variant', {
5 primary: {
6 light: '#f1c40f',
7 dark: '#f39c12',
8 },
9 secondary: {
10 light: '#2ecc71',
11 dark: '#27ae60',
12 },
13});
14
15const Button = styled.div`
16 background-color: ${backgroundColor};
17`;
18
19export default function App() {
20 return (
21 <ThemeProvider theme={{ mode: 'light' }}>
22 <Button variant="primary">
23 Primary Button
24 </Button>
25 <Button variant="secondary">
26 Secondary Button
27 </Button>
28 </ThemeProvider>
29 );
30}

What are some other styled-component magic ✨ you use? For using styled-components with TypeScript, see my post on that.

Buy me a coffeeBuy me a coffee