I recently started a total redesign of my website, and after recently learning about the cool new things happening with CSS, like Tailwind and Styled Components, I figured it would be the perfect learning experience.
Specifically, I was interested in either learning Tailwind (+ PostCSS), or Styled Components. Both tackle the problem of applying styles very differently from the approach I'm accustomed to in CSS and Sass. Tailwind uses utility classes that only set a few CSS properties at a time, allowing them to be composed. Styled Components, on the other hand, makes styling part of component creation by moving the CSS into JavaScript (or in my case, TypeScript).
For my website, I decided to use Styled Components since it would rid me of CSS preprocessors and free me from having to write CSS anywhere but right with the component I was building. What follows are what I've learned so far from this brief peek into the world of Styled Components.
A Run Down Of Styled Components
Styled Components is pretty slick. To write a style for your component, you just write plain CSS straight into your component with the css
prop:
import "styled-components"
const Component = () =>
(
<h1 css={`
font-family: Helevetica, system-ui, sans-serif;
`}>
Hello, World!
</h1>
);
This code would result in a <h1>
tag with the phrase "Hello, World!" being displayed in a sans-serif font. But a better way to write this with Styled Component's styled
export:
import styled from "styled-components"
const SansH1 = styled.h1`
font-family: Helevetica, system-ui, sans-serif;
`;
const Component = () =>
(
<SansH1>Hello, World!</SansH1>
);
This creates a component called SansH1
that renders only an <h1>
tag that has the given style. Most of the time, this is what you use when you use Styled Components, since the entire point of using the library is to directly connect styles to your React Components.
First Impressions
At first blush, I thought that the styles you could provide Styled Components could only look like a flat list of CSS properties (no Sass-style nesting). Not only that, but at this early stage, I didn't know about the css
prop (nor could I use it, due to needing some extra configuration for TypeScript). This lead me to initially create a styled component for every element in the component I wanted to style, which ended up looking like this:
import styled from "styled-component";
const StyledHeader = styled.header`
position: sticky;
width: 100vw;
padding: 1rem;
`;
const SuperAwesomeSiteTitle = styled.h1`
font-family: fantasy;
font-size: 2rem;
font-color: coral;
`;
const StyledNav = styled.nav`
display: inline-block;
float: right;
`;
const SuperAwesomeLoginButton = styled.button`
padding: 0.5rem;
background-color: coral;
color: white;
font-family: Comic Sans, sans-serif;
font-size: 1.5rem;
`;
const SiteHeader = () =>
(
<StyledHeader>
<SuperAwesomeSiteTitle>
Super Awesome Site Title
</SuperAwesomeSiteTitle>
<StyledNav>
...
</StyledNav>
<SuperAwesomeLoginButton>
Super Awesome Login Button
</SuperAwesomeLoginButton>
</StyledHeader>
);
As you can see, that method is quite verbose and wasteful. The point of components, after all, is reuse, and what is the point of creating components for the containing <header>
and that <nav>
if the site header is the only place where they'll be used?
Inlining One-Off Styles
You could, of course, avoid that by just using the css
prop to inline those styles and avoid creating whole components:
import { styled } from "styled-components";
const SuperAwesomeSiteTitle = styled.h1`
font-family: fantasy;
font-size: 2rem;
font-color: coral;
`;
const SuperAwesomeLoginButton = styled.button`
padding: 0.5rem;
background-color: coral;
color: white;
font-family: Comic Sans, sans-serif;
font-size: 1.5rem;
`;
const SiteHeader = () =>
(
<header css={`
position: sticky;
width: 100vw;
padding: 1rem;
`}>
<SuperAwesomeSiteTitle>
...
</SuperAwesomeSiteTitle>
<nav css={`
display: inline-block;
float: right;
`}>
...
</nav>
<SuperAwesomeLoginButton>
...
</SuperAwesomeLoginButton>
</header>
);
Which does look much nicer, but that is not the direction I went.
Taking Advantage of Styled Components' CSS Syntax
Instead, my ignorance of the css
prop continued and I learned more about what kind of CSS Styled Components understands.
And as it turns out that Styled Components actually understands a dialect of Sass. This dialect does not support variables, helper functions, or special @
rules like Sass, however. What it does support is nesting and the &
selector (really, this could be a dialect of Sass or Less). What this means is that we would could write the above styles like so:
import { styled } from "styled-components";
const Container = styled.header`
position:sticky;
width: 100vw;
padding: 1rem;
nav {
display: inline-block;
float: right;
}
`;
const SuperAwesomeSiteTitle = styled.h1`
font-family: fantasy;
font-size: 2rem;
font-color: coral;
`;
const SuperAwesomeLoginButton = styled.button`
padding: 0.5rem;
background-color: coral;
color: white;
font-family: Comic Sans, sans-serif;
font-size: 1.5rem;
`;
const SiteHeader = () =>
(
<Container>
<SuperAwesomeSiteTitle>
...
</SuperAwesomeSiteTitle>
<nav>
<ul>
...
</ul>
</nav>
<SuperAwesomeLoginButton>
...
</SuperAwesomeLoginButton>
</Container>
);
Making the top-level element a component here is somewhat unnecessary, but it does make things look cleaner when we get to the component. And now all our non-reusable styles are confined to a single place instead of being sprinkled throughout our component. This still is a bit of a trap however. For instance, if you are styling a blog like mine, which automatically generates HTML from Markdown or MDX, you would end up with a container component providing styles for every element to be found in the generated HTML (since you can't provide the styles inline like you would with other components).
This leads to a very, very long block of CSS that, aside from being literal CSS inside JS, resembles the way you wrote CSS before. It is disorganized, hard to read, and not very modular or reusable. Not to mention that we have not even talked about themes, how to handle mix-ins, or how to utilize component props to change styles.
Computed-Styles and Themes
In Sass, you can define special Sass variables and use control flow commands to compute styles at build time. But in Styled Components' CSS dialect, you have access to neither. Instead, what you do have access to is the power of template literals. Since all CSS used for Styled Components has to be written in template literals, you can also make use of features like template expansion to inject values from your code into your styles.
In the Styled Components documentation, they even demonstrate this usage for applying values stored in a theme object to your styles:
import styled from "styled-components"
const ThemedComponent = styled.div`
background-color: ${ ({ theme: { bg } }) => bg };
color: ${({ theme: { fg } }) => fg};
`;
These callbacks are passed all the props of the styled component, as well as an additional prop called theme
that stores the theme provided by a <ThemeProvider>
somewhere higher up the DOM tree. From what I've seen, it's difficult to use this syntax and have a cleanly organized theme object. If you try to pseudo-namespace parts of your theme, you end up with deep theme objects that require long lines just to access the values you're looking for:
const ThemedComponent = styled.div`
background-color: ${ ({ theme: { colors: fg } }) => fg };
`;
Or, if you keep the object shallow, you end up with a jumble of properties in your theme object:
const theme =
{
fg: "#111",
bg: "#fff",
fontMono: "Monaco, Consolas, monospace",
paddingSm: "1rem",
paddingBase: "2rem",
...
};
Mixins
Another thing that Sass provides and Styled Components lacks is @mixin
. However, our styles as template literals are here to save the day again. Since a @mixin
from Sass is just a function that generates valid CSS, in Styled Components, all we need to do to create mixins, all we need to do is write a function that returns valid CSS and inject it into the tagged literal containing our CSS. This is done with the help of Styled Components' css
tagged template literal:
import { css } from "styled-components";
const style = css`
padding-top: 1rem;
`;
Since css
is a tagged template literal, you can use the same tricks to inject values into the CSS inside of it. So, for instance, you can create a mixin that accepts a theme object (or a color map from a theme object) and applies the foreground and background colors:
import styled, { css } from "styled-components";
const theme =
{
fg: "#111",
bg: "#fff",
};
const setForegroundBackground = ({ fg, bg }) =>
css`
color: ${ fg };
background-color: ${ bg };
`;
However, JavaScript and TypeScript have no idea how to handle CSS units, so when I tried to port a mixin that I wrote in Sass for generating typescales to TypeScript, I had to resort to using the polished
library and it's math
helper to compute type sizes without having to make serious assumptions about what units the base font size was using.
Take Aways
The Good
Styled Components got rid of CSS pre-preprocessors from my project and reduced my code base to only TypeScript and config files. This was nice and meant that I have less to debug when it came to build errors and tooling.
The Bad
Styled Components didn't really solve some of the core problems I have with CSS and styling components, which is largely figuring out what should get it's own, unique styles (i.e. become a styled component), and what should just make use of utility classes (i.e. just have styles fed in through a css
prop). This echos a lot of the issues that people already have with CSS and picking class names for specific components.
The Ugly
Styled Components code can get quite ugly and verbose if not properly managed. For me, a lack of experience makes this abundantly clear. Theming is no longer as simple as swapping out some variables, and has come with it's whole own API. Styles are found in a dozen different places, and, out of a lack of personal rigor and no idea what I'm doing, no unifying organization.
Conculsion
I am writing this well after I got started re-designing my website, and I have well moved past these first impressions. Personally, I have started to find Styled Components to be too overwhelming for something as simple as my personal website. Having access to CSS, as well as being able to programmatically configure styles right in my code is great. However, I have yet to find the sweet spot that lets me have my cake and eat it too with Styled Components.
I've recently begun experimenting on CodePen with Tailwind-style utility classes and Sass and found that workflow to be both faster and more enjoyable. You can achieve something similar with usage of Styled Components' createGlobalStyle
, but unlike Sass, you can't just write @extend
into the css
prop of an element for it to inherit the styles of a class. Instead, you end up having to double up with className
and css
in order to both use the utility styles and inline styles.
In the future, I may experiment with Tailwind and Styled Components, utilizing the popular twin.macro
library that allows Styled Component style blocks to @extend
Tailwind utility classes. The pairing of the two may just be what I need to keep me hooked on Styled Components.
Top comments (0)