Naman Goel
Maintainer of Stylex, Facebook's not-yet-open-source CSS-in-JS framework. Working on Web Designs System Components at Facebook.
Rethinking CSS - Introducing Stylex
React Finland 2021React Finland 2021
25 min
Rethinking CSS - Introducing Stylex
CSS + superpowers - bloat. How Stylex creates a zero-cost abstraction that gives CSS superpowers.
About Stylex JS
My name is Naman Goel and I'm an UI engineer at Facebook.
So what is Stylex JS? Stylex is a CSS in JS library that is fast, familiar, and flexible. Let's look into what that means.
[00:10] Stylex is fast as it statically generates a CSS file at compile time. So Stylex is really written in JS but not executed in JS. It detects and removes unused styles, uses atomic styles for maximum re-usability and easily generates critical styles. Stylex also uses zero cost abstractions wherever possible. So you only pay for what you actually use.
Next, Stylex is familiar because it has a simple API inspired React Native that lets you declare groups of styles and objects just like you would while declaring class names. You can continue to nest pseudo selectors and media queries within those objects just like you would in your favorite CSS pre-processor. And you can pass those style objects directly to the class name attribute like you would set class names.
[01:31] Stylex also lets you define fallback styles for progressive enhancement. And it avoids using any special syntax when a simple string might suffice for things like CSS variables, gal functions etc. Finally, Stylex is also more flexible than almost any other CSS in JS library out there because it gives you features like an intuitive API for declaring conditional styles. You can simply use ternary statements and other JavaScript expressions. You don't have to learn any new special syntax. Stylex also lets you pass styles between components and compose them in a very predictable way. Further, styles are completely typed and can be constrained with prop types. Stylex is managed to remove all specificity wars altogether. So even if you split up your generated CSS file, you can all load it in whatever order you want and things will work consistently. Finally, Stylex gives you a way to generate CSS variables automatically when you need to create some dynamic styles and it integrates with build tools like Webpack, Rollup and you can roll your own integrations in the future.
What it looks like
[02:57] So what does Stylex look like? This is a simple example. If you're familiar with React Native, this might look quite similar. Even if you're not, these style objects should look fairly similar to defining class names. As you may notice, we use the word start and end instead of left and right, this is because Stylex supports RTL layouts automatically. If you want to add pseudo selectors or media queries, it's quite simple as well. But let's stick to something simple for now.
This simple example generates the following HTML and CSS. There is a simple Div on the left with a list of class names and the generated CSS on the right with one class name for each style. We'll take more into atomic styles later. So for now, let's go back to the code that you would write. Once you've written this code, Stylex comes into play. At compile time, the stylex.create call is removed and the values are replaced the associated class names. The rest is left as is. This is how you should think about Stylex in your mind while using it. The Stylex function works like an object at a sign, but it's able to merge objects deeply. But I said that Stylex likes to create zero cost abstractions whenever possible. And while JavaScript objects are cheap to create and merge, it's not a completely free process. And so in an example like this, when Stylex can see all of the styles defined within the same file, it can optimize further and remove the objects all together and leave you with something like this. Stylex is smart enough to detect that some of these styles were applied conditionally and create the strings accordingly.
Predictable Style Resolution
[05:04] Next, let's look at how Stylex gives you predictable style resolution. When you're writing regular CSS, the order of the class names doesn't matter at all. In this particular case, the class named red comes first and the class named blue comes second. But even if I reverse that order, the box on the right remains blue. This is because the class named blue comes later in the style sheet in order to make the Div red, we actually need to change the order of the class names itself.
This is not completely practical in a production app because you can't guarantee that your CSS will load in any specific order, especially when you want to bundle split it. Stylex solves this problem completely. And gives you a simple rule of thumb, the last style always wins. In this particular case, comes last so the box is blue. If came last, the box would be red. The order of the styles doesn't matter, only the order in which it's applied.
Style Shorthands
[06:14] This even extends to style shorthands. Consider this example, when you're using margin and margin-vertical. Again, notice that you have the ability to write margin-vertical, which defines margin at the top and bottom just like React Native. In an example like this, it might be somewhat trickier to decide what the final style should be. If comes last, we would probably expect the margin of 8 on the sides and margin of 16 on the top and bottom. But what if I was to change the order? What would happen now? In other cases, this might be a little confusing to decide. But in Stylex we could follow the simple rule of thumb, the last style always wins. And so in this case, we can say with certainty that the margin should be 8 on all sides.
Stylex does this automatic expanding all shorthand properties at compile time which means that even though you wrote margin and margin-vertical while coding, at compile time this gets expanded into this. This ability to compose styles consistently lets us compose styles across components.
Composing Styles
[07:40] So say you're defining a component, you can now just accept a prop that is a style object. In this particular case, my component takes a prop called xstyle and you can pass it with any style object defined elsewhere. You can even define multiple style objects and Stylex will automatically merge them. You can even define styles conditionally. The xstyle prop behaves exactly like the Stylex function.
But remember I also said that these styles are all typed. Which means that if you're using type scripted flow, you can provide prop types for the xstyle prop. The most simple case would be the Xstyle type. Xstyle stands for external style. This gives you the ability to pass any arbitrary object of styles. But you can constrain this a little further and define just the keys that are allowed to be passed into this component.
So in this example, my component takes styles of a color and margin, but nothing else. We can take this a step even further and even define the values that are allowed for those keys.
Design Decisions
[08:59] That was a quick tour of what Stylex looks like. Now let's look at some of the design decisions behind Stylex.
First, atomic styles. Regular styles look something like this. You define a class name and you define a whole bunch of properties for it. But atomic styles flips on his head and gives you one class name for each property. At first, this may seem wasteful, but in production, this ends up saving you a whole bunch of tes. When using classical CSS, the number of CSS tes grows linearly with the number of components. When you're building large web apps, such as Facebook, the number of CSS tes can go into megates or even tens of megates. But when you use atomic styles, after a certain number of components, the number of CSS tes starts to plateau. This gives us a whole bunch of performance benefits.
[10:15] Atomic styles makes it so that the CSS file is small enough that we can just load the entire CSS file upfront without worrying about performance. This also eliminates the performance penalty of loading additional CSS at run time. And the browser has a much easier time resolving styles that should be applied as your HTML changes.
Other than the performance benefits, there are some features that atomic styles give us as well. Since every style has its own class name, we can target style.
[10:54] Consider sticky position, sticky position is a very useful CSS property, but it breaks completely, if it's embedded within an overflow hidden element. Now, targeting style, we can look for anything that might have an overflow hidden set on it and then handle that case accordingly.
In this example, I'm looking for any parent that has overflow hidden on it. And if this element is a descendant of that element, we can apply some different styles.
[11:31] Next, consider a value like current background color. CSS already gives us current color which gives us the current value set for text color, but it doesn't give us anything for current background color. Since we use atomic styles, we can just only fill this feature in. Every time a class name is generated for a background color, we can automatically insert a CSS variable that sets the variable to that same color.
[12:01] Next let's look into the tooling for Stylex. Stylex's tooling builds on top of three pillars. Firstly, there is a Babel transform that compiles away some function calls such as stylex.create, and it helps you extract a static CSS file. Next, there is an ESlint plugin that validates your styles and detects unused styles. And third, static types. If you choose to use them, validate that your style objects that you pass as props are correct, and that when you compose styles, you're composing style, you're composing style objects created using stylex.create and not other objects.
Developer Experience
[12:52] Finally, let's look at the developer experience. Stylex tries to give you the best developer experience possible. And so Stylex will work even if there is no tooling setup. When you're ready, setting up a build process will help you be production ready. Stylex also supports fast-refresh out of the box.
So you may be wondering when you can use it. Stylex was initially made for the new It has since been adopted Workplace and WhatsApp and Instagram is currently in the process of adopting it. But Stylex is not quite ready for prime time. We're working on it hard and we expect to open source a beta this year. We can't wait to get it into your hands.
[13:51] To recap, Stylex gives you a simple React Native like API. It is fast, familiar and flexible. It builds on top of a Babel, ESlint and static types. It uses atomic styles to give you performance and unique features. And it should be open source this year, hopefully. Thank you.
[14:24] Manjula Dube: Thank you. That was a wonderful talk Naman. Thank you so much. Okay. We have a few questions now. So we have one question from Andrei: ''How does Stylex guarantee that the last applied style always wins in two different components which apply styles in different ways?'
[14:46] Naman Goel: So can I ask for a clarification on this question since you're right here? When you say two different components, do you mean when style are being passed in as props or do you mean two separate components that have nothing to do with each other?
[15:04] Andrei Pfeiffer: Let's say you have two different components, like your example with the red and blue classes.
Naman Goel: Right.
Andrei Pfeiffer: So one component applies them in one order and the other one in reverse order.
Naman Goel: Correct.
Andrei Pfeiffer: The style that describes your style sheet will be like static.
Naman Goel: The same, yeah.
[15:19] Andrei Pfeiffer: The first one that is encountered, but the other component would like to display them in reverse order, but that won't apply out of the box. So I don't know if that's something that you handle or Stylex handles.
[15:35] Naman Goel: So yeah. So Stylex does handle that. And it's, I guess I should have spent more time on that. But at the very beginning, one stylex.create has been extracted out and you basically have these objects. Essentially what you're left with at run time, at least for thought pro... Just to think about that, it can be more optimizations are these objects which have keys, like color on the left and class names on the right. And when you apply those class... When you apply those objects onto a Div, you're merging these objects. So at any time, you will only ever have one class name that defines a certain property. So it's impossible for there to be two colors defined in two separate class names, both being applied to the same div at all. You'll only get either the blue class name or the red class name. You'll never get both on the actual Div. Because when you merge the two objects, the keys will clash and the last one will win. It's exactly like an object or assign.
Andrei Pfeiffer: Okay. Right.
[16:50] Naman Goel: So the idea was to basically give you an experience that is as close to inline styles as possible without the slowness of inline styles.
Manjula Dube: Cool. Thank you for that. Let's see if we have some more questions.
[17:16] Andrei Pfeiffer: So I guess that you mentioned something about server side rendering. And Stylex, I guess, supports server rendering. And I'm curious if you did some measurements regarding the overhead, I'm talking about file size now, you basically, you say the CSS file is vastly reduced. But I don't know if you measured the increase in, probably the increase in HTML in the render, in the server side render content increase. Does that at the end... I mean, did you get a benefit at the end like an overall decrease in file size?
[18:02] Naman Goel: So I guess like a Facebook is kind of bad example for this. Because before Stylex, we were using something that kind of looks like a very plain dumb version of CSS modules. And we don't use Webpack or anything like that internally. So before Stylex, the total CSS that had, was in like tens of megates. Not that it would all load, but if you kept on clicking on more and more pages, it would keep loading additional CSS. And it was possible to get into like more than 10 megs of CSS is on the page. With Stylex, we are under 200 kilotes before gzip which is pretty good. And as of right now, we just load the entire CSS file upfront which is like, if we need to, we could optimize it further. But in practice it's been totally fine because you paid maybe like a small amount of extra penalty upfront to just load all the CSS, but then you never have to worry about it again. And when... And the HTML itself is bigger, but only when it's not gzipped because there's so many repeated class names all over the place. It actually gzipped extremely well. So the increase in HTML size is actually quite small. And one thing that I kind of touched on is with something like Facebook, where there are pages where there's like five things going on at the same time, there's a header, there's a chat window and three columns. When you have a complicated page like that with a lot of HTML, loading additional CSS was actually a performance bottleneck for us. So none of the HTML changed, maybe like one tiny little dialogue opened and it brought in an additional CSS file. And that was a performance bottleneck. Because as soon as you bring in new CSS on page, the browser has to recheck everything on the page to make sure that the same CSS still resolves for each class name. It needs to make sure that nothing changed because of new CSS on the page. And the fact that we don't have to load new CSS all the time completely removes that problem.
Andrei Pfeiffer: Got it. Thank you.
[20:34] Manjula Dube: Naman, I have a quick question for you. So how are the teaming support and how do you handle like the global CSS with Stylex?
[20:45] Naman Goel: So that's something that I was trying to learn from Mark on, but we had something that was a little more similar to that. And we needed kind of a complex solution because we were still supporting IE11, which doesn't have CSS variables. But we've recently dropped IE11 so we are investigating if we can do something better. As of right now, we don't do anything which basically just means that we define CSS variables in a plain CSS file before the Stylex generated file and then we just use those variables as strings as is. So theming is... global theming is supported, but Stylex doesn't do anything special to help you with it. You just do what you would do with Vanilla CSS.
[21:40] Manjula Dube: Thank you for that Naman. Mark, I would like to know your views on Stylex.
[21:46] Mark Dalgleish: Yeah, I think it's really interesting to see how many different trade offs there are to make, if you're trying to tackle this problem. What I think really interesting about Stylex is it's trying to be... The way I look at it is it's really going for the least amount of trade off from the developer experience. It tries to make it feel as if you're not doing anything static built on. It makes it like, you look at the code and the ergonomics of it, it feels like Naman said, "it feels like inline styles". And obviously that's a harder problem to solve then. So there's trade offs then around what the tooling has to do. So a good of one big benefit of that Stylex has over Vanilla is that with Vanilla, you are very much still in that world of like, having to worry about specificity and if we swap the order of class names, it doesn't do anything, right. And so there are ways to solve that. Like what we were doing with Sprinkles is a good example of where we sort of build abstractions on top that kind of force you into a system where you can't apply the same, like competing styles. But it's really interesting. I think seeing how little they're compromising on the developer experience.
[23:13] Manjula Dube: Andrei, do you want to add something?
Andrei Pfeiffer: I'm just waiting for open-source so I can...
[23:23] Naman Goel: So to comment on Mark's comment, we had actually a very different experience internally with engineers where engineers were more willing to forego features entirely then change the developer experience. So when Stylex was new, it didn't do any composition, it didn't do almost anything like all the styles had to be local to the file. So the best case scenario for performance was the only possible case. And engineers would rather have that than an API that was more complex. So we had to find ways to add features without making anything harder for anyone.
[24:14] Manjula Dube: Naman, the last question for you, what do you think about Vanilla extract?
Naman Goel: No, I think Vanilla extract is like really fascinating. I saw it a while ago and I was like, this is interesting, but I don't fully get it. And then I've heard Mark talk twice now and every time I hear him talk like Vanilla extract makes complete sense, like before this talk even, and then I wasn't completely sure how Sprinkles worked and that makes more sense now. And I got to ask him the question of how it works and that is what made it click for me because I was like, I get what it does, but I don't see how. And it was like, now it makes sense. The curtain has been lifted. And I find it really fascinating because as I said I feel like we are chasing very similar goals from slightly different directions. And I'll definitely be trying to learn from Vanilla extract. I'm already thinking about how to do teaming in a similar way, but with the Stylex limitations, design constraints.