DEV Community

Cover image for How to Make CSS Reactive!
Adam Crockett ๐ŸŒ€
Adam Crockett ๐ŸŒ€

Posted on • Updated on

How to Make CSS Reactive!

So picture the scene, we are at a tech conference in your home city, and a strange developer comes up to you, (that's me) I say to you.

Hey, is your CSS Reactive?
...

๐Ÿคจ You might think some of the following:
Was I supposed to answer that, there was a long pause, this is awkward.
What is reactive?
Why would you want to make CSS Reactive?
And finally...
Hey stop putting words in my mouth.

What is reactive programming?

This is a big topic but to sum it up without doing it much justice.. Some data changes and JavaScript responds to those changes, the data is bound to a function like a stick and a seperate pointy end is bound by lashings of glue. But we are not going to talk about survival on a desert island today, mores the pitty.

Why would I make my CSS Reactive?

I hear you, you might think it's some gimmick maybe or just adding complexity?
I have thought about this for a while, I have came to the conclusion that the standard model of styling individual elements and expecting the end result as whole to be consistent is complex, having styling update on user interaction whilst also handling business logic side effects are separate issues and therefore more complex than the single way to handle styling logic that I will present.

What if I said. Stop styling DOM elements! Too far? how would you make beautiful UI's?

Yeah that's a bit silly, but actually, is it? CSS custom properties have been around for a while now, they allow you to style what you might consider a pointer of sorts rather than an element to style. I feel that variables the best way to produce repeatable results in any language, that's what you need for a consistent design right? Now to be clear I'm still saying style classes etc, as normal but use more variables at runtime when the need arises, il get to that

Not only that, custom properties can have several useful behaviors.

  • scope reduction when applied to elements as roots
  • real-time changes
  • custom syntax
  • store data CSS does not understand
  • quick to compute

Tip:

If using Sass or any preprocessor, CSS properties have a use at runtime and should not be overlooked. Infact you can build custom properties from sass variables which I have covered in the past for insane results.


Okay back to the story; You have gone home now, pondering what on earth I was talking about, re.css, what's that?

Here's what I hope will show up one day when you Google re.css.

re.css is style guide (TODO) and accompanying library which branches off from css-in-js, it's goals are to provide enhanced performance, split responsibility and improved UX.

Split responsibility?

If you have ever styled a single thing in JavaScript, be it through vanilla, React, Vue or Angular, you are going to have something like this happen.

  • HTML loads and parsed
  • CSS loads parses and renders by looking up the DOM tree for selectors. Actually there is more to it via the CSSOM but il get to to that another time
  • JavaScript loads a few scripts, maybe your bundles or ESMs and then renders strings of HTML
  • If string HTML contains CSS then that gets applied as before

Phew that was actually a really simplified description of the loading a typical SPA without SSR and even that was a-lot.

It's almost like doing the same thing twice, this is what you might then see, a flash of horrible white un-styled content which nobody ever wants to see, your users want to see something, anything, just not that. It's the equivalent of a hotel staff member spitting at you, not very welcoming really ๐Ÿ˜ฑ.

Well there's SSR, server side rendering generally helps because the first part of the render where your js's HTML and CSS is not loaded, suddenly it is, that unifies the process a bit. But there's a cost at your expense, you then have to go and use another framework specific to your chosen architecture to enable SSR.

What if we did things differently, we set a CSS variable in a stylesheet then style placeholder element where our component renders, once JS has loaded, we hand over responsibility of that variable or a set of variables to JavaScript, because this is the bit I neglected to mention, CSS custom properties can be interacted with using JavaScript! You would see something that could bare ๐Ÿป some resemblance to your content before loading in that content. No more flash of un-styled content.

But it gets better, if all your CSS variables and styling mostly reside in stylesheets, all you need to do is set the property on the JavaScript side (I mentioned they are pointer like when used by scripting) and let the browser handle the rest, there is no further computation from JavaScript required.

But that's not true I hear you cry, "the reason I use CSS-in-JS is so I can compute styles based off of other styles or data." Example: if background is grey then border blue and width is 3px on a Tuesday. Well as it turns out CSS is kind of logical thanks to calc and we can still keep responsibility on the css side.

Calc

You can do ALOT with pure CSS that's for sure, when you first see the calc function your mind races, this is mathematical logic in real time, amazing! Then it hits you, JavaScript has access to way more stuff! CSS can't tell me where the mouse cursor is or the ambient light around me, so the logic is a bit limited to what is inside my stylesheet and viewport.

That's why we have these pure CSS vs JavaScript debates, re.css say something else, let them both be friends and allow each to play to thier strengths rather than one assuming the responsibility of the other.

There are a few things to know before we continue.

/* calc can be nested */
/* CSS custom properties are valid calc values */
calc( calc( var(--the-room-is-dark) * 200) - 20);
Enter fullscreen mode Exit fullscreen mode

You could certainly feed CSS variables from JavaScript and compute with calc, I think it's viable for smaller calculations. The above example had magic numbers though, ideally all the values would be variables.

Okay so it looks like we moved a good chunk of our styling logic into stylesheets but what about the other way around, I mentioned that you could store values in CSS that CSS doesn't understand, why would I want to do that?

Take this example:

:root {
    --current-theme: light;
    --theme-text-color: #000;
    --theme-bg-color: #fff;
}
Enter fullscreen mode Exit fullscreen mode

CSS cannot directly use the current theme value but it's best bud JavaScript can. Your JavaScript could then set dark-mode and then switch the colours accordingly through more CSS custom properties.

The trouble is, media queries and dev-tools, supports rules and other external factors can change the value of a CSS variables and JavaScript then become out of sync, there is no event listener for onPropertyChange, so I wrote a simple yet powerful library to add reactivity to those changes (caveat you must handle the media query or mousever from the JavaScript side), also it helps with getting and setting none reactively as well.

Imagine external changes just inexpensively cause JavaScript to fire functions which sets off a beautiful chain reaction of styling logic, that's essentially what I have published (but CSS cannot directly trigger the observable, JS has to handle that, see the comments) regardless, it does 90% of what I set out to do and there's always 2.0.0, anyways I want to share with you.

GitHub logo adam-cyclones / reactive-css-properties

Set css custom properties and react to changes in realtime from JavaScript

Reactive css logo

Reactive CSS Properties

A tiny library to supercharge your styling workflow. With Reactive CSS Properties (re.css) you can set css custom properties and react to changes in realtime from JavaScript

Website - Report a bug - Supported frameworks

Table of contents

The case for re.css

You can think of modern JavaScript having two main responsibilities, updating business logic and updating styling, The trouble with the latter is that this adds extra overhead and new problems to overcome, re.css sets out that it is css's responsibility to update styling and JavaScript shouldโ€ฆ

There is a lot more to cover but this post is getting long, next time let's build something, maybe style a TODO app with ideas from re.css

Top comments (11)

Collapse
 
joelbonetr profile image
JoelBonetR ๐Ÿฅ‡

I view this as adding bunch of third party code as a possible solution to a bad practice instead on working well.

If you already mapped your CSS properly you already have the properties and values that points to elements and states; most of them can be handled with plain CSS and others could require js to add or remove a class. This last action does not modify the CSSOM and does not force an entire sequence of render-painting (when removing) or isolates the new element styling to render it fast (when adding) and re-painting the entire tree (don't mind how this plan to avoid that).

Also transpiling scss to css on the fly for getting dynamic values into properties is a bad practice for performance and there's no reason for that, Sass have the potential for managing it easy on development time and you can manage a "dark theme" by simply transpiling two versions of your Sass with a single variable change, then checking a cookie with user preferences and dealing one stylesheet or another (or a tree of them inside a directory) for example.

The tests I did with react, preact, angular and plain js lead me to use well structured and scoped scss (transpiled in the deploy process) and loading it across the component (usually those frameworks do this by default, just pointing it in case of plain js).
Also and following for performance concers, taking advantage of HTML states (CSS Pseudos) instead on using JS for anything related with styling help a lot (accordions, modal windows, form inputs instant feedback, tabs...) All done using CSS only (Sass on dev time).

I didn't catch the point where this re css can help to anything but I'm interested on knowing further details, could you please add more detailed information about what is intended for, with use cases and examples?

I would like to see if there's a way on getting the best performance or if this solves an issue I'm not noticing at this point (I'm sleepy at the sofa to be honest and it's quite possible ๐Ÿ˜…)

Much appreciated

Collapse
 
adam_cyclones profile image
Adam Crockett ๐ŸŒ€

I'm not really following your comments but that might because I am also sleepy and in bed. Il take a look at this tomorrow and see if I can help unravel this a bit. However your comments about scss on the fly, I really didn't say anything about that. Thanks for the interest, il be back tomorrow.

Collapse
 
joelbonetr profile image
JoelBonetR ๐Ÿฅ‡

Yup, i was only pointing different practices i saw some people apply as an approach to dynamic styling, you can forget all about I just don't get what does re css solves and why I could need it (performance, faster development...? And why?)

Thanks! Sweet dreams ๐Ÿ˜

Thread Thread
 
adam_cyclones profile image
Adam Crockett ๐ŸŒ€

Morning, so lets break this down, firstly I want to disclose that I am not always correct but I am trying to present an alternative way of working which I have used and enjoyed, anyone who hasn't tried the library, give it a whirl then come back to me so we can make it better. There are 11 issues to work on, also I have put my own time into this, I believe in this and I think its worth pursuing. I don't expect the points to be liked by everyone nor understood without the documentation and proofs as I say in the readme. I could use contributors who want to explore this.
Its very hard to get a library off the ground and I see a lot of negativity towards different ideas which makes me a bit sad.

I view this as adding bunch of third party code as a possible solution to a bad practice instead on working well.

A bunch is a strong word, re.css is a 0 dependency library which using nothing that your browser doesn't already ship, it weighs 71.9 kB because it ships both CJS and ESM so you can cut that figure in half, its really quite small, you don't have to use the library, its just a nice interface intuitive (I hope).

If you already mapped your CSS properly you already have the properties and values that points to elements and states; most of them can be handled with plain CSS and others could require js to add or remove a class.

re.css is already plain css, there is nothing un-vanilla about it css will soon be able to register @properties as the lines become increasingly blurred. I state explicitly in the readme that you probably should declare variables before any component loads, you should do this via stylesheets. Classes could be used as mixins and I really didn't say anything against using classes with variables attached. But equally you have more granular control styling variables directly which is nice, more control.

This last action does not modify the CSSOM and does not force an entire sequence of render-painting (when removing) or isolates the new element styling to render it fast (when adding) and re-painting the entire tree (don't mind how this plan to avoid that).

The CSSOM existed before the low level API's perhaps I am mislabeling it. To put it another way There is a css parsing and computing engine in any browser, for instance this would probably lead off to some c++ code to do such parsing and computing work, by making any changes such as adding a class, lets just say, you are doing work which causes a repaint.
But that is not what I had in mind for a theoretical performance improvement, adding a class in the DOM vs updating a css variable, I dont know which should be a little faster yet, I suspect its variables given that this is the single place you would edit. I know that I need to run perf tests before I can proove or disproove my point, and thats what I have been working on, adding E2E and perf.
For the CSSOM I wanted to add support for this developer.mozilla.org/en-US/docs/W...
Brining true immutability, custom typed syntaxes and other nice things that you cannot achieve any other way.

Houdini is going to be big and that is an advantage to using this technique, you already have your values ready to feed into a paint work-let for example.

Also transpiling scss to css on the fly for getting dynamic values into properties is a bad practice for performance and there's no reason for that, Sass have the potential for managing it easy on development time and you can manage a "dark theme" by simply transpiling two versions of your Sass with a single variable change, then checking a cookie with user preferences and dealing one stylesheet or another (or a tree of them inside a directory) for example.

As I said, "Also transpiling scss to css on the fly for getting dynamic values into properties is a bad practice for performance and there's no reason for that", umm yeah thats right, not mentioned anywhere in the post, I mentioned building up variables with mixins. --my-var: $my-var;. This isn't bad anything.

"dark theme" by simply transpiling two versions of your Sass with a single variable change, then checking a cookie with user preferences and dealing one stylesheet or another (or a tree of them inside a directory) for example.

Yeah a dark theme is a pretty contrived example yet understandable use case, it is not the only use case and sure you could build the file, Im not arguing with that. Here is a good use case I have a three.js model that needs to be styled with the same colours as the css in my design, I need to changing the lighting of the model and sync up the animations, The model is a watch, with lume hands, when it goes dark, the lume should light up. Now in this case getting the css properties controlling such things as animation timing and current color, and using them in the css and also within the canvas, thats trivial, updating the styling is also trivial.

The tests I did with react, preact, angular and plain js lead me to use well structured and scoped scss (transpiled in the deploy process) and loading it across the component (usually those frameworks do this by default, just pointing it in case of plain js).

This sounds like you tested my library? Anyways yes building scope into a template is great but its still going to be evaluated at run time so I think we are still equal apart from, I have less overhead, the document from re.css would be lighter.

Also and following for performance concers, taking advantage of HTML states (CSS Pseudos) instead on using JS for anything related with styling help a lot (accordions, modal windows, form inputs instant feedback, tabs...) All done using CSS only (Sass on dev time).

I don't follow :/ sorry, I really tried but I dont, just try the library and tell me how you would work with it.

I didn't catch the point where this re css can help to anything but I'm interested on knowing further details, could you please add more detailed information about what is intended for, with use cases and examples?

Ouch, okay, I feel it has plenty of uses and I think you might have some misconceptions about what Im saying perhaps because this post is light hearted and less technical. The readme is more detailed, the issues are more telling and the docs / style-guide are on their way! I hope you follow along and stay skeptical, I really need opinions like yours.

I would like to see if there's a way on getting the best performance or if this solves an issue I'm not noticing at this point (I'm sleepy at the sofa to be honest and it's quite possible ๐Ÿ˜…)

We were both tired, I promise, the library probably does something but Im not in a position to proove it yet.

Much appreciated

Thread Thread
 
joelbonetr profile image
JoelBonetR ๐Ÿฅ‡

"I think you might have some misconceptions about what Im saying" yes, totally agree ๐Ÿ˜‚

I didn't tried your lib yet, i was only trying to figure out why I should need it. I think I understand better ehat you are talking about specially referencing to Houdini, I'll take a look at it when possible (I'm hands on various projects so I've not much time left).

I'm working through performance as first need with my colleagues since some months ago and I'll try to check your source code and make some tests.

The tests we already did was using some libs like Material vs custom styling and inside custom styling we tried some different methodologies to achieve the best performance possible, meaning to deal as few weight as possible to the client and getting as few execution timings and resources wasting. With this in mind, Preact is better than React or Angular, also svelte has a nice way to work from my POV, hope it and it's community grows more for being able to present Svelte demo as valid
framework for a long term application on production (as it manages DOM instead on generating and comparing virtual DOMs the resources needed are way less).

By the other hand I couldn't find a better way to style the Apps than using CSS for all the possible use cases, this is what you said you don't follow, no problem, take a look at my profile and find some articles about "CSS only interactive components". I use :target pseudo for showing/closing modal windows, :checked for dealing with tabs and accordions etc instead on using JavaScript for that.
This permits quick user interactions as the CSS is pre-rendered, no re-rendering nor re-painting here.

At this point you must note that avoiding JS is the key for performance, CSS is loads faster to load and execute and it's API growth well those years, I hope it will grow more on the following for this reasons. Then of course you need some kind of strict pattern and architecture design into your stylesheets to avoid common issues (need for overrides, proper inheritance and so); a mix of BEM methodology with some modifications is what I found better to balance a clean development with a good performance (I'll write a post about in the following days).

I'm a little skeptical about adding libs because we managed to generate a production preact App which uses a custom css framework created by my own with preact components and custom styling that weights less than 400Kb in total; adding a lib which weights 75kb + is a huge percentile weight addition.

Anyway as I said I'll take a look for sure and try to figure out the use cases and thinking about this out of the box objectively (if you already tested it for specific use cases on which you know it helps, please tell me to reproduce it on my tests).

I'll read the readme and the info you linked tomorrow as late ๐Ÿ˜

Collapse
 
arqex profile image
Javier Marquez

Interesting article! Don't you think that if css variables makes the js ones change, we are breaking the unidirectional dataflow? Usually the changes goes from Js to Css, if we allow the other way around we can have infinite update loops and unexpected changes on the data side (js side) difficult to track.

Collapse
 
adam_cyclones profile image
Adam Crockett ๐ŸŒ€ • Edited

Yes that's a valid point and well spotted that the flow changes, I think it's a pro in my eyes to be able to inform js from CSS but I have done some testing and there's one thing I haven't got right about my own library ๐Ÿ˜ณ (I need to amend this, we all make mistakes at midnight), since we are watching the style attribute of the given root, the only things that even trigger the observable is something that changes the style attribute, eg devtools or JavaScript, which means that CSS can't trigger a observable callback, unless I did some polling on computed values which I think is maybe an option, but like everything in the library, it's opt in. So the reality is:

  • only overloaded variables or new variables from the library trigger changes
  • devtools does too
  • media queries don't, but match media API will do from the Js side
  • CSS can update the value of a variable but this will not fire the callback and so is is unaware ๐Ÿ˜… going to need to solve this but also il take into account the loop problem
  • JS can manually get the value of a CSS variable from say a mouse over or resize which kind of sidesteps the problem listed in the point above.

Dilemma resolved?

Collapse
 
adam_cyclones profile image
Adam Crockett ๐ŸŒ€

Amended the post, thanks for reminding me ๐Ÿ˜…

Collapse
 
adam_cyclones profile image
Adam Crockett ๐ŸŒ€

Wrote on my phone at midnight, there will be typos, I will fix tomorrow.

Collapse
 
stu_cx profile image
Stu Cox

CSS is already reactive.

Collapse
 
adam_cyclones profile image
Adam Crockett ๐ŸŒ€

Very good, yes it is but not from JavaScript's perspective which is what this was all about