Theming in React with Styled Components
Get started using Styled Theming on top of Styled Components for multi-dimensional CSS theming

Embedded Themed CSS with Styled Components
Styled Components aims to solve the problem of applying CSS to Javascript frameworks on a per-component basis; and this is particularly useful with React. Although this article is primarily aimed at theming with the ecosystem of tools built around Styled Components, it can also serve as an introductory to the package and its capabilities.
The package itself is continuing an upwards trend of adoption, currently on 736k weekly downloads at publish time. It is clear that embedded styles will play an important role in the future of Javascript frameworks, having a number of benefits over stylesheets.
One of the more advanced use cases of the package is theming — allowing your app to support multiple looks and feels. Adopting a light and dark mode will involve theming your app, as would integrating a compact vs cozy layout. We will explore how to do this at scale, along with the tools needed for the job. As well as styled-components
, we will also utilise styled-theming
; a small package designed specifically for defining and managing themes on top of styled-components
.
Setting up Styled Components for Themes
This section documents the critical tools needed to efficiently use styled components.
Syntax Highlighting
As you may know, styled components utilise template literals as a means of defining styles directly into your component files:
// MyComponent.jsxconst Wrapper = styled.div`
display: flex;
padding: 1rem;
`;class MyComponent extends React.Component {
...
Having full-blown styles directly in Javascript is powerful, but by default most IDEs do not recognise that we intend this string to represent CSS. Luckily the community have steadily been bringing support to the major IDEs:
- Atom: Install the
language-babel
package and restart Atom for syntax highlighting to take effect. - Sublime Text: The best option right now is Naomi, supplying enhanced syntax highlighting for the editor. git clone the package directly into your Packages/ folder and restart Sublime. You can then find
Naomi
syntax options underView -> Syntax
. - Visual Studio Code: Install Language Babel from the Visual Studio Marketplace.
Check out a full list of syntax highlighting options here.
Installing styled-components
We have a couple of options for supporting styled-components
within your React project. You can indeed install the package as a dependency:
yarn add styled-components
From here we can import functions like we would any other package:
import styled from 'styled-components'
However, if you use Create React App where support for babel macros has been available since version 2, we also have the option of installing styled-components
as a babel macro too:
yarn add styled-components.macro --dev
And import functions from the macro like so:
import styled from 'styled-components/macro'
Note: babel-plugin-macros
defines a standard interface for libraries that want to use compile-time code transformation without requiring the user to add a babel plugin to their build system. This speeds up compile time and makes managing babel plugins more streamlined. The feature has been picking up steam recently, on around 2M weekly downloads at the time of writing.
Styled theming
styled-theming
gives us a means to more effectively manage themes as they grow (this is the package in its entirety; ~300bytes minified). Install it with the following:
yarn add styled-theming
Let’s next explore styled-theming
and the benefits it brings to the table in conjunction with styled-components
.
Exploring Styled Theming
Why use styled-theming
on top of styled-components
beyond just taking my word for it? It gives us a means to effectively scale themes as they grow in complexity by defining names instead of styles. Consider the following example, defining a couple of properties for a dark and light mode:
...
import theme from 'styled-theming';// define background colours for `mode` themeconst backgroundColor = theme('mode', {
light: '#fafafa',
dark: '#222'
});// define text color for `mode` themeconst textColor = theme('mode', {
light: '#000',
dark: '#fff'
});// apply theming to a styled componentconst Wrapper = styled.div`
background-color: ${backgroundColor};
color: ${textColor}
`;// use styled-components as we would expect in render(), housing <Wrapper /> within a <ThemeProvider />export default function App() {
return (
<ThemeProvider theme={{ mode: 'light' }}>
<Wrapper>
Hello World
</Wrapper>
</ThemeProvider>
);
}
As you can see, styled-theming
allows us to compartmentalise theme properties. Concretely:
- We are not limited to defining every theme option in one file. As demonstrated, we are using the
theme()
utility fromstyled-theming
, that can be used anywhere in your component hierarchy. It is an accepted development style to create component-specific theming within that component’s file, rather than defining the theme in its entirety at the root component. - The same point regarding the component tree can be applied to our
Wrapper
component: Styled components are also defined within their respected component file. This is the beauty of using the package; defining styles where they are used as opposed to importing a stylesheet that a multitude of components may be using, decreasing the complexity of maintaining styles. - Within
<ThemeProvider />
we provide a value for themode
theme.mode
can be referred to as a theme “name”.
Multi dimensional theming
We are not limited to only one theme name — we could define an additional name, let’s say, layout
, that determines how compact the app content is:
...
const padding = theme('layout', {
compact: '0.5rem',
cozy: '1.5rem'
});...
<ThemeProvider theme={{ mode: 'light', layout: 'cozy' }}>
...
The power here lies in the ability for multiple themes to be used together. For example, I may want a compact dark mode, or a cozy light mode, that utilise two separate themes concurrently. Having the ability to manage this is pretty convenient as a developer, and a pretty cool experience for an end-user.
Theme Variants
Along with theme()
, styled-theming
also gives us another utility: theme.variants()
. Wouldn’t it be nice if we could have a concise way to define a range of styles for a component based on props? I could then tailor my button styles by simply passing props, like <Button small />
or <Button medium />
— that will also be fully compatible with my themes.
This is the problem theme.variants()
aim to solve.
As an example of using variants, we may have many button styles to contend with: default
, inverse
, large
, small
, etc. We can provide specific styles for these variations using the appropriately termed “theme variants”.
Here is an example that takes a kind
prop that then determines a font-size
for a button, depending on the given layout
theme:
// defining our `layout` theme properties based on `kind`const buttonFontSize = theme.variants('layout', 'kind', {
small: { compact: '0.9rem', cozy: '1.2rem' },
medium: { compact: '1rem', cozy: '1.3rem' },
large: { compact: '1.1rem', cozy: '1.4rem' },
});// defining our Button styled componentconst Button = styled.button`
font-size: ${buttonFontSize};
`;// configure propTypes and defaultProps for ButtonButton.propTypes = {
kind: PropTypes.oneOf(['small', 'medium', 'large']),
};Button.defaultProps = {
kind: 'medium',
};...// render render() {
return (
<ThemeProvider theme={{ layout: 'compact' }}>
<Button large>
Sign in
</Button>
</ThemeProvider>
);
}
We have given our Button
component prop types and default props, of which kind
will determine the font-size
taken from our theme variants.
Ideas may now be coming into your mind with ways you can apply these tools — light and dark, cozy and compact are just a couple of ways we can coherently manipulate CSS based on a theme.
Here are a few more use cases:
- Accessibility: Do you wish to provide large fonts and a more grey-scale oriented design for visually impaired or colour blind users?
- User customisation: Allow your users to customise their profile page with a main colour that then determines other shades that compliment the UI. A good example of this are Twitter’s customisable user profile options.
- Compatibility: If your audience primarily uses legacy hardware, you could provide a compatibility theme with a limited and fully-featured theme;
fully-featured
providing transitions, keyframe animations and other cutting-edge CSS features, while alimited
theme providing no transitions, transforms, text gradients, etc. - Time of day: You could utilise MomentJS and the user’s local time to determine the time of day, and reflect this with a theme suited for that time: perhaps a
sunrise
,daytime
,dusk
andnight
themes would provide some nice UX customisation.
The <ThemeProvider />
As demonstrated above, your themes must exist within a <ThemeProvider />
component that defines which theme is currently in use by child components. Theme Providers are contextually defined — in other words, we can overwrite a ThemeProvider further down our component tree:
<ThemeProvider theme={{ mode: 'light' }}>
<SideBar />
<ThemeProvider theme={{ mode: 'dark' }}>
<Content>
</ThemeProvider>
</ThemeProvider>
In the above JSX, my <SideBar />
will be inheriting light
theme styles, whereas my main <Content />
will be inheriting dark
theme styles.
This can be useful for the profile page customisation use case, where you may wish to keep other sections of the app with a default style, like your legal, press, or jobs sections.
Theming with Styled Components
Now, with the understanding of styled-theming
, let’s visit some important features of styled-components
that we can utilise in relation to theming, and understand the differences between the two.
styled-components
have a section on their Advanced usage page that documents how to theme your styles without the addition of the styled-theming
package.
This documentation should not be confused with styled-theming
.
The section does however demonstrate how we can use props to pass theme styles through the <ThemeProvider />
, in the form of the following syntax (Can you see how the usage differs from styled-theming
?):
// Define the `theme` propconst theme = {
main: {
color: 'black'
},
...
};// Button styled-component with props.theme propertiesconst Button = styled.button`
color: ${props => props.theme.main.color};
border: 2px solid ${props => props.theme.main.color};
`;// provide default theme properties for ButtonButton.defaultProps = {
theme: {
main: {
color: 'white'
}
}
}render(
<>
<Button>Un-themed Button</Button> <ThemeProvider theme={theme}>
<Button>Themed Button</Button>
</ThemeProvider>
</>
);
The above example attempts to structure a vanilla styled-component
theme solution in a way we would expect coming from styled-theming
. But there is one key difference in the way we are providing theme values — let’s examine this difference.
Theme name vs theme styling paradigm
So it is now clear that styled-components
and styled-theming
solve the theme problem in different ways:
styled-components
pass theme styles down a Theme Provider.styled-theming
pass theme names down a Theme Provider.
Unlike styled-theming
, we do not have the convenience of theme()
and theme.variants()
, that coherently allow us to define theme properties within any component based on the theme name. Instead, we are relying on props to pass our theme styles down components via the Theme Provider.
Note: Styled Components provide a withTheme()
HOC that injects a theme prop into non-styled React components. Although your theme is primarily used for styling purposes, it can also be referenced for other logic within your components. I do not deem this feature overly useful, but it is there nonetheless if you deem it necessary for a use case — perhaps to determine an image for each theme.
A note on function themes
Passing styles directly down from our root component may appear limiting. styled-components
attempt to overcome this with function themes, allowing us to take a theme from a parent Theme Provider and redefining it to be injected into a child Theme Provider.
This is useful for amending themes as you go further down your component tree, but its use cases are limiting — as a designer you will want to keep your theming consistent throughout the app, and therefore refrain from too many changes.
Media Templates
Although not strictly theming, media templates are another useful feature that we can take advantage of to optimise the syntax when defining media queries. Some might view this as a type of theming, whereby styling for various media sizes represents a theme.
The following example demonstrates how to define a media template to handle styles for various screen sizes:
// configure screen width thresholdsconst ScreenSizes = {
DESKTOP: 992,
TABLET: 768,
PHONE: 576
}const sizes = {
desktop: ScreenSizes.DESKTOP,
tablet: ScreenSizes.TABLET,
phone: ScreenSizes.PHONE
}// iterate through sizes and create a media templateconst media =
Object
.keys(sizes)
.reduce((acc, label) => {
acc[label] = (...args) => css`
@media (max-width: ${sizes[label] / 16}em) {
${css(...args)}
}
`
return acc
}, {});// use media methods with styled-component instead of raw @media queriesconst StyledContent = styled.div`
width: 100%
${media.desktop`
padding: 10px;
`}
${media.tablet`
padding: 15px;
max-width: 700px;
`}
${media.phone`
padding: 20px;
max-width: 900px;
`}
`;export class Content extends React.Component { render() {
return (
<StyledContent>
{ this.children }
</StyledContent>
);
}
}
The above example utilises media templates to adjust the component padding and maximum content width depending on the screen size; a compact mobile design will require less padding and the full screen width, whereas a desktop screen will require a maximum content width and centered content.
There is no mention of how to pass your media
object through components in the styled-components
documentation, but you will undoubtedly want to take advantage of it with styled-theming
when defining styles on a per-component basis. Although you could embed media
into <ThemeProvider />
as another theme name, you may wish to set up your own Context Provider to pass it down, keeping the utility separate from your themes.
Where to go From Here
Styled Components is now adopted as a critical tool for large scale apps, and will not be going away anytime soon. If only for restructuring your CSS without theming, the package may be a great step forward for making your code more maintainable. Not having to manage class names in and of itself will eliminate the problem of unused styles, while keeping what you have defined down to a minimum.
There are many features we have not touched on here, including support for Typescript, Jest testing, and more — in fact the sheer amount of features and support for styled-components
is what makes the package very capable for both small teams and large organisations.
For raw documentation, start off with the documentation home page.
Continue reading my next article to learn how to use styled-components
and styled-theming
to toggle between a dark mode in your React apps: