Theme with styled-components and Typescript

Ethan Nguyen
RBI Tech
Published in
4 min readJul 14, 2020

What is styled-components?

Styled-components is arguably the most common CSS-in-JS library out there (Don’t trust me? Check out their site and their adoption from many organizations, 7.6M/month downloads on NPM 🔥).

One of its advanced use cases for this library is theming. Theming in styled-components allows your app to support multiple design patterns within the same app. Imagine an amazing code editor with no dark mode! Having options to customize the look and feel of your app from the primary colour palette to the typography scale can help our system to capture more personas.

This article will give some breakdown of the basic setup and naming convention for theming your app with styled-components, and how to setup Typescript with styled-components themes

Installation

yarn add styled-components
yarn add @types/styled-components -D

VSCode plugin

If you are using Visual Studio Code, I highly recommend installing this helpful plugin. It will highlight and IntelliSense your syntax for styled-components

Create a declaration file

Firstly, we will need to define how our theme interface looks like. Inside src folder, create styled.d.ts file and extend the existing styled-component DefaultTheme. This will help with IntelliSense and ensure a contract for all available themes.

// styled.d.ts
import 'styled-components';
interface IPalette {
main: string
contrastText: string
}
declare module 'styled-components' {
export interface DefaultTheme {
borderRadius: string
palette: {
common: {
black: string
white: string
}
primary: IPalette
secondary: IPalette
}
}
}

Naming convention

Let’s break down some logic here for the naming convention of theming. As I mentioned earlier, our goal for theming a system is to be able to control the compactness and colour palette. Hence we should define our theme around these styles like border-radius font-weight font-size … and all colour related.

About colour naming, avoid naming variable with actual colour but do name with something likeprimary or secondary. Contradicting with what you see above right? With common colours like white black and grey are the exception since as a design system, those are neutral tones to be used for all purposed design rather than the main colour palette.

We will cover more in-depth on this topic on a later post (will update the link here) about the study of common theme standards within all design systems.

Create a theme

Once we defined our theme interface, we can import DefaultTheme to our theme file and assign all theme values.

// theme.ts
import { DefaultTheme } from 'styled-components'
export const defaultTheme: DefaultTheme = {
borderRadius: '4px',
palette: {
common: {
black: '#222831',
white: '#ffffff'
},
primary: {
main: '#726a95',
contrastText: '#ffffff'
},
secondary: {
main: '#709fb0',
contrastText: '#ffffff'
}
}
}

Theme Provider

Now the easy part, once we got the theme object, import to your root component along with ThemeProvider and wrap your root App. We recommend doing this as early as we can.

import { ThemeProvider } from 'styled-components';
...
<ThemeProvider theme={theme}>
<Button>THEME BUTTON</Button>
</ThemeProvider>

Theme button example

Phew! 😅 All the setup is done. Now let’s take a look at what can we do with our theme from within the component.

import styled from 'styled-components';enum VARIANT {
PRIMARY,
SECONDARY
}
interface IProps {
variant?: VARIANT
}
const Button = styled.button<IProps>`
margin: 8px;
border-radius: ${props => props.theme.borderRadius};
${props => {
switch (props.variant) {
case VARIANT.SECONDARY:
return `
color: ${props.theme.palette.secondary.contrastText};
background-color: ${props.theme.palette.secondary.main};
`;
case VARIANT.PRIMARY:
default:
return `
color: ${props.theme.palette.primary.contrastText};
background-color: ${props.theme.palette.primary.main};
`;
}
}}
`;

How to update the theme

We have been talking about having multiple themes within the App. So how can we switch between themes? Let’s look at this example code

function App() {
const [theme, setTheme] = React.useState(defaultTheme);
const updateTheme = () => {
setTheme(secondaryTheme);
}
return (
<ThemeProvider theme={theme}>
<Button>PRIMARY BUTTON</Button>
<Button
variant={VARIANT.SECONDARY}
onClick={updateTheme}
>
SECONDARY BUTTON
</Button>
</ThemeProvider>
)
}

In this example, we use a simple case of storing the theme within component state and on button click, we will update the variable theme with secondaryTheme. This usage can be extended to useContext or redux where we can store the value of our theme and update anywhere within the app.

How to extend the existing theme

Imagine we just need to update black colour value within the theme object. We can update that by doing so

const newTheme = {
...theme,
palette: {
...theme.palette,
common: {
...theme.palette.common,
black: '#000000'
}
}
}

Or we can use immer to safely update 1 field without mutating the theme

import produce from 'immer'const newTheme = produce(theme, draft => {
draft.palette.common.black = '#000000';
})

Upcoming in this series

Given the flexibility of theming, we can now separate the logic of the styling and rendering component itself. Treating the theme as data, we can apply a JAMstack (JavaScript, APIs, and Markup) approach to design a white-label system that leverages a shared design system. We will go through all the preparation to build a scalable and maintainable system with themes.

  • Building a component library with customizable theme
  • Break down JAMstack with theme
  • White label website builder driven by JAMstack and theme

--

--