With the advent of modern component-based frontend frameworks, the need to write CSS that is scoped to a particular component has increased. And as with most problems in the frontend sphere, a variety of solutions have sprung up to address it.
The most common solution is CSS-in-JS, a pattern that involves writing component and app styles (CSS) in JavaScript. There are a number of CSS-in-JS libraries out there today, but perhaps the most popular and enduring one — and the focus of this post — is styled-components.
Styled-components is a CSS-in-JS library that drastically improves developer experience for the modern frontend developer while providing a near-perfect user experience. Aside from giving developers the ability to write component-scoped CSS, styled-components comes with a number of other benefits, including:
- Automatic vendor prefixing
- Unique class names for each styled component
- Easier maintenance of styles — developers can delete or modify styles without affecting other components
Now that we’re properly introduced to the styled-components library, let’s take a look at a basic use case, which involves using the styled API.
The styled
API: Where it all begins
The styled
API allows us to create a StyledComponent
— a component with styles, as the name implies — by using either a regular HTML element or another StyledComponent
as a base.
Let’s take a look at the first method. If we want to make a styled div
element and a styled <h1>
element into React components, we can simply write:
import React from "react"; import styled from "styled-components"; const StyledDiv = styled.div` padding: 1.5rem; background-color: skyblue; ` const StyledH1 = styled.h1` color: blue; `
And with that, we have two React components that we can now use as regular components:
function App() { return ( <StyledDiv> <StyledH1>A styled H1 element</StyledH1> </StyledDiv> );
The result in the browser should look like this:
That’s how easy it is to style our components!
Note: You may have noticed that the
styled
API makes use of backticks (“). This a feature in ES6 called tagged template literals; it’s the same as calling a function.
As I mentioned earlier, we can also create a new StyledComponent
by using other StyledComponent
s as the base. So if we wanted to create another StyledComponent
that has all the styling properties of <StyledDiv>
but with more rounded borders, we could do this:
const StyledRoundedDiv = styled(StyledDiv)` border-radius: 30px; `
So now <StyledRoundedDiv>
inherits all the styles of <StyledDiv>
, but with its own style for adjusting the border-radius
property. With this feature, we can create different components that all inherit styles from a single base component.
The styled
API also allows us to create StyledComponent
s by using JavaScript to style objects (commonly used in JSX). As stated in the documentation, “This is particularly useful when you have existing style objects and want to gradually move to styled-components.”
If we were to recreate our StyledDiv
component using style objects, our code would look like this:
const StyledDiv = styled.div({ padding: "1.5rem", backgroundColor: "skyblue" })
Dynamic styling using React props
Another powerful feature of styled-components is its ability to alter a component’s styles using props. To better illustrate this concept, let’s take a look at the Material-UI React library. We have a <Button>
component that takes in different props, but we will only focus on the variant
and color
props.
With the variant
prop, we can determine whether we want our button to be outlined or just filled with the color specified in the color
prop. With the color
prop, we can specify the color of the outline or the color of the button, depending on which value we pass to the variant
prop.
If we were to implement such a component using styled-components, our code would probably look a lot like this:
const Button = styled.button` border-radius: 4px; padding: 6px 16px; min-width: 64px; text-transform: uppercase; font-weight: 500; font-size: 0.875rem; letter-spacing: 0.02857em; line-height: 1.75; font-family: "Roboto", "Helvetica", "Arial", sans-serif; background-color: ${(props) => backgroundColor(props)}; ${(props) => colorAndBorder(props)} `; const colorToValue = { primary: "#1976d2", secondary: "rgb(220, 0, 78)", }; const colorAndBorder = (props) => { var finalColor = "white"; if (props.variant == "outlined") { if (colorToValue[props.color]) { finalColor = colorToValue[props.color]; } else { finalColor = "black"; } } else if (props.variant == "contained") { if (props.color) { finalColor = "white"; } else { finalColor = "black"; } } return css` color: ${finalColor}; border: ${(prop) => props.variant == "outlined" ? "1px solid " + finalColor : "none"}; `; }; const backgroundColor = (props) => { if (props.variant == "outlined") { return "white"; } else if (props.variant == "contained") { if (props.color) { let color = colorToValue[props.color]; if (color) { return color; } else { return "#e0e0e0"; } } else { return "#e0e0e0"; } } };
From the above code block, you can see that I had base styles for the Button
component and dynamic styles for the background-color
, color
, and border
properties, depending on the props.
Since the styled
and css
APIs make use of tagged template literals, we can “inject” JavaScript (using string interpolation) into our CSS to dynamically style components, which we did using two custom functions: backgroundColor
and colorAndBorder
.
backgroundColor
takes in the StyledComponent
‘s props and returns the suitable background color of the button as a string. The outer function in the Button
component styles can now use that string to style the button.
For the color
and border
properties, however, we used a function that returns a value we can use directly in our Button
‘s styles string instead of just a string to be used by an outer function. This is where the css
helper function comes in.
The css
helper function generates a value that can be used directly in template literal styles, especially when the template literal passed to the css
function contains string interpolation.
We can now use our Button
component like so:
function App() { return ( <> <Button variant="contained" color="primary"> Primary Contained </Button> <Button variant="outlined" color="secondary"> Secondary Outlined </Button> <Button variant="contained"> Default contained </Button> </> ); }
Additional styling methods with the attrs
constructor
styled-components also allows additional props or HTML attributes to be added to a component using the attrs
method. Using attrs
, we can define static props or attributes like the type of an <input>
element as well as dynamic props. Here’s an example of the attrs
method in use:
const Link = styled.a.attrs((props) => ({ href: props.$to, }))` text-decoration: none; color: #000; border: 1px solid #000; padding: 1rem 1.5rem; border-radius: 5px; `; function App() { return ( <> <Link $to="https://blog.logrocket.com">Test link</Link> </> ); }
In the above code, we attached an href
attribute to our <Link>
component, whose underlying HTML element was an anchor element. The value passed to the href
element was received from the transient prop, $to
, of our <Link>
component.
Transient props are a feature in styled-components that allow us to use props that will not show up on the DOM. So if you inspect our Link
component, you will notice that it was rendered as simply:
<a href="https://blog.logrocket.com" class="sc-bdfBwQ geNjDe">Test link</a>
Theming React apps with styled-components
styled-components also supports theming through the use of a ThemeProvider
component. We can pass a theme
prop to the <ThemeProvider>
, and this theme
will be passed to all the children of the <ThemeProvider>
as a prop.
We can now choose to dynamically style those child components based on the value of the theme
prop. Here’s a simple example to show how this works:
const lightTheme = { main: "#fff", }; const darkTheme = { main: "#000", }; const invertColor = (props) => { if (props.theme.main == "#000"){ return "#fff" } else { return "#000" } } const Div = styled.div` padding: 4rem; background-color: ${props => props.theme.main} ` const Button = styled.button` padding: 1.5rem 2rem; color: ${props => invertColor(props)}; border: 1px solid ${props => invertColor(props)}; background-color: ${props => props.theme.main}; transition: all 0.5s; &:hover{ color: ${props => props.theme.main}; background-color: ${props => invertColor(props)}; border: 1px solid ${props => props.theme.main}; } ` /*Default props for the Button component in case it has no theme attached to its props */ Button.defaultProps = { theme: darkTheme } function App() { return ( <> <ThemeProvider theme = {lightTheme}> <Div> <Button>Light Theme button</Button> </Div> </ThemeProvider> <ThemeProvider theme = {darkTheme}> <Div> <Button>Dark Theme button</Button> </Div> <Button>Dark Theme button</Button> </ThemeProvider> </> ); }
The result of the code block above in the browser should be:
As you can see from the code, there are two objects, lightTheme
and darkTheme
, to represent the primary colors for the light and dark themes, respectively.
There’s also a function called invertColor
, which simply takes in props and returns the opposite primary color of the theme attached to the props. Using the theme objects and the invertColor
function, we were able to create a light and dark theme and style our components accordingly.
Also notice that we used the ampersand (&
) symbol in the template literal of the <Button>
component; its usage is similar to what you would find in a CSS preprocessor like SCSS. The symbol simply represents the component we are styling so it can be used to style the component’s CSS pseudo-elements or classes, like we did.
Animation capabilities
As it should, styled-components also supports CSS animations through the keyframes
helper function. This function makes use of template literal to define the CSS of the animation and returns a value that can now be interpolated into the css
helper function or used with the styled
API.
Here’s a simple example from the docs that shows what the keyframes
function looks like in our code:
import styled, { keyframes } from 'styled-components' const fadeIn = keyframes` 0% { opacity: 0; } 100% { opacity: 1; } ` const FadeInButton = styled.button` animation: 1s ${fadeIn} ease-out; `
Conclusion
We’ve finally covered what I believe to be the most important and frequently used parts of the styled-components library. You can learn more about the library in the comprehensive documentation here.
Get setup with LogRocket's modern React error tracking in minutes:
- Visit https://logrocket.com/signup/ to get an app ID.
- Install LogRocket via NPM or script tag.
LogRocket.init()
must be called client-side, not server-side. - (Optional) Install plugins for deeper integrations with your stack:
- Redux middleware
- ngrx middleware
- Vuex plugin
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>