A history of how code transformation has affected the industry. And how it led to changes in how we write CSS
This talk has been presented at React Day Berlin 2024, check out the latest edition of this React Conference.
A history of how code transformation has affected the industry. And how it led to changes in how we write CSS
This talk has been presented at React Day Berlin 2024, check out the latest edition of this React Conference.
StylX is a tool for generating atomic CSS, maintained by Naman at Meta.
Early CSS lacked namespaces and variables, making it difficult to maintain and requiring repeated code.
CSS preprocessors like Sass and Less introduced features such as variables, nesting, and loops, improving CSS maintainability and functionality.
The community developed CSS modules and CSS in JS to address namespacing and scaling issues, with each approach offering different solutions and benefits.
Atomic CSS breaks styles into small, reusable pieces, maximizing reusability and scalability. It helps keep CSS size manageable as applications grow.
Tailwind CSS uses atomic styles to create a single small CSS file for the entire application, minimizing the need for lazy loading and improving performance.
While both generate atomic CSS, Tailwind works with almost any language and encourages repetition, while StylX is a JavaScript compiler that promotes reusability and is strongly typed.
React Strict DOM is a project that builds on StyleX to provide a common baseline for writing UI across web and React Native, offering web-like APIs that are not global.
Compilers have facilitated advancements in web development by enabling features like atomic CSS, improving performance, and enhancing developer experience.
Initially, web styling relied solely on HTML with limited capabilities like blink and marquee tags. CSS was later introduced to solve styling limitations, offering more power and reusability.
Hello, I'm Naman. Today, I'm going to talk about atomic power and the story of StylX. In the beginning, there was only HTML without CSS. We used blink tags and marquee tags for styling, which was limited. Then CSS was invented, solving some problems but creating new ones. CSS preprocessors like Sass and Less emerged to address these challenges.
Hello, I'm Naman. I maintain StylX at Mera, and today I'm going to talk about atomic power. And not the nuclear kind, but I'm going to talk about atomic, and I'm going to talk about power. And then using that lens, I'm going to tell you about the story of StylX.
So let's get started from the very beginning. In the beginning, there was HTML and it was the birth of the World Wide Web. And there was no CSS. HTML is all we had. And with that HTML, we used blink tags and marquee tags for some fun, font tags for some styling, and we dealt with table layouts too. That was not fun. This wasn't great, but it was very limited. All kinds of layouts that we do today were simply impossible. And it was very tedious. There was no way to reuse our styles across components. Components didn't even exist yet.
And so in a few years, smart people got together and invented CSS. CSS solved some of our biggest problems with styling in HTML. It was way more powerful, there were actual layouts you could do, and not just with tables. And it was way more reusable. You could define a CSS selector once and use it all over your HTML. But as we solved some problems, we discovered new ones. And we realized that since CSS had no namespaces, it was really difficult to maintain. And since this is early CSS, it didn't even have variables. So we don't have to repeat ourselves in our HTML, but now we have to repeat ourselves in our CSS.
So some more smart people got together, and we came up with a new set of tools. This time, they were CSS preprocessors. Things that would process something you wrote and generate your CSS. You may have heard of Sass and Less. I used to use Stylus, and CSS was CSS in JS before CSS in JS. CSS preprocessors solved some of our biggest problems with CSS, such as variables and nesting.
In the next era, there was a fork in the road. Some moved towards CSS modules, while others moved towards CSS in JS. CSS modules solved namespacing, but had scaling issues. CSS in JS provided namespacing and better developer experience. Compiled time CSS in JS evolved, solving some problems but not all scaling issues. The idea of atomic CSS emerged as a convergence between CSS modules and compile-time CSS in JS.
They also added a bunch of other powerful features like loops and includes. But they still didn't have namespacing, and it was still hard to maintain at scale. And so, engineers kept working. And in the next era, there was a fork in the road. And some of the community moved towards CSS modules, and another part of the community moved towards CSS in JS. The reason that this happened was because this is the era when single-page applications were becoming popular, and frameworks like React had started to come out. And just to be clear, there were many people who didn't move to either of these new tools and continued to use good old CSS or CSS preprocessors. But I'm going to be focusing on every step forward that we take and not continue to keep mentioning that, yes, the old tools never went away.
So, over on the CSS module side, it finally solved namespacing. This was huge. This is why CSS modules is so successful. But it was still hard to scale in some ways. In general, code maintainability had been solved for the most part, but you could still run into some amount of CSS conflicts, and your CSS bloat would continue to bloat as your application gets bigger. On the other hand, CSS in JS gave us namespacing as well, and it gave us a whole bunch of other powers. It gave us much better developer experience than anything we had tried before. You could now write your style in the same file as your markup, in the same file as your component. But most of these early CSS and JS libraries had really bad performance because they relied on runtime style injection. And so JavaScript developers, being JavaScript developers, we kept at it.
A bunch of really impressive libraries came and became successful. And eventually we reached the next milestone, which was compiled time CSS in JS. This was an evolution of the CSS in JS idea, where you don't have the performance overhead because you compile all of your CSS at build time, and you don't have any runtime-style injection anymore, but you still have the same great developer experience, which is why CSS in JS was so successful in the first place. And while it solved so many problems, it did not completely solve all of the scaling problems that we've had with basically every CSS solution we've ever tried. As your application gets bigger, your CSS gets bigger. Then you're forced to lazy-load your CSS, which means your page updates get slower because you're waiting on additional CSS to load and process and compute. And so we continued on. A bunch of more great compile-time CSS in JS libraries came out, like Linarea and Vanilla Extract, and we hit some kind of convergence, where the two streams kind of agreed on the idea of atomic CSS. Of course, not everyone agreed. Many people stuck to CSS modules, Vanilla CSS modules, and many others continued to use the compile-time CSS in JS, or even runtime CSS in JS. But it is important to note that a lot of people from both streams did agree that atomic CSS was a good idea. But what is atomic CSS? As soon as the term is mentioned, this thought pops in everyone's head, this elephant in the room.
Atomic is breaking something into small, reusable pieces for maximum reusability. Compilers and transpilers have played a significant role in achieving milestones, from preprocessors to bundlers. Tailwind is a successful implementation of atomic styles that scales well. Stylix also generates atomic CSS.
We'll address that, but before that, let's talk about what atomic even means. Atomic is to break up something into small, reusable pieces so that you can put them together and maximize reusability. It's kind of like components. And when we find the right way to break up our problems, to find the right atom for each problem, it's usually a successful development. Atomic design was atomic thinking for designing. Component architecture was atomic thinking for UI development. The Unix philosophy decades ago was atomic thinking about an operating system architecture. And functional programming often suggests architecting your entire code base in small atoms of pure functions. All of these are successful strategies and have stood the test of time.
But to get to this point, to get to the idea of atomic CSS, we had to go through a lot of milestones. We had to make a lot of progress. So what was this power that got us to these milestones, that moved us forward, that was always behind the scenes helping us move along? We don't like to think about it, but it's just compilers or transpilers or code transformation of any kind. All sorts of tools from CSS preprocessors and CSS modules and postprocessors are all compilers. JSX and other syntax extensions are compilers. Entire languages like TypeScript are compilers. And even React features like server components and React compiler is a compiler. And we've been using them for ages. We even forget about our bundlers and minifiers, which, yeah, they are compilers. And so compilers have got us to the point where we can start talking about atomic CSS Make it actually nice to use and not just fast to use.
But okay, since I've mentioned atomic CSS, let's address that elephant in the room. And yes, I'm talking about Tailwind. And Tailwind is a bit hard to ignore these days. I maintain Stylix, but I spend more time talking about Tailwind. And to be honest, I don't have anything bad to say. Tailwind uses atomic styles, which scale extremely well. Your application can keep getting larger and your CSS kind of stops growing after a point. And this lets you generate a single small CSS file for your entire application. So you don't need to lazy load CSS on like route updates and slow down something that is already so much work for your browser and your network to deal with. And yeah, as the maintainer of Stylix, we agree. If we flip over to Stylix, you'll see that yeah, we also generate atomic CSS, which scales well.
Stylix is a JavaScript compiler that works only with flavors of JavaScript like TypeScript or Flow. Tailwind encourages repetition and may lead to unintentional style updates. Stylix allows defining styles once and reusing them. Tailwind styles are untyped strings, while Stylix styles are strongly typed objects. Stylix is built for fast style merging.
And we also ship a single CSS file for the entire application. We got there from a different path, we got there from CSS modules. And Stylix happens to look a little different. This is the same exact styles that I showed you in Tailwind before. But we agree on the broad ideas. Except there are some differences, too.
Tailwind, because of how it's designed, works with almost any language. Stylix is a JavaScript compiler. So it only really works with flavors of JavaScript, like TypeScript or Flow. Tailwind kind of requires and encourages a lot of repetition. So it's not uncommon to repeat the same class name on many elements in your markup. This example is actually from Tailwind's own website. And this can be good or bad, but the pitfall is that you may accidentally forget to update one of the five images when you're updating the styles. It's a little hard to create constraints where all the images are the same. There are ways, of course, but it's a bit tedious.
On the other hand, Stylix is a compiler, so you can easily define styles once and reuse them in a bunch of places. And if you need to make a change, you know where to make the change. And yeah. Tailwind lets you define your styles inline, which is convenient for a lot of prototyping and getting there quickly. Stylix styles are not inline, which might feel like a bit more ceremony. Tailwind styles are just untyped strings. There are some tools that show you what those strings mean, but you won't get any safety guarantees from them. Stylix styles are completely strongly typed objects, and you can use them as constraints for your prototypes. Tailwind doesn't really support merging out of the box, but you can use a tool like Tailwind Merge to merge styles at runtime. And it adds quite a bit of overhead. Stylix is built for fast style merging. So, like, yeah, Tailwind is durs, but, yeah, it could probably be harder to read. And Stylix is more verbose, but maybe it's a little more readable. So isn't this just syntax? Like, are we arguing syntax over here? Is that what we're doing? And yes, we are.
Syntax is important to people, and so it's time for a demo. I have a website with a page for talks. Instead of designing from scratch, I'll use an existing design and convert it to Tailwind classes in my StyleX project. Tailwind is working even though I'm using StyleX. You can do more with StyleX than just using Tailwind.
So isn't this just syntax? Like, are we arguing syntax over here? Is that what we're doing? And yes, we are. Syntax is important to people, and so it's time for a demo.
Over here I have a website. It's an XJS website, and it has a page for talks. These are all of the talks I've given so far, and hopefully this talk will make it to this list. And I want to add a proper card in here, and I don't have a design yet. So instead of coming up with a design from scratch, I kind of like this design that's on the ReactConf website already. So I'm just going to take one of these cards that they already have and copy the HTML, and I'm just going to simply paste it right here. And actually, just so it's a little easier, I'm going to paste it in an HTML file, format it, and then copy it, and then paste it here. And of course I'm going to get a bunch of errors, because HTML isn't just JSX out of the box. So I'm going to change my class to class name. I'm going to detect these little inline style tags and just convert them to Tailwind classes as well. Then I can get rid of this. In fact, I can get rid of all of this. And yeah, let's use the props for the image. And let's clean up a bit more while we're at it. And yep, this is display none, so let's just remove that entirely. And let's save it, let's format it, and let's look at what the page looks like. There it is. I didn't have to do anything. So Tailwind is just working, so I must have Tailwind set up, right? Actually, no. This is a project that uses StyleX everywhere. And this Tailwind is just getting compiled to StyleX. And if you want to see what it looks like, this is my input folder, and I'm using the StyleX CLI to compile the entire folder. And if I show you what it's compiled to, that's StyleX. And the StyleX developed debug class names and everything. But, okay, that's kind of impressive, but what's the benefit of this? Like couldn't you just already use Tailwind? Well, kind of, but you can actually do a bit more.
I noticed the cards are small and not filling the grid. The styling is hardcoded, so I'll write StyleX styles for the image wrapper and images. Now the images fill the grid as expected. I mixed StyleX and Tailwind in the same file. I replaced hard-coded data with correct information and added styling for the links.
So I just noticed that these cards are a little small. If I look at the images, you'll see that these cards are in a grid, but the cards are not filling up the grid. And that's because the styling for these cards is hardcoded, and I don't want it to be. But I'm also more comfortable writing StyleX than I am with Tailwind.
So let's write some StyleX styles. So I'm going to write some styles for the image wrapper, where I'm going to make my width 100%, and I'm going to make my aspect ratio 16 by 9, and I'm going to make my height auto. Okay, so I've defined that. How do I apply that? So this is my image wrapper. This actually doesn't do anything, so I can get rid of it. What I can do is I can take this class name, replace this, use my StyleX.props, apply that, but I want to override what was already there. So there's this function called tw that's also exported from the Tailwind to StyleX package. And I can just define that there, and just like that. Yeah, as you can see, my cards are a little bigger, but the images still are not. So let's get to the images next.
Yeah, so I want the same thing for the images, so I'm just going to replace this with the same thing, and let's make sure it's the same styles. And this time, instead of applying the image wrapper styles, let me define an image styles where I have width and height set to 100%. And I pass that in outside the tw function, and there we go. So if you see, the images are now filling up the grid as expected, and it's looking good, and I just mixed and matched StyleX and Tailwind in the same file. I can even add some more styling here. So let's replace this hard-coded data with the correct things. So I already have the correct image. Let's change the link to the top link and open it in a new page. And that's that. And instead of this, I'm going to make a doc.conference, and I'm going to wrap this in a link. And that's looking a lot better, but I don't like the way the links look. So yeah, let's continue with some styling and say color is colors.accent, which is coming from my StyleX team right here, right there. It's one of two colors. And let me apply that. So already looking a bit better. Let's change the underline.
Text decoration should be default to none on hover and focus. The TW function allows defining constants and converting them into StyleX objects. Parsers powered web development progress. StyleX uses a compiler and enables Atomic CSS. Styling on the web took parallel paths.
So text decoration should be default to none, but it should show it on hover and it should show it on focus visible. Actually just focus itself. And text underline offset, let's set that to four. And yeah, that's looking a lot better.
So there's one additional feature that I didn't show so far. And to show it, let me just take one of these things out. So now that you can define your tailwind classes in this TW function, you can just define constants in your components and define them and then use them. Wouldn't it be nice to be able to do this in Tailwind 2? Well, now you can.
And here's the crazy thing. If you look at the type of this object, it's strongly typed. This TW function is able to read the entire string and convert it into the exact StyleX object that it would generate, almost, more or less. And yeah, this is an entire parser written in Tailwind, but it does work.
If I remove a few of these and, for example, this is a bit redundant because it's already 320. And if I just remove these for a second, you'll see, yeah, both the keys and the values are there. And if I show you a different one, yeah, it's all there. So you can do that. And you can now get rid of these redundant class names, which we don't need. We don't need a width. And we don't need a width here either. And we actually don't even need this. So we could probably get rid of this entirely. And yeah, that's looking quite good.
Okay, so that was the demo. And let's wrap up. Parsers have powered the progress of web development. They got us where we are today. And so we decided to use a compiler when building StyleX and to enable Atomic CSS. Styling on the web has taken many parallel paths. At Meta, we went through a CSS module's architecture. The larger ecosystem did many different things.
Atomic CSS is a widely agreed concept among different people. Tailwind and StyleX have similarities in generating output. Tailwind can be used with StyleX. React Strict DOM is a project that allows writing UIs for web and React Native. Cross-platform React talk is recommended for learning more about it.
And many different people have independently arrived at the same conclusion of Atomic CSS. Not everyone agrees, of course, but a large part of the community does. And Tailwind and StyleX are both more similar than they might seem as a result. They both kind of want to generate the same kind of output. And with tools like Tailwind to StyleX and the demo that I just showed you, maybe the best way to use Tailwind would be StyleX.
And before I go, I want to talk about one more thing. And that one more thing is React Strict DOM. And React Strict DOM is a project that builds on top of StyleX to create a common baseline to be able to write UIs for both web and React Native. It's a UI that looks very web-like, and it gives you APIs that are HTML and CSS. It's like, hey, what if web APIs were not globals?
And if you want to learn more about it and try it out and build some applications across web and mobile, I gave a talk about this at React Conf earlier this year. It's called Cross-platform React. So do check that talk out. And thank you. Thanks, everyone. I'm going to go ahead and end the talk. So thank you so much for watching. I hope you enjoyed it. And I'll see you guys in the next one. Bye-bye. Bye.
We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career
Comments