React Styling Sucks

Nick Gard
6 min readFeb 27, 2019

I’ve been writing React applications and components for about 4 years now and I’ve used several styling “solutions”: CSS-Modules, Glamorous, Glamor, Emotion, Styled-Components, inline style attributes, and the out-of-the-box import './styles.css' method.

They all suck.

Debugging sucks

Where the hell did that hashed class name / data attribute come from? Some libraries have configurations or webpack plugins that (slightly) demystify these names but you’d better have it set up correctly so that this only happens in “dev” and not “prod” so you don’t ship the extra weight. You probably won’t ever need to debug production code, right?

DevTools interop sucks

Many, if not most, CSS-in-JS libraries use insertRule to add styles to the page. While performant, this method has the unfortunate side-effect of creating rules that are un-editable in browsers’ developer tools. Need to see what a slight change to your styles look like? You’d better hope the Hot Module Reloading mechanism is working because you won’t be able to live-code the changes in the browser.

Vendor-lock sucks

Your app is using react-emotion. A dependency is using glamor. A sub-dependency is using jss. No problem, except that your app is now shipping 80kb of javascript to manage three different styling engines. Did you want to try the new CSS-in-JS hotness? It’ll be more than a drop-in change. The API for every one is different. Sometimes only slightly enough that the odd styling bug leaks into production.

Version-lock sucks

You finally got on board with emotion. All of your dependencies and sub-dependencies have been corralled and coerced into compliance with The One True Styling Library. Except that you’re on version 9, and version 10 just came out. It’s so cool but it’s a breaking version change. Then one of your dependencies upgrades. Now you must re-corral and re-coerce everyone to the new version or face the same issues as vendor-lock: shipping twice the code for (essentially) one styling engine.

Caching stylesheets sucks

There’s a small styling bug that needs to be fixed and shipped. Since the styling all happens in javascript (as does the constructing of the HTML) you will need to invalidate the cache for everything in order to get the fix out to your users. It’s not like caching is important for web performance or data-sensitive users. (If it wasn’t clear, that last line was sarcasm.)

Predicting cascades sucks

Your app has a CSS Reset, some utility styles, some global generic / normalizing styles, your components’ styles, and, of course, your app’s styles, which include a few global overrides for some components and a couple of conditional overrides for others. In order to avoid specificity wars, these styles need to be loaded in a precise order. Too bad this is all done magically for you by your CSS-in-JS library.

Does your app load its own styles before or after loading all of its top-level components? The order matters. Did your ESLint or Prettier IDE plugin rearrange your imports for you? Is that the source of your specificity bug? Does one component lazily load another? The sub-component’s styles now have higher precedence that its consumer’s. Does your CSS-in-JS library give you a modicum of control over the load order? Some do. Most don’t.

Overwriting default styles sucks

You’ve written a wonderful little component to be used everywhere. Every use case will be slightly different and you’re sure that each consumer will want to style it themselves, but you don’t want to ship the component out unstyled. You add some default styles with your favorite CSS-in-JS library. Now it’s up to the consumer to fight with that until they beat your component into stylish submission. They end up writing an overly-specific rule and use several !important declarations. Everybody lives. Until that higher component is itself consumed and re-styled. The abusive cycle continues.

Writing performant animations sucks

You may have heard that CSS can’t transition an element’s height from 0 to auto. Fortunately for you, you are using CSS-in-JS and it’s trivial to grab the element’s height at render time, slip it into your dynamic styling rule and animate that element. You shove performance concerns to the back of your mind and brag about your accordion-like component to anyone who will listen. It’s so popular with the team that it gets used everywhere. That’s when you notice the performance problems. How do you fix that? Maybe force only one to animate at a time? Use a Context Provider to coordinate all the animations? Let’s not call this over-engineering…

Using library methods sucks

Every CSS-in-JS library has a different API and a different strategy. But sometimes these don’t differ by much. Did your old library let you use template literals? The new one doesn’t. What about object notation? The keys are camel-cased versions of CSS properties, right? How is & used in the keys again? What if a selector key has a leading space, like " .sub-part"? Is it targeting a descendant with that class? If there’s no leading space, is it a conditional rule for the element it’s being applied to? Or is it still targeting a descendant?

Are you using a Styled-Component-like library? You get to write styled.div`border: 1px solid #000;`; but what if you need an element that could be either an anchor tag or a button? You have to create two different components. Do these generated components accept style or className attributes? How do they merge them or order the rules? That depends on the library and version.

Syntax highlighting sucks

CSS has had syntax highlighting in every IDE since the dawn of time. You don’t get that highlighting when you use CSS-in-JS, unless you are using the template literal methods and have a special plugin for your IDE. Should this be a deal-breaker when choosing styling libraries?

Adding global rules sucks

Adding a rule to the top of the cascade is easy. Too easy. Use injectGlobal(), :global { }, or whatever tool your library supports and the component you wrote can contribute a top-level rule. Now the rule is affecting things across the page. No component should cause an app-wide styling change, though. That’s the application’s job! When the next developer is hunting down the “bug” that injected this rule, they can only find this:

<style id="csUI987" type="text/css">
/* offending rule */
<style>

I guess you forgot to add in the webpack plugin that left in file name identifiers. Whoops.

Conclusion

So what doesn’t suck? CSS. Maybe SASS/SCSS. You already know it. That new dev you hired? She already knows it too. So do the designers you work with.

What order is the cascade in? In whichever order you loaded your CSS files. No magic. No mystery.

What about scoping styles to components? BEM. Bam.

What about smooth, performant animations? Avoid JS and only animate opacity and transform. Or embrace JS with the FLIP method or greensock.

What about creating default component styles and enabling overrides? Put a single class on each of the styled parts of the component, publish or provide a stylesheet that the component’s consumers can load in the correct order. Then they’ll be able to target and override the parts they need to. As for styles that must exist for the component to work correctly, inline them with !important like native elements do in the shadow DOM.

An input’s placeholder gets “display: none !important” when the input is not empty.

CSS is one of the three main pillars of web development. Don’t shy away from it or hide it. It is a robust language built to tackle the difficult issue of styling across various devices and browsers. Lean into it and learn it well and you will have a happier web development career.

--

--