Real-world CSS vs. CSS-in-JS performance comparison
pustelto.com
pustelto.com
- Styles are global
- Styles are targeted via brittle, untyped, and opaque "magic strings” basically. This means mistakes are more likely to be caught at run time than compile time. Eg, I wouldn't get a compile time error if I did `position: oops` or `class="oops"`.
- Styles are often "far away" from their target which makes mistakes more likely; ie this deeply nested HTML element in one file is coupled to a deeply nested style sheet in another file
- It is easier to perform complex manipulation of styling if it is made up of JS objects. Eg, if I wanted to do math or I wanted one style to be a function of another (eg `marginLeft: PAGE_MARGIN`)
That being said, I’m sure there are some better ways of doing traditional CSS since I last tried it that I’m unaware of...
As far as the performance trade off, I'd love it with styled components did not come with this but, at least for my use case, it is usually worth it
At least for my applications, the computers my users run on can't handle the performance implications of styled.
But beyond the performance, I legitimately build faster using tailwind. I also find it easier to understand the components others build as well.
It was a little struggle at first with our previous setup, but I spent a week or so moving everything over to Vite with hot module replacement which has been life changing.
This whole discussion feels silly after a day building things with Tailwind. The system, defaults, docs and tooling are excellent.
And dev speed is ludicrous.
Can encourage others to give it a try as well. Dev productivity through the roof indeed :)
It would be more fair to compare with frameworks that work with scoped styles (such as Svelte or Vue’s single file components). Developing in these is also an excellent productivity boost.
In my opinion the bad ergonomics is actually when authoring the components (not consuming it). Many of the faults have been excellently pointed out previously[1]. The most glaring the boilerplate you have to write if you want your attributes to reflect a property.
This has an easy fix though, which is that you simply don’t write your web components by hand. You either use a library (like lit-element[2]) or a compiler (like stencil[3]). I’m personally waiting for a less opinionated compiler with a smaller runtime then stencil (preferably no runtime; perhaps that is Svelte with a web component target, I haven’t tried it).
There is also a proposal for a declarative shadow DOM[4], which aims to tackle some of the bad dev experience we have with web components. However I’m personally a little skeptic that it is a good proposal, or that it will fix what most web component devs are concerned about.
1: https://dev.to/richharris/why-i-don-t-use-web-components-2ci...
It also is more based on what css can do instead of having it's own abstraction. Most of the class names in Tailwind are very close to their css key+value equivalents. For example if you wanted to write `float: right` in css you'd use the Tailwind class `float-right`.
But point 2 and 4 stand.
(eg: `margin-left: var(--page-margin);`)
The main issue I still see with CSS variables is IDE support and static validation. With my CSS written in typescript I know that every variable I’ve imported works statically.
I try to focus on writing nice HTML often multiple relevant class names. Then later make things look the way I want in CSS.
I find it hard to think in CSS in translate that to HTML but a lot of developers I know tend to work that way.
https://youtu.be/jUQ2-C5ZNRc?t=683 Looks like the spec is at https://drafts.csswg.org/css-scoping-1/ with examples like https://drafts.csswg.org/css-scoping-1/#example-f1503361 and more details at https://css.oddbird.net/scope/
Still experimental of course, but could be very useful if implemented alongside Shadow DOM. Note this isn't 2012's scoped CSS: https://caniuse.com/style-scoped
If we get this the only thing I'll continue to dislike about Web Components would be the global namespace of web components and that Web components HTML attributes can't be rich objects or arrays natively. (I'd love it if web browsers simply implemented JSX, for example, or a syntax that looks like DOM but is actually a function call that can produce DOM. Bonus points if they also implement Markdown or MDX as an HTML element of some kind.)
Web components largely failed at this point though, the spec needs to be rewritten from scratch to be more pragmatic. People have been using React and Co for more than a decade now, spec writers certainly have enough hindsight as to how people practically program front-end applications.
Looking it up, archive.org says it's been over 20 years… https://web.archive.org/web/20001218094100/https://www.mozil...
This is the webpack loader that generates type def files: https://github.com/seek-oss/css-modules-typescript-loader
- Styles are targeted via brittle, untyped, and opaque 'magic strings' basically.
- Styles are often 'far away' from their target which makes mistakes more likely; ie this deeply nested HTML element in one file is coupled to a deeply nested style sheet in another file"
You can get a long way to solving those three issues with good organization, but only if you control your whole project, stick to the plan, and don't have any libraries.
But over the course of a long lived project CSS is a real foot-gun that even well meaning developers will shoot themselves with. It's hard to unweave a tightly woven CSS nest, and once you introduce third party libraries and custom CSS things start to get wild. Who hasn't seen a "custom.css" overriding the "global.css" which was itself just a bandaid to fix an issue with some third party library.
I use a method I called "Contexts and Components". It starts with a reset. 98% of the CSS is inside the component files, and target component classes, eg <div class="product-card">. But of course things can change depending on what page they're on, and those are called contexts. Maybe a product card on the home-page has a border or whatever. That gives you a way to address client requests for things to differ but without making crazy complicated configurable components.
That way things are really shallow, only one level deep unless they are modified by a context, which makes it two levels deep. I find I need way less CSS, no crazy hard to reason about selectors, and it's all very easy to understand. Best of all, it's easy to remove stuff entirely, so you don't end up with overrides over overrides.
Is there something extra these React libraries do that prevents them from doing this?
I think one of the main appealing aspects is the idea of never having a styling conflict with other components ever.
Less macro-level management and organisation required.
--
Edit: sorry, just realised you're talking about a specific implementation.
CSS in JS just isn't required a lot of the time. We have more mature methodologies available.
The web has never lived up to that ideal. Sometimes you need to add a wrapper div with no semantics to make your layout work. Sometimes you need some Javascript to do a special layout CSS doesn't support or work around some other limitation. I've never seen a project that actually had meaningful "separation of concerns" based on technology, whereas almost every componentized project has at least decent separation of concerns along the component axis -- where you can drop a component into a design without thinking about it's internals at all.
> I've never seen a project that actually had meaningful "separation of concerns" based on technology.
With respect, you just need to look a little harder.
I've used react libraries which are fundamentally tied to their styling, due to the fact they use CSS in JS.
Applying custom styling has been more difficult than it should have been.
You're being obtuse. JavaScript is used for more than presentation.
Using hand-tuned CSS will undoubtedly be the most performant option, but it leaves you with very difficult problems at the boundary of your styling and content/structure concerns. If you have even a sliver of dynamic content in your website, it will quickly become near-impossible to verify that your content and styles work together as expected, or even to verify that your class names match between your CSS and HTML.
On the other hand, if you use CSS-in-JS, what you lose in performance you gain in compatibility guarantees between your concerns, regardless of how you prefer to separate. Are you putting your styles in the same files as your layout components to fully separate one feature from another? Great, you can unit test those components and be reassured that the elements are styled as expected. Are you isolating your styles to only a certain subset of components that deal directly with styling concerns? Also great—if you're using TypeScript, you can guarantee correct use of those styles at build time.
For a large enough team, those guarantees really pay off. If you have lots of customers using 2G/3G networks and want to hand-roll your CSS, I commend you! For most products, I think there's a better way to make that tradeoff and your users won't mind a slightly slower experience that has fewer bugs.
If you want to share styling between your app and your website; I would also state that this process is made more difficult.
If you want to create, implement and maintain an atomic design system to build in consistency and reuse .. again, more complicated that it might have been.
To add to that, this article suggests that there's a performance deficit.
e.g. const Container = styled.div` width: $props => props.width `
In those scenarios, there are a few benefits: (a) no worries about accidentally trampling on someone else's styles, or styles from elsewhere overriding your styles; (b) build configuration is simpler; and (c) no worries about how CSS/SCSS is referenced/loaded - all references are handled in plain JS with no special loaders or anything.
I wouldn't use it everywhere (I've used other approaches that have worked just fine), but for some scenarios it makes sense.
note: @emotion/styled is equivalent to styled-components, lighter and easier to set up for SSR (it works out of the box https://emotion.sh/docs/ssr#default-approach)
When using server-side rendered pages, there's no performance loss on the client-side, maybe a little more processing server-side, not sure if significant
styled-components does not, and I'm not sure exactly why. It may be related to that they support runtime variables inline as React props and/or that they didn't want to complicate the build pipeline more than they already complicate it. (Many of the libraries like Vue's have necessarily tighter coupling with webpack or whatever other packer they use to get the extraction automated.)
If I write plain CSS, it's easy to make changes right in the browser and have it react instantly. Or I can make them in the .css file, and webpack (or whatever it is) will automatically redisplay them without having to restart the app.
Perhaps if I had much larger projects I'd see more headaches that drives people to css-in-js. But I manage fairly large things and don't have too much grief with plain css.
How would you go about doing this?
--
Edit: to the person who downvoted me for asking a question. Classy.
Or alternatively, apply a class to the body tag – e.g. `<body class="high-contrast">` – and declare CSS rules accordingly. Specificity should take care of overriding the 'normal' style rules where needed.
The best you can hope for is to forget about it. It's unsatisfying, but less unsatisfying than trying to get back at them or apply "sour grapes" or whatever. None of that works.
The sooner you put your brain on something else, the sooner you'll forget that somebody was a dick to you and got away with it. It sucks, but it's the least worst alternative. Treat it as noise -- which I think is what the OP was trying to say.
I also think it's worth calling it out too though.
Sadly, Chrome doesn't support this standard (it has been around for a while and is in HTML4, HTML Living Standard, CSSOM). But you can work around this by JS, see above.
(Edit: It's for things like this that you still want a menu bar for your browser.)
https://developer.mozilla.org/en-US/docs/Web/CSS/Alternative...
I’ve implemented that two ways: having JavaScript pop the alternative attribute off for all but the desired style sheet or simply using a top-level class to modify the relatively few things we were customizing. Back in the day, I used SCSS but CSS Variables have matured to the point where you can do an awful lot that way, too.
For me, writing javascript that directly sets css properties based on state variables is a more powerful and clearer approach to managing dynamic styles than manually managing class names.
Further, when combined with typescript, you can type a theme object in the styled-components theme provider and get auto-complete in your editor on your style tokens like colors, spacing values, timing in ms, etc.
So it has many benefits beyond scoping styles to a component.
I'd argue there's a lot more flexibility available if you choose to use classes.
Sure, it's more complicated and involves more work; but to say it's less powerful just isn't true.
That is simply not true. Apart from what was described in the post above, in JSS frameworks such as styled-components, you usually have ways to hook directly into css parser, allowing you to implement custom expansions, replacements or basically whatever logic you want. This is not possible unless you write your own language and transform it to css.
I find it hard to believe you think you can do more with plain CSS than with the full power of the Javascript programming language.
However, when used correctly you can create a system that will allow you maintain and apply your presentation layer in an efficient, extensible, consistent and reusable way.
Lack of knowledge isn't an excuse for believing otherwise.
--
Edit: I'm actually shocked by how much bad feeling there is for standard front-end technology.
A domain specific solution is a benefit.
If that is your intention, typescript is a better tool for the job: you get type safety, ide autocomplete, tests (have you ever written a test for css?). You are right, being a domain specific solution is a benefit, but that has nothing to do with how good of a system you can make with it. I'm sure it's possible to write a system in typescript that would be as good as what you describe, but it will be more flexible at expense of being slower.
I think you are entirely incorrect.
Typescript is absolutely not the solution.
I also find much state has a nice selector (e.g. :hover, :checked, :invalid, :empty, :focus-inside, etc.) or media queries `@media (prefers-color-scheme: dark)` which I can change the custom property values inside. So it is really an exception when I manually need to change the value of custom properties with JavaScript.
For example, I've used it on a personal app to allow adjusting the placement and opacity of controls. I prefer the CSS-in-JS approach because the code is simple to understand, I can reuse the code in multiple areas, and I can pass state to some function to generate the styling. I also imagine that I could also write some spec to support it, but it's not something that I plan on quite yet.
It's not a technique that I would use for ALL styling, but I think for the use-case I mentioned above, it works great.
myElement.style.setProperty('--my-custom-property', 'some-value');For making code easier to understand you can set a CSS custom property instead of specific style values, and the CSS will be really straight forward.
Where that technique fails is when you have hundreds of possible values that can occupy a given state.
If I want to set the rotation or opacity for a control and I want to use a range of values, there is nothing native in CSS to allow me to do that.
You can use SCSS to dynamically generate the CSS class declarations for every possible value, but I would only ever use that solution if I needed the performance boost and that was the only way to get it.
.rotatable {
transform: rotate(var(--rotation-angle));
}
...and set it directly with JavaScript document
.querySelector('.rotatable')
.style.setProperty(
'--rotation-angle',
`${angle}deg`,
);If you want to have a range of applicable values for some CSS property or state-based styling, CSS-in-JS can be a good tool for expressing that as opposed to writing JS that updates some styling declaratively
I think there is a fundamental difference in setting style properties in JS and setting CSS custom properties (even though CSS custom property is a style property). The main difference is that it is clear from the CSS that this property is dynamic and can be expected to change. If you overwrite a style attribute (e.g. `element.style.transform = "rotate(50deg)"`) this is less clear.
So in honestly I don’t see how setting a CSS custom property in JavaScript as lacking in expressing the intent.
With React, you don't modify the DOM directly, so the use case you have laid out is not applicable.
With that in mind, you are still free to use the CSS custom property with React, but if you want to have multiple transformations across multiple parts of an application, it's a lot simpler to use CSS-in-JS to generate styling based on state.
The use case you have brought up should work well with vanilla js/jquery; however, the context of the discussion is rooted in front-end libraries and the popularity of CSS-in-JS, so I am not sure how custom properties are relevant.
Also I explain on a sibling thread that there is a fundamental difference between setting a custom property (like I do here) and setting a specific style value. While I do see how the latter can be problematic as there are no hints to the changed style in the CSS file, the former does not have that issue, particularly if you name the property with a hint to the dynamic nature if it.
The price you pay comes in the way of performance and maintainability. Since the line becomes very blurry between styling and markup, it's up to the dev(s) to bring order to potential chaos.
For any bigger project, my go to solution is SCSS modules. It's by far the most versatile solution I've tried if you want to roll your own CSS and not rely on a CSS framework. You don't need to worry about scoping, the bundle process is excellent, you have access to all of your normal SCSS shenanigans and most important it's a solution that makes it dead easy to collect or break down parts into logical units.
I haven't used SCSS modules though.
import { mainGraphic } from './component.scss';
[...]
return (
<figure className={mainGraphic} />
);
Where `mainGraphic` is a regular class block in the SCSS. So it's pretty straight forward and the mapping is still fairly tight. At bundle time, the class name is minimized, deduped and a hash is added to it that corresponds to the rule content. The generated standalone CSS file also gets hashed for cache busting purposes. It's pretty neat.And since it's SCSS you can keep constants, mixins, functions, etc. imported and organized within and between SCSS files, separated from the components.
"Great developer experience shouldn’t come at the expense of the user experience."
Great developer experience can conceivably deliver better user experience. A more capable, responsive and lower defect application that load 10% slower may be a net improvement.
I'm not arguing that CSS-in-JS actually delivers that. Only that load times aren't the only variable in the "user experience" equation.
The problem with this approach is that this 10% is not a one-time fee, but is collected on a regular basis, with compounding.
Although, in my experience, usage of react is not indicative of a more capable, responsive, or lower defect application. Quite the opposite (for me) actually. React is a good (but not absolutely reliable) indicator of slow, unresponsive (in terms of response time, not resolution scaling, buggy, and frustrating UX.
From the code examples of the suggested library, it looks like a pretty simple drop in replacement for many (if not all) cases. 10% for free would be a slam dunk.
It reminds me the era when devs were chastised for using tables for layout, and using floats was deemed the right way to do things, when both techniques were essentially just hacks with both their strengths and weaknesses (overflow:hidden anyone?), but somehow the entire dev community was gazlit into believing that "tables" for layout were bad. Tables weren't bad, CSS wasn't just good enough, otherwise Flexbox or Grid would not be needed.
Flexbox considerably simplified CSS layout. The people who never had to use a single float attribute in their style declaration cannot fathom that.
CSS, the way it was designed, has been terrible for a long time and a thorn in the side of generations of web developers, but nobody want to acknowledge that. IF there is one thing the web failed at, it's absolutely CSS, from its syntax to the way it works in conjunction with HTML And Javascript.
> The reason tables for layout were so bad was that the table as a whole couldn't be rendered (laid out) properly until every cell was fully rendered first, including images with unspecified width/height attributes as well as dynamic cell dimensions based on free-flowing text, etc [...]
That was purely a limitation of the CSS rendering engines, it had absolutely nothing do to with table layout themselves, from a developer perspective. There is no reason this problem cannot/couldn't be worked out by CSS rendering engine developers. I get what you are saying, but it wasn't a good enough reason to dismiss tables entirely (and that wasn't the biggest reason used back then when "a list apart" writers decided that tables were cancer).
Floats were so good yet developers had to resort to CSS frameworks and grid frameworks for years in order to make working with CSS bearable? No, float positioning was horrible, un-intuitive and just a hack. Again, the culprit was CSS itself (and by extension the rendering engines), not the developer using tables.
The irony is that all these CSS frameworks were essentially tables re-implemented on top of "floating divs".
A good measure of whether a web spec is good or not, or pragmatic enough or not is how much effort developers go through in order not to use that spec directly. Generally, if using a framework on top of the spec to make that spec somehow useful is what most developers do, it means that the spec needs to be revised and improved in order to fulfil the needs of the developer, not the other way around. Obviously that doesn't apply to low level protocols like WebRTC and co, but CSS isn't a low level protocol, it was supposed to make web presentation easy, and it failed at it for decades. That's all I'm saying.
Just thank god we now have at the very least Flexbox and Grid. Which makes bootstrap and co completely redundant even for devs allergic to design.
Using almost no absolute pixel sizes and none of the usual suspects (clearfix, floats, position: absolute, z-index: 9999 ...none of that crap).
The point of static extraction (something the current wave of new CSS-in-JS libraries seem to be focusing on), is to generate all the style sheets at build time and serve them as regular CSS files — so that your styles don’t inflate your JS bundles (a problem most older CSS-in-JS libraries have).
My point is, the comparison seems more “js runtime stylesheets” vs “regular static stylesheets.” Styled Components, or CSS-in-JS, isn’t really the main focus here. There are options to get the static stylesheets generated, and I predict most CSS-in-JS frameworks will eventually provide the tools to do so.
Both emotion and styled-components have dabbled in supporting static (build-time) extraction, but it's actually a hard problem to solve when you have such flexible APIs. The libraries that have cropped up to support this notion of near-zero runtime CSS-in-JS (Linaria, Astroturf, vanilla-extract, and more) do so by providing tighter constraints surrounding what you can and can't do.
I'll try to answer few common things for everyone's context
Why even use CSS in JS?
- SPA bundling usually loads all CSS at once and all styles collide. You need to be super good at naming stuff/or load CSS based on module. So your CSS will be conflicting, so scoping is helpful here.
- Not having to jump from your component file to CSS file. Save some context switching or few strokes.
- Dynamic control over style. Basically your stylesheet is JS function returning style. You can do anything here. See this https://github.com/styled-components/styled-components/issue...
- Support for JS reference in color, font sizes, etc with editor support. It'll lead to more consistent design system.
- More cleaner API when using media queries, pseudo states. eg-https://emotion.sh/docs/media-queries
Another alternative is Emotion, also I find it to be much more cleaner in code perspective. (Good performant library in terms of layout and painting).
Why is export option not available for most CSS in JS (some suggested this as solution)?
Read this https://github.com/emotion-js/emotion/blob/bcf40cf2c20153481...
Is it worth it?
- Depends a lot on context and your perfonal objective. Generally I personally feel it's worth it as it's good tooling.
- CSS in JS is lot better than what it used to be. Styled component had good perf bump in last few years.
https://www.npmjs.com/package/classnames
In the end, I generally prefer to use tailwind + emotion. My goals usually is to save time and make system more consistent rather than perf gains, which isn't that much on new systems.
You don't need to be that good, and there are methodologies that can help eg BEM.
It gives you in my opinion a nicer style syntax (flat style props on the component) while avoiding the downsides of CSS-in-JS by extracting everything but the most dynamic parts to pure CSS.
The optimizing compiler part was a large investment to get right, especially with theme and media query support. But the main claim to fame is that it also works on React Native, and optimizes there as well, so you get for the first time a really performant way to style native and web apps at once.
https://webcache.googleusercontent.com/search?q=cache%3Ahttp...
https://web.archive.org/web/20210608190243/https://pustelto....
https://github.com/Pustelto/personal_web/blob/master/src/blo...
I'm hoping to eventually find one of these build-time CSS-in-JS frameworks that is smart enough to partially eval ~80% of our `<div css={Css.m4.black.$}>` expressions to be zero runtime.
And, if/when this happens, do this as a seamless upgrade to our existing codebases, i.e. without any lines of `css={Css.m4.black.$}` in our app need to change.
Basically we're using our Truss DSL both for atomic/utility class names today + a decoupling layer to switch CSS-in-JS libs in the future if/when needed.
I think Linaria and https://github.com/twstyled/twstyled (based on/forked from Linaria) are the closest to doing this eval during compilation, but haven't had to dig in so far (runtime emotion has been fast enough for us so far).
Granted, many react apps aren’t serverside-rendered, so these numbers are still useful and interesting. But I would say that if performance is a priority, moving to an SSR solution (or away from React altogether) would have a way bigger impact on performance.
Somewhat superficial content but might spawn a few interesting discussions.
First time I've seen this Cloudflare error. Doesn't their free plan have almost ~1TB buffer before they take notice and ask you to upgrade?
https://developers.cloudflare.com/workers/platform/limits#wo...
The real question is whether we want styling/theming to be in it's own domain specific language. From an ergonomics standpoint, CSS is such a bad language we would rather write JS instead. But the downside of not having it in a more restricted language is that it's much harder to build tooling for it. For example, you won't be able to open up any webpage and know you can inspect and change the style of things. Instead you need to know the specific JS class for that component or ThemeProvider and modify that instead. Every ui framework is going to do things slightly differently which will be a huge blow to user customizability.