Styled-components,
Why you should (or should not) use it
Choosing the styling module that perfectly fits your needs is as difficult as choosing a javascript framework. Your final choice could be determined by the scale of the project, the existing stack of the company or simply a matter of taste. If React is your framework, then styled-components cannot be excluded from your list of possibilities. Bas Bastiaans – Frontend Developer at PanCompany – recently migrated from ‘less’ to styled-components and shares the benefits he experienced afterwards. Next to that, he also discusses some talking points you really have to consider before taking the migration steps he did.
What is Styled-Components?
Styled-Components is a library for React that allows you to write your CSS directly in your javascript. This is called ‘css-in-js’. This method is not exclusive to React, you can implement css-in-js with almost every javascript framework available, but styled-components is probably the most popular. If you are familiar with any method of CSS, then the step to styled-components is fairly easy. Of course, switching from less to styled-components requires you to overcome a bigger learning curve than, let’s say, going from classic CSS to CSS modules, but you will adapt to it naturally if you are a javascript developer. The syntax for writing the styling is still purely CSS, the main difference is that you can write it directly in your javascript.
Let’s look at the difference between implementing classic CSS and styled-components. In CSS, you create global styling classes which you inject in your javascript and for every component you determine whether or not it needs a certain classname. Especially in big projects with a lot of components, these classes can overwrite each other, which causes styling inconsistencies in your application. Styled-components fixes this issue, because you need to determine your styling locally. The scope of the styling is inside your components.
That is possible because styled-components benefits from a javascript functionality called tagged template literals (a way to call a function with backticks). This is best explained by actually showing it. You make a styled-component out of a React element by defining it with the ‘styled’ object. Here you see a simple example of a div with red text and font size of 16px:
import styled from 'styled-components';
const StyledTextBlock = styled.div`
color: red;
font-size: 16px;
`;
And you can use that styled component like so:
const App = () =>
<StyledTextBlock>
I am a pretty text block
</StyledTextBlock>
The local scoping is a major advantage compared to regular CSS, but it is not the main reason to switch to styled-components. CSS modules, for example, also fixes the issue with scoping. One of the biggest advantages is that styled-components allows to create styling as a javascript developer. And because you are using a template literal, you can adjust your component dynamically by using props. This allows you to very easily change the look of your component, caused by a change in your data. This is a major benefit compared to regular CSS, in which you would have to inject a different classname for each different styling.
If you want to style your React component based on a prop, you can do that like so:
const App = () =>
<StyledTextBlock isBold>
I am a pretty text block
</StyledTextBlock>
And then you would adjust your styling based on that prop like so:
const StyledTextBlock = styled.div`
color: red;
font-size: 16px;
font-weight: ${props => (props.isBold ? 'bold' : 'normal')}
`;
Or, after a little refactor, in a more robust way:
const StyledTextBlock = styled.div(({ isBold }) => `
color: red;
font-size: 16px;
font-weight: ${isBold ? 'bold' : 'normal'};
`);
Some more advantages of styled-components compared to regular CSS
The previous examples already prove how you can benefit from the dynamic characteristics of styled components. However, there are a lot more:
- Styled-components does not need an extra step in your CI/CD pipeline
Another advantage is that building your application is a lot easier, because you do not have to account for any .css files. The styling is in your javascript, so you only have to build the javascript in your pipelines. The only configuration is you might have to add is the babel plugin for styled-components.
- Styled-components generates unique class names
If you inspect the previously built component, then we see the following in the DOM:
<div class="sc-hLQSwg eZXEhV”>I am a pretty text block</div>
Behind “class” you see a generated unique name. This makes sure you will almost never have bugs related to class names.
Tip: If you use snapshot testing, it can be annoying that classes are generated dynamically. To prevent that, you can use the following library: jest-styled-components
- Styled-components makes for easy and accessible theming
Another big advantage is the built in “Themeprovider”. With that provider you can create a theme filled with predefined colors, spacings and other values and utilize it for your entire React application. This is way easier to accomplish using styled-components, than by using classic CSS because of the dynamic characteristics of styled-components. In every styled component you can access the theme object to, for example, give every input a border radius of 6px.
The value of theming is best described by adjusting the previous component again. We can add a theme to our application in this way:
import styled, {ThemeProvider} from 'styled-components'
const theme = {
borderRadiusBlock: '6px',
}
const App = () =>
<ThemeProvider theme={theme}>
<StyledTextBlock isBold>
I am a pretty text block
</StyledTextBlock>
</ThemeProvider>
And you can implement it like this in your styled components. Like you see down here, you can acces the theme object directly and adjust the entire styling of your application from a centralized location.
const StyledTextBlock = styled.div(({isBold, theme}) => `
color: red;
font-size: 16px;
font-weight: ${isBold ? 'bold' : 'normal'};
border: 2px solid black;
border-radius: ${theme.borderRadiusBlock};
`);
Still, there are always some things to consider
The advantages of styled-components are a delight to every javascript developer, but there are still some things to overcome. The following argument is not really a reason to not go for styled-components at all. It is merely a slight irritation that you have to get used to: wrapping your components can cause overhead.
In a big application where you reuse a lot of components, it happens a lot that you want to make a slight adjustment to another elements. That is very easy if you reuse a styled-component like in the following example:
const StyledButton = styled.button`
display: inline-block;
color: black;
`;
const RedButton = styled(StyledButton)`
color: red;
border-color: red;
`;
In the previous example you see there are two separate buttons, a red button that has overwritten the styling of the already styled button. This is always possible, as long as the element that you are trying to overwrite is also a styled component (or a native React element). If you want to overwrite a component that is not a styled component, you can only do that by adding the className prop to the component, all the way until you reach a native React element:
const Link = ({ className, children }) => (
<a className={className}>
{children}
</a>
);
const StyledLink = styled(Link)`
color: black;
font-weight: bold;
`;
However, it is also very likely that you are using a component library where className is not added to every component. For example, if you are using a form from an external library, but you are not a fan of the padding around the input fields. Then there is no escape from using a wrapper component.
Imagine you are using the following component:
const SomeComponentWithInput = () => (
<div>
<input />
</div>
);
But what if you want a red border? Then you cannot do that by simply putting SomeComponentWithInput in a styled object. In that case you can wrap the component in another styled component, in which you try to reach the component that you want to style. For example:
Import SomeComponentWithInput from ‘component-library’;
const SomeWrapper = styled.div`
input {
border-color: red
}
`;
const App = () =>
<SomeWrapper>
<SomeComponentWithInput/>
</SomeWrapper>
Because you can reach an element in every child component, you still have a lot of flexibility in overwriting other styling. Just like with regular CSS, you can reach other elements with things like classname or id, but you can also call other styled components. Imagine that you want to call a component that has a child component that is a styled component, then you may do the following:
const StyledInput = styled.input`
border-color: green
`;
// this component has a styled component StyledInput as Child
const SomeComponentWithInput = () => (
<div>
<StyledInput />
</div>
);
Then you can reach it with a wrapper in this way:
const SomeWrapper = styled.div`
${StyledInput} {
border-color: red
}
`;
The disadvantage of using these wrappers is that your code base can get complicated. An advantage of styled components is that you immediately see where your styling is coming from, but with wrappers that benefit loses its value. Next to that, extra wrapper divs can cause changes in tests and makes debugging a little harder.
Also consider performance as a possible disadvantage
This article can not be concluded without mentioning the slight performance issues that could negatively affect your user’s experience. Because styled-components is a css-in-js method, everything is written in javascript, which of course increases the javascript execution time and also the bundle size. This negatively impacts the initial loading time of your application. You also cannot use the performance boost that you could have had with caching. Classic CSS files can be cached, which you cannot do with styled components because there are no CSS files.
Conclusion
Considering the performance issues, should you still migrate your React application? Even if you already built it with CSS modules or any other solution like Tailwind or PostCSS? Maybe. Personally I feel that the advantages greatly compensate the possible disadvantages, especially as a javascript developer. Next to that, the ease of using themes really benefits the collaboration with your UX designer. But whether or not you should migrate is still very much depending on other factors like taste, project scope and the existing knowledge of you or your team mates. Always carefully consider your styling tool, but choosing styled-components could certainly make your life as a React developer a little more pleasant.