Demystifying styled-components

When I first started using styled-components, it seemed like magic ✨.Somehow, using an obscure half-string-half-function syntax, the tool was able to take some arbitrary CSS and assign it to a React component, bypassing the CSS selectors we’ve always used.Like so many devs, I learned how to use styled-components, but without really understanding what was going on under the hood.Knowing how it works is helpful. You don’t need to understand how cars work in order to drive, but it sure as heck helps when your car breaks down on the side of the road.*Debugging CSS is hard enough on its own without adding in a layer of tooling magic! By demystifying styled-components, we’ll be able to diagnose and fix weird CSS issues with way less frustration.In this blog post, we’ll pop the hood and learn how it works by building our own mini-clone of 💅 styled-components.Intended audienceThis article is written for experienced React developers. I assume knowledge about React, styled-components, and functional programming principles.There’s some pretty gnarly stuff in this one. I’ve done my best to simplify things, but there’s no getting around it: this stuff is complicated.Let’s start with a minimal example, taken from the official docs:styled-components comes with a collection of helper methods, each corresponding to a DOM node. There’s h1, header, button, and dozens more (they even support SVG elements like line and path!).The helper methods are called with a chunk of CSS, using an obscure JavaScript feature known as “tagged template literals”. For now, you can pretend that it’s written like this:h1 is a helper method on the styled object, and we call it with a single argument, a string.These helper methods are little component factories. Every time we call them, we get a brand-new React component.Let’s sketch this out:When we run const Title = styled.h1(…), the Title constant will be assigned to our NewComponent component. And when we render the Title component in our app, it’ll produce an

DOM node.What about the styles parameter that we passed to the h1 function? How does it get used?When we render the Title component, a few things happen:We come up with a unique class name by hashing styles into a seemingly-random string, like dKamQW or iOacVe.We run the CSS through Stylis, a lightweight CSS preprocessor*.We inject a new CSS class into the page, using that hashed string as its name, and containing all of the CSS declarations from the styles string.We apply that class name to our returned HTML elementHere’s what that looks like in code:If we render Hello World, the resulting HTML will look something like this:There’s more to this story!As you might imagine, the real styled-components codebase is significantly more complicated than this. We’re skipping a bunch of optimizations and quality-of-life fixes.One example that might be bugging you: with the code written as-is, a new CSS class will be generated on every render. In the real world, we’d want to slip that work behind a useMemo or useEffect hook, so that it only happens when necessary.In React, it’s common to render some JSX conditionally. In this example, we only render our element if our ItemList component is given some items:Code PlaygroundJSXfunction ItemList({ items }) {
if (items.length === 0) {
return “No items”;
}

return (

{/* Stuff omitted */}

)
}

const Wrapper = styled.ul`
background: goldenrod;
`;

// Rendering with an empty array:
render();function ItemList({ items }) { if (items.length === 0) { return “No items”; } return ( {} )}const Wrapper = styled.ul` background: goldenrod;`;render();It may surprise you to learn that styled-components doesn’t do anything with the CSS we provided in this case. That background declaration is never added to the DOM.Instead of eagerly generating CSS classes whenever a styled component is defined, we wait until the component is rendered before injecting those styles into the page.This is a good thing! On larger websites, it’s not uncommon for hundreds of kilobytes of unused CSS to be sent to the browser. With styled-components, you only pay for the CSS you render, not the CSS you write*.The only reason this works is because JavaScript has closures. Every component generated from styled.h1 has its own little scope that holds the CSS string. When we render our Wrapper component, even if it’s seconds/minutes/hours later, it has exclusive access to the styles we’ve written for it.There’s one more reason that we defer CSS injection: because of interpolated styles. We’ll cover those near the end of this article.You might be wondering: how does that createAndInjectCSSClass function work? Can we really generate new CSS classes from within JS?We can! One straightforward way is to create a