Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@joeybaker
Last active August 29, 2015 14:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joeybaker/fc72e5e2c43a98d46cce to your computer and use it in GitHub Desktop.
Save joeybaker/fc72e5e2c43a98d46cce to your computer and use it in GitHub Desktop.

React's Inline CSS

React encourages you to deal with CSS with 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 when working with a team of more 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 Reacts'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 queries and 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.

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 are 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 and kill performance.

If you opt for straight 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 and you loose a lot of the benefit of server-side rendering – your users are likely to experience a Flash of Incorrectly Styled Content (FISC). That's a major gotcha. With CSS, we're ensured that the stylesheets download before the HTML, so we never see this problem.

No Global Styles

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.

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.

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 I'd Like to See

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 FISC . And, without style-fallbacks, we don't get wonderful features like flexbox.

But, all is not lost – much, 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.

@jmorrell
Copy link

Some thoughts off the top of my head.

Style fallbacks

Great point, and one I hadn't thought of.

No Global Styles or Media Queries

Why not combine approaches? Nothing says you can't drop in normalize.css. Or use media queries to structure your page. Ideally your components are pretty isolated from this or it makes them hard to share across projects / teams / companies.

How do you guys solve that problem with atomify? If I want to use two components that depend on different global styles, there's not really a way to resolve that is there?

CSS animations have their own problems, and there are good reasons why you might want to control them in JS, but for simple UI flourishes.. yeah, that clearly belongs at the component level, and there isn't a good way to do that inline. I've seen wrapper components deal with the animation in JS, then you just wrap it around what you need as the method of re-use. I believe React Native does something like this, which gives a button (or view, or image) an animation in response to being touched.

<Tappable>
  <Button/>
</Tappable>

In the worst case, you could re-implement something like browser animations in React. Not that I would advise doing such a thing.

<Animate properties={...} keyframes={...} duration={...} onEnd={...}>
  <View/>
</Animate>

Performance

I'd be curious to see how inline styles compress when gzipped, but your point remains. I don't think anyone has really benchmarked this.

As expected, CSS is just better at some things than JS.

Certainly! CSS is brilliant for layout (well, parts of it) and styling. It's used for much more than that though.

Is this a good use case for CSS: http://alistapart.com/article/quantity-queries-for-css ? Impressive hack, but not even close.

What about selectively hiding different panes of a dialog depending on which class is applied? No, that is pushing your application logic into your styling and mixing concerns, but I've seen it done a lot.

.optionA .pane1 { display: none; }
.optionA .pane2 { display: block; }

.optionB .pane1 {display: block; }
.optionB .pane2 {display: none; }

With the hover issue I mentioned in my tweet, you could make a good argument for solving it on either side.

What about :before and :after? Is that part of "CSS: The Good Parts"? Or was it a workaround for not having components?

Reacts' approach is not ready

This approach is already working in React Native with teams of devs. The concept, at least, is vetted, but there are realities about the browser environment that make it difficult.

I don't think they are insurmountable, but I agree I wouldn't start building a real product like this just yet.

@jmorrell
Copy link

Also, all of this should probably be prefixed with the assumption that you're on a decently-sized development team building an intensive app and treating the browser as a application platform.

For simple interactions and small apps a bit of CSS will wafflestomp the amount of code you'd need to write to try to do it all in JS. But the latter can have serious benefits as complexity grows or requirements for interaction are much higher.

@jmorrell
Copy link

Oh, and Netflix's platform, Gibbon, uses the equivalent of inline styles if you want another example of the approach working elsewhere for a complex app.

@vjeux
Copy link

vjeux commented Mar 16, 2015

But, it also means that you 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 and kill performance.

Media queries are based on the window dimensions, not arbitrary element dimensions. Asking for window.innerWidth will not cause a recalc style or any bad performance pattern. As for listening to the window resizing event, well, if you want to change the element dimensions when the window dimension changes, you don't have much choice, you need to listen to the event. But, rest assured that the browser is doing the same :)

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.

What about the fact that you don't need to download and parse the CSS rules for server side rendering. What about the fact that the browser doesn't have to run the algorithm to match each element against ALL the css rules to see which one is active.

Some of the work is shifted from inside of the browser to JavaScript. But that doesn't necessarily mean that it's going to be slower. Only performance measurement on real applications will tell. My experience is that the cost associated with styles (both css and inline styles) has very rarely ever been the bottleneck.

@jmorrell
Copy link

More likely, it's fundamentally flawed.

The phrasing here makes it sound like the idea of writing styles in javascript instead of CSS is a bad idea entirely, but I don't think you've made a very strong argument for it. There are several examples of other platforms using inline styles: React Native, react-canvas, Netflix (both before and after switching to React), Android, and I'm sure I could find more if I had more experience outside web.

I think your argument makes more sense framed as "This approach currently falls down when applied to the DOM because of these reasons." (though making a big claim in the title could lead to more clicks... "React inline-css considered harmful, will ruin your life.")

  • Needing to override a style on a selector (web legacy)
  • Need to render statically on the server and load code dynamically (specific to the web)
  • Need for global styles to provide a baseline across different browsers (web legacy, multiple competing runtimes also specific to the web)
  • Needing to hard-code device size into your styles during static rendering (specific to the web)

I'm not particularly convinced on the other media queries or performance arguments, but they can also fit into that paradigm.

inline css

But this is a fundamental flaw of inline styles.

There's nothing specific to inline styles that makes them slower than a stylesheet that I'm aware of. Like @vjeux said you get to skip the matching step so they should be faster. Though they may be unoptimized in current browsers since their use is discouraged. There are lots of conjectures here but not a lot of real-world experience.

Another thing you might consider when you're arguing for performance in this case is time to render. Loading CSS in the head is a blocking operation, resulting in a white page until an additional HTTP request returns, while the browser could start to render a page with inlined CSS as soon as it received those bytes. Adding in HTTP2 bundling only complicates this question more.

js performance

Implicit in a number of your assertions is that native code is always better / faster than javascript (animations, matching styles, media queries). That's not always true, especially when you're talking about generic platform code vs code specific to your application.

often devs still approach performance of JS code as if they are riding a horse cart but the horse had long been replaced with fusion reactor
https://twitter.com/mraleph/status/411549064787152896

Things like CSS-animations are certainly easier now, but that may not always be true as more sophisticated libraries are built and it becomes easier and easier to consume them.

But, all is not lost – much, 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.

You mean like react-style does? or something more clever?

@ianobermiller
Copy link

Couple other points:

No Style Fallbacks

What you are really trying to solve is vendor prefixing, and you should be doing that before you construct an inline style object anyway. There are are lots of libraries to do lightweight prefixing in JS. Here is an example usage of cssVendor.

Re-usable animations with @Keyframes are out too
Sure, if you assume inline styles mean you can NEVER use CSS, but that doesn't have to be the case. With a simple helper, you can inject the keyframes into a style tag and use them in your inline styles.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment