Haven't written frontend in a few years but honestly for me CSS Modules solved the scoping issue and all the CSS in JS/Tailwind stuff since then feels like a red herring.
coxmi · 4h ago
I’d go further than this and say globally-scoped CSS is fine as long as you’re using a decent naming pattern (e.g. BEM), or @layers.
For me, back in the JQuery days, the problem was always globally-scoped JS/DOM, rather than CSS. The big revolution was simply co-locating/importing styles in JS modules during the compile step, which works surprisingly well even outside of any framework.
Just using vanilla DOM or a small wrapper around web components for connectedCallback/disconnectedCallback logic is refreshingly simple. It’s quite sad that most SSR frameworks don’t allow this sort of approach, and lock you in to a specific front-end library.
diggan · 3h ago
> I’d go further than this and say globally-scoped CSS is fine as long as you’re using a decent naming pattern (e.g. BEM), or @layers.
Hear hear. Started using BEM in like 2016, haven't had collision issues since. Not sure why people are so hellbent on doing CSS inside JavaScript when CSS by itself can do almost anything you'd want at this point, even handling basic interactions.
coxmi · 2h ago
Nice to know i’m not alone, I’m sure there are dozens of us — dozens!
theknarf · 4h ago
I've always thought CSS Modules was the best solution as well, having worked a lot with Tailwind its entirely okay, but "CSS in JS" was always a bad idea from the get go.
joduplessis · 5h ago
CSS-in-JS is possibly the worst idea to come out of the last 10 years of frontend. Saying that as someone who has written a lot of CSS-in-JS code.
pimterry · 4h ago
Personally I've started migrating to vanilla-extract (https://vanilla-extract.style/): syntactically & conceptually similar to SC, relatively easy migration and lots of the same advantages, but entirely precompiled with zero runtime and nicely framework & tooling agnostic.
cocody · 2h ago
Hi, I made the forks linked to here :D
We're also migrating to vanilla-extract, huge fan of the runtime type-safety ;)
The SC forks is to buy us time, Rome wasn't built in a day :)
jorams · 4h ago
Sounds like a good development, but some statements in the post are a bit odd to me:
> Your app has styled-components. That's not changing today. But it doesn't have to be slow while you figure out tomorrow.
It always was. You decided to make it slow and were fine with it, not sure why that's suddenly unacceptable.
cocody · 2h ago
When you start out on a small app the slowness doesn't stick out anywhere. App keeps growing until one day it becomes very obvious in perf profiles. At that stage it's difficult to refactor out of it to another system, as a lot of the brownfield might've drifted into relying on techniques that can only be used when inserting CSS at runtime:
- generate `@media` query selectors with ranges based on JS state and nesting.
- calc background color that pass a11y contrast ratio tests based on dynamic foreground colors (for example the color palette generated from an image asset)
Even so, it takes time to refactor thousands of files.
>It always was. You decided to make it slow and were fine with it, not sure why that's suddenly unacceptable.
The decision might've been made years before you joined the organization, and you're now left with that decision and have to plan for what to do about it.
RedShift1 · 5h ago
Maybe CSS in JS was the wrong thing to begin with. Your developers were so preoccupied with whether they could, they didn't stop to think if they should.
breakingcups · 5h ago
I was about to say the same. Having lived mostly in Vue land, this quote felt astounding to me:
"The React team themselves have made it clear: runtime CSS injection will always be slower than statically extracted styles. They recommend using <link rel="stylesheet"> for static styles and inline styles for dynamic values. That's the future."
I thought this lesson was already widely learned since the jQuery days.
Perz1val · 5h ago
"React Andies" have not seen jquery days
cocody · 1h ago
Hi fork author here :)
I've been around since the jQuery days ;)
Important context here is that a big chunk of the popularity of styled-components comes from the ease of distributing npm libraries where you could "just import JS" and not worry about how to load the resulting CSS, it "just worked"™.
Some frameworks, like vite and webpack, have generally good support for npm libraries that import CSS files and know how to load them efficiently so userland don't have to deal with it. In the past that was certainly not the case with grunt, gulp, custom webpack where you have no control over how style-loader, MiniCssExtractPlugin, and how any of that was setup.
For React authors things have improved, the ecosystem have pretty much decided on vite being the way to go, and next.js with turbopack align really well, same about parcel 3.
React 19 itself also makes life much easier for framework authors by allowing libraries to render stylesheet tags from anywhere: https://react.dev/reference/react-dom/components/link#linkin...
This works regardless of how a React app is built and rendered (RSC, client components with streaming SSR hydration, regular fully client rendered app with react-dom createRoot which is how Sanity Studio is deployed).
There's even first class APIs for preloading the stylesheet itself, and any fonts it might have: https://react.dev/reference/react-dom/preload#reference
This matters since npm libraries are often specialized. Maybe it's a code syntax highlighter, it might only be rendered in very specific cases and not for the majority of the users of your app. Does it make sense for its chonky CSS to be added to your static global.css and loaded up front?
What about a charts library, maybe it's only used by your dashboard, seen by authenticated users, and not your marketing website overall?
styled-components worked really well in these cases, as it supported regular JS lazy loading techniques and CSS weren't inserted until it was needed.
While it was possible to achieve these same benefits in an app that you have control over the bundling and chunking, the challenge library authors face is that you don't have any control over that.
With React 19, this changes, though it'll take time before 19 is the baseline for most devs, it's currently 18.
TL;DR I agree with you this statement sounds silly for app builders. For framework authors it's a bit too tongue in cheek IMO.
jeswin · 5h ago
It might seem like a terrible idea now. But when it was first introduced, a) people were still trying to get CSS and Components to work together, React having made components quite popular; and (b) React was actually usable, compared to the complete mess it's become now, especially after they introduced hooks.
I switched to web components for all my personal work.
a_humean · 5h ago
But we already had things like tachyons css (tailwind precursor) and webpack css modules at the time that both offered static stylesheet solutions to that problem.
jeswin · 4h ago
They weren't great either in hindsight.
But more importantly, we need to explore different paths to figure out what doesn't work. Things which seem like bad ideas in hindsight aren't bad experiments to run - otherwise we will all learn nothing.
mootoday · 5h ago
I came to post the same comment :-)!
lloydatkinson · 5h ago
I normally refrain from commenting on anything regarding CSS, styling, and design systems as I feel that HN in particular has disingenuous debates on it.
Rather than using a fork, you could completely remove the “style sheets at runtime” part by using a CSS in TypeScript tool which builds everything and produces plain CSS files and CSS variables.
There are at least four main benefit:
- Can enforce design tokens (colours, spacing, whatever) with type safety
- The previous point helps enforce and encourage design consistency; I have lost count of the amount of times I’ve seen lazy “just append some more unmaintainable mess” at the bottom of CSS files
- CSS is built at build time which is what you are already familiar with if you’ve used a CSS preprocessor before
- Smaller file sizes sent to the users browser
Oh and one final thing: vanilla-extract is not just for React, its standalone meaning you can use it even with something entirely server side if you wished.
VPenkov · 2h ago
I'm really not a fan of CSS in JS, however it does have it's use-cases. Class mangling is very convenient with it and allows you to be prescriptive about how you're doing theming support, which is great when building libraries that 3rd parties embed on their websites.
The trade-off is that of course your customers can't style things you haven't anticipated, but it means you can control what changes are breaking.
And you can always add an extra variable in a new version if a customer wants to change a border color.
cocody · 2h ago
Hi fork author here :)
I agree, the end game should be to get rid of styled-components. At Sanity we are in fact in the process of moving to https://vanilla-extract.style/
At the same time, with thousands of styled components already in use in production, it'll take a while until they're all refactored. And so it makes sense for us to make the best of styled-components and make it as fast as it can be on the React 18 and 19 baselines, to buy us time for the larger refactor that completely solves it.
TL;DR it's better to never insert CSS at runtime and only link to a cached external stylesheet. If you have brownfield code that inserts CSS at runtime that should be refactored. Until they are refactored, they should insert CSS at runtime in the best possible way (even though it's never truly good or fast).
reify · 5h ago
Does this mean I can eat 40% faster when I use a fork.
if so, I shall set the dining table without knives.
there is no spoon!
dalf · 5h ago
You might not have a fork if you dine with philosophers
For me, back in the JQuery days, the problem was always globally-scoped JS/DOM, rather than CSS. The big revolution was simply co-locating/importing styles in JS modules during the compile step, which works surprisingly well even outside of any framework.
Just using vanilla DOM or a small wrapper around web components for connectedCallback/disconnectedCallback logic is refreshingly simple. It’s quite sad that most SSR frameworks don’t allow this sort of approach, and lock you in to a specific front-end library.
Hear hear. Started using BEM in like 2016, haven't had collision issues since. Not sure why people are so hellbent on doing CSS inside JavaScript when CSS by itself can do almost anything you'd want at this point, even handling basic interactions.
We're also migrating to vanilla-extract, huge fan of the runtime type-safety ;) The SC forks is to buy us time, Rome wasn't built in a day :)
> Your app has styled-components. That's not changing today. But it doesn't have to be slow while you figure out tomorrow.
It always was. You decided to make it slow and were fine with it, not sure why that's suddenly unacceptable.
While these have new solutions: - @container queries, no need to fiddle with global viewport size calc. - https://developer.mozilla.org/en-US/docs/Web/CSS/color_value...
Even so, it takes time to refactor thousands of files.
>It always was. You decided to make it slow and were fine with it, not sure why that's suddenly unacceptable.
The decision might've been made years before you joined the organization, and you're now left with that decision and have to plan for what to do about it.
"The React team themselves have made it clear: runtime CSS injection will always be slower than statically extracted styles. They recommend using <link rel="stylesheet"> for static styles and inline styles for dynamic values. That's the future."
I thought this lesson was already widely learned since the jQuery days.
I've been around since the jQuery days ;)
Important context here is that a big chunk of the popularity of styled-components comes from the ease of distributing npm libraries where you could "just import JS" and not worry about how to load the resulting CSS, it "just worked"™.
Some frameworks, like vite and webpack, have generally good support for npm libraries that import CSS files and know how to load them efficiently so userland don't have to deal with it. In the past that was certainly not the case with grunt, gulp, custom webpack where you have no control over how style-loader, MiniCssExtractPlugin, and how any of that was setup.
For React authors things have improved, the ecosystem have pretty much decided on vite being the way to go, and next.js with turbopack align really well, same about parcel 3. React 19 itself also makes life much easier for framework authors by allowing libraries to render stylesheet tags from anywhere: https://react.dev/reference/react-dom/components/link#linkin... This works regardless of how a React app is built and rendered (RSC, client components with streaming SSR hydration, regular fully client rendered app with react-dom createRoot which is how Sanity Studio is deployed). There's even first class APIs for preloading the stylesheet itself, and any fonts it might have: https://react.dev/reference/react-dom/preload#reference
This matters since npm libraries are often specialized. Maybe it's a code syntax highlighter, it might only be rendered in very specific cases and not for the majority of the users of your app. Does it make sense for its chonky CSS to be added to your static global.css and loaded up front? What about a charts library, maybe it's only used by your dashboard, seen by authenticated users, and not your marketing website overall? styled-components worked really well in these cases, as it supported regular JS lazy loading techniques and CSS weren't inserted until it was needed. While it was possible to achieve these same benefits in an app that you have control over the bundling and chunking, the challenge library authors face is that you don't have any control over that.
With React 19, this changes, though it'll take time before 19 is the baseline for most devs, it's currently 18.
TL;DR I agree with you this statement sounds silly for app builders. For framework authors it's a bit too tongue in cheek IMO.
I switched to web components for all my personal work.
But more importantly, we need to explore different paths to figure out what doesn't work. Things which seem like bad ideas in hindsight aren't bad experiments to run - otherwise we will all learn nothing.
Rather than using a fork, you could completely remove the “style sheets at runtime” part by using a CSS in TypeScript tool which builds everything and produces plain CSS files and CSS variables.
There are at least four main benefit:
- Can enforce design tokens (colours, spacing, whatever) with type safety
- The previous point helps enforce and encourage design consistency; I have lost count of the amount of times I’ve seen lazy “just append some more unmaintainable mess” at the bottom of CSS files
- CSS is built at build time which is what you are already familiar with if you’ve used a CSS preprocessor before
- Smaller file sizes sent to the users browser
Oh and one final thing: vanilla-extract is not just for React, its standalone meaning you can use it even with something entirely server side if you wished.
The trade-off is that of course your customers can't style things you haven't anticipated, but it means you can control what changes are breaking.
And you can always add an extra variable in a new version if a customer wants to change a border color.
I agree, the end game should be to get rid of styled-components. At Sanity we are in fact in the process of moving to https://vanilla-extract.style/
At the same time, with thousands of styled components already in use in production, it'll take a while until they're all refactored. And so it makes sense for us to make the best of styled-components and make it as fast as it can be on the React 18 and 19 baselines, to buy us time for the larger refactor that completely solves it.
TL;DR it's better to never insert CSS at runtime and only link to a cached external stylesheet. If you have brownfield code that inserts CSS at runtime that should be refactored. Until they are refactored, they should insert CSS at runtime in the best possible way (even though it's never truly good or fast).
if so, I shall set the dining table without knives.
there is no spoon!
( https://en.wikipedia.org/wiki/Dining_philosophers_problem )