React Inline Styles are Fundamentally Flawed

The official approach to styling with React.js is to use inline styles, but there's a better future ahead.

heads up: this is a pretty old post, it may be outdated.

React.js encourages you to work with CSS as inline styles. The advantages are numerous: but can be succinctly stated as: it ensures that components do not overwrite the styles of other components. That's a huge win when working with any team larger than a few people.

As a fan of CSS (as completely flawed as it is), I was skeptical from the start, and I'm now sure that React's approach is not ready. At a minimum it's not ready. More likely, it's fundamentally flawed. It’s no good at vendor prefixes, abstraction, media/element queries nor performance.

I will say, once you get used to JSX, it's actually really nice be able to write styles in the same file. It feels like a real "component". But, there are far to many restrictions to make this practical.

No Style Fallbacks

There's no way to override a style on the same selector. This is impossible to do:

div {
  display: -webkit-flex;
  display: flex;
}

Because you can't declare two display styles. That makes flexbox impossible because iOS needs the webkit prefix. My workaround is to add a .flex class to the global CSS and apply that classname in React, but that's a kludge, and gets away from "component" feel of React.

If you use babel (and you should), babel-plugin-react-autoprefix might be a good answer.

No Media Queries or Element Queries

Media queries are broken. You have to use JS to detect widths and update inline styles accordingly. I was initially excited by this, because it means that element queries could be the default! But, if you go down that route, you'll have to use .offsetWidth and window.resize all over the place. That's crap for performance. All the global resize event listeners will slow you down, and offsetWidth is a good way to cause a recalc-style.

If you opt for faux media queries (which is unfortunate because element queries are more component-style), then you need to use something like matchmedia (polyfill). This is a bit slower than the native code the browser can use to do these same calculations… but it's close enough, so at least performance isn't much affected.

No matter which approach you choose, if you render on the server (you should), you have no way of finding the window/element size. This means, the initial state you ship down to the client has a very good chance of being wrong. You've lost a lot of the benefit of server-side rendering – your users are likely to experience a Flash of Incorrectly Styled Content FISC1). That's a major gotcha. With CSS, we're ensured that the stylesheets download before the HTML, so we never see this problem.

There's Jed Watson has an interesting proposal extract inline styles into a style sheet when rendered on the server. This could work for some basic styles, but still doesn't address the problem of media queries.

On top of which, the flexibility that inline-styles gives you as a trade-of for the lack of features built into CSS, comes with some subtle security gotchas.

No Cascading Styles

Avoiding inline styles is crucial. Inline styles are really hard to override, leading to a lot of unnecessary !important rule-sprinkling. Most of the time, a CSS class will do a better job at grouping a set of styles together, making it easier for the developer to identify what the styles are trying to accomplish, change the component stylistically, and generally have more control over the styles of the component. Not every inline style can be avoided. Positioning styles, such as left top right and bottom, that frequently change over time have a place inline. Coincidentally, the developer is expected not to have a need for modifying these styles, where control is deferred to the library author to do as they intended.

Nicolas Bevacqua, Designing Front-End Components

The whole point of inline-styles is to prevent global styles – but sometimes, global styles are good. One of the first things I wanted to do was use normalize.css. There's no way to that, you have to have a stylesheet. Things would be different if you could tie a stylesheet to a component. For example, you could have a component that only styles headings called type. Then each time you use a heading, you require that type component, and your <h1> just looks right.

Re-usable animations with @keyframes are out too. That's a bummer because CSS-based animations are a lot easier to make performant than JavaScript based animations. Even simple things like :hover have to be managed in JS with inline styles. Projects like Radium ease the pain of writing this code, but don't solve the inherent performance problems.

Further, debugging in devtools is a pain. Inline-styles make it impossible to see changes made to one component affect all other similar components.

Of course, there's also the problem of re-usability. One of the greatest features (and pain points) of CSS is that styles can be overridden with specificity or order. Inline styles have the highest specificity and are last in the order priority. Without a way to override styles, it's much harder to build generic components like button or a.

Performance

After all this, you're still stuck with the fundamental flaws that are inline styles: additional initial download times because of all the additional markup, and the ongoing performance hit as the the browser needs to parse all the style tags instead of a single CSS rule. Inline styles are slower than a stylesheet, loading CSS files in the <head> can be parallelized, and don't necessitate downloading duplicate markup for (potentially many) similarly styled elements.

Granted, all CSS performance is unlikely to be a large bottleneck. But this is a fundamental flaw of inline styles. It might be worth the trade-off but combined with the other limitations, I'm not sold.

What Could Work

More markup and moving calculations from heavily optimized browser-internals to JavaScript is net performance loss. Without @keyframes animations gets a lot harder. Without global styles we loose out on the cascading nature of CSS. Without stylesheet downloads, our pre-rendered HTML is subject to FISC1 . And, without style-fallbacks, we don't get wonderful features like flexbox.

But, all is not lost. Many, if not all, of these problems could be solved if we moved away from rendering CSS to inline styles and instead rendered them to a stylesheet! React already attaches a unique id to each component. It should be possible to grab on to that to abstract our styles, authored as inline, to a stylesheet.

What's Around now

There are a series of good ideas

  • react-inline. Seems to solve all of the complaints above (except element queries, but they're not solved by CSS anyway). Though how does the server handle media (or element?) queries?
  • react-jss allows you to define CSS in your JS as JSON, insert a <style> tag, and use the referenced classes in your React components. It's a decent approach, but one that requires you write JSON instead of CSS (though maybe there's a way to avoid that?) Ideally, we'd have something like a string template function, or even just a pre-compile step like JSX that allows you to write normal CSS in your JS. I'm also not clear on how the server could pre-compute a stylesheet.
  • jsxstyle Has a cool API – it's just more JSX. However, it leads to div-itis which can lead to performance issues. It explicitly doesn't care about semantic tags (e.g. <header>), but these can help with accessibility and human readability.
  • Radium. Still uses JS for faux media queries :( There 1.0 roadmap still leaves unanswered questions.
  • react-style with react-style-syntax. Is a cool approach. It lets you write CSS instead of JSON, and can extract out a stylesheet for server-side rendering. However, it doesn't support CSS selectors, pseudo-classes and CSS animation.
  • styling is a webpack based method to write styles in JS and output a stylesheet. It's very tied to webpack, but does many things right. It's conceptually similar to Radium.
  • csjs is a es6 template string that has the same syntax as CSS, but in JS. It works nicely with browserify, can output separate CSS files, supports a CSS modules-style workflow, and might be the best approach I've seen yet.
  • Aphrodite is used by Kahn Academy. It's syntax looks a lot like LESS, and is written in JS. Because of this, it has many of the problem of native inline styles: no cascading styles, nor style fallbacks for you. It's also not able to deal with dynamically injected components.
  • cssx allows you to write CSS directly in your JS. It's a nice approach to use if you just want your CSS to be in the same file as your JS.
  • glamor is a functional approach to inline styles. It's a nice abstraction around common problems like :hover, but hasn't been battle-tested.
  • styled-jsx

Looks pretty darn good. I'm not sure how the <style> tags play with a style sheet.
  • styletron Focuses on small CSS output by having one class name per rule. This allows faster styles parsing (though I'm not sure about style-recalc time). It solves some issues like media queries but is designed for a style tag, not a CSS file, so the postcss workflow is out-of-band.
  • css-template Still does inline styles.
  • css-literal-loader for webpack. Gives you CSS in a JS file, but compiles to CSS files. It's pretty nifty, though the community isn't too large.
  • styled-components A React-only solution, but one of the leaders in the community right now. Allows you to write real CSS in a JS file, but outputs either inline styles for React tNative or a CSS file for the web. Seems like a decent compromise, even if it's not quite feature complete.
  • glamorous is a styled-components fork, that enables all-JavaScript "CSS" authoring. If you like styled-components, but want more control over programatically manipulating styles, this might be a good fit.

Radium has a massive list of most, if not all, inline-style approaches on Github.

Michele Bertoli has a smaller list that shows some different features on Github.

So just use CSS

The css-modules Github organization has an interesting approach with local-scope. It makes the class names human-readable to make debugging easier, adds a lot of other niceties, and can be used with inline-styles where appropriate. This comes the closest to what the "ultimate" solution likely is: Shadow DOM.

One thing to be cautious of when using CSS: avoid the "cascading" part of CSS.

One shortcut we've been taking is a context prop that is more “flexible”, e.g. <Button context='thatDamnPopupMenu'> that becomes .Button--isInThatDamnPopupMenu. This kinda couples the components, but sometimes there's no way to avoid coupling. Like if there's only one place in the app where button's styles are different (usually due to layout) and you can't make up a prop that makes more sense than a generic context. Still, it is absolutely explicit and defined inside Button, so this coupling is visible and colocated with the rest of the Button styles.

@gaearon

Many thanks to @jmorrell, @vjeux, and @ianobermiller who reviewed drafts and waited patiently for it to be published.

Footnotes

  1. Flash of Incorrectly Styled Content 2