DEV Community

Cover image for What does StyleX solve?
Dušan Perković
Dušan Perković

Posted on

What does StyleX solve?

Facebook/Meta recently released the StyleX library, and already there are many articles and youtube videos explaining how it's THE TAILWIND KILLER (ooh, spooky).

I'm going to go through some of the main points from this video by Web Dev Simplified to try and explain why I'm not convinced of the StyleX hype. If you're not familiar with the StyleX library, it's a nice way to get introduced to it. This channel has a lot of great videos, even though I don't agree with his take here.

So let's start with the first big StyleX feature:

Co-location

Image description

Co-location is the practice of closely grouping things that belong together. So why not keep styles in the same place where you define your component code?

I have nothing against this approach. In fact, I'm glad that we are embracing this principle more and more. But this implementation honestly seems like a step back from what Tailwind is doing. If we take a look at the example:

import { Button } from "./Button"
import * as stylex from "@stylexjs/stylex"

const styles = stylex.create({
  heading: {
    color: "blue",
  },
})

export default function App() {
  return (
    <>
      <h1 {...stylex.props(styles.heading)}>Heading</h1>
      <Button>Button</Button>
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

Compared to how we would write it in Tailwind:

import { Button } from "./Button"

export default function App() {
  return (
    <>
      <h1 className="text-blue">Heading</h1>
      <Button>Button</Button>
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

Seems like the Tailwind implementation is actually better with co-location. The classes that impact the element are written directly on the element, instead of somewhere above or below.

The difference in the example is small, but imagine defining styles for a more complex component. With StyleX, you would have a big chunk of CSS-like code at the beginning of your file (as is visible later in the video), in which case you would have to keep scrolling up-and-down to find and change the styling for a given element. With Tailwind this is never the case, because you only change the classes on the element you want to change itself.

At that point, it might actually be helpful to move the StyleX code to another file, so you can keep the styling code open side by side with the component code. And if you're doing that, you're basically just doing CSS Modules, so I'm not really sure how this is better?

Deterministic resolution

Image description

"The last style applied always wins."

Conflicting CSS selectors can be a pain, especially if you don't use a convention like BEM or something similar.
If you use Tailwind however, you never have these specificity selector issues, because the classes are atomic, and applied directly on the element in the HTML.

So unless you are just randomly creating selectors, without any convention or structure, and without scoping things with CSS Modules, I'm not really sure what this solves?
If you are, then I don't think StyleX is gonna help you much. You have an organizational problem, not a tooling one.

Bigger projects are easier to maintain with StyleX.

Image description

I don't really think this is the case. Actually I think on larger projects, StyleX will suffer from almost the same issues that CSS does.
One of the biggest issues with CSS on bigger projects, is knowing if a defined selector is still necessary, or if it can be removed from the stylesheet. Bigger projects tend to have a lot of selectors which get applied in multiple places, so knowing if a selector should or shouldn't be removed can be tricky.

Since "The last style applied always wins." (just like the selector with the most specificity wins in plain CSS), all the styles that are applied until the last one may or may not be partially needed. So on larger projects you will still have the issue described above, just with a different tool. It definitely helps a bit that these useless styles are stripped in production, but they still remain in the codebase and make maintenance harder.

Reusability

Image description

I really don't think styles should be shared between components, expect maybe in very specific cases. A components style is an integral and identifiable part of it. Trying to reuse it, or a part of it just creates extra dependencies which makes things weird, and long term project maintenance difficult.

Let me try to explain what I mean through this example: Let's say we have a simple button with a little bit of styling on our project:

import * as stylex from "@stylexjs/stylex"

export const styles = stylex.create({
  base: {
    backgroundColor: "blue",
    color: "white",
    borderRadius: ".25em",
  },
})

export default function Button(props) {
  return (
      <button
        onClick={props.onClick}
        {...stylex.props(styles.base)}
      >
        {props.children}
      </button>
  )
}
Enter fullscreen mode Exit fullscreen mode

And let's say that at some point we need to define another button, which should be the same as the original button, but a little bit different.
So we might do something like this:

import * as stylex from "@stylexjs/stylex"
import {styles} from "./Button.jsx

const newStyles = stylex.create({
  base: {
    backgroundColor: "red",
  },
})

export default function DeleteButton(props) {
  return (
      <button
        onClick={() => {
          if (confirm("Are you sure?") {
            props.onClick()
          }
        }}
        {...stylex.props(styles.base, newStyles.base)}
      > 
         {props.children}
      </button>
  )
}
Enter fullscreen mode Exit fullscreen mode

This works, but puts the DeleteButton component in a weird spot. Co-location is effectively broken, because the styling for DeleteButton depends on the styling from the Button component. A dependency is also introduced, which means that any future style changes to the Button will also impact the DeleteButton. So your boss and QA are gonna love you, for making their job twice as difficult.

We could also extract the styling to a separate file like a button-styles.js:

import {styles} from "./Button.jsx

export const styles = stylex.create({
  base: {
    backgroundColor: "blue",
    color: "white",
    borderRadius: ".25em",
  },
  delete: {
    backgroundColor: "red",
  },
})

Enter fullscreen mode Exit fullscreen mode

But at this point we are just writing CSS with extra steps, so I don't really see the benefit?

A simpler approach would be to just keep the styles unique to each component, but at that point we have no need to share the styles, so reusability becomes a moot point. And since Tailwind is better with co-location as discussed above, it could look something like this:

export default function Button(props) {
  return (
      <button
        onClick={props.onClick}
        className="bg-blue text-white rounded"
      >
        {props.children}
      </button>
  )
}
Enter fullscreen mode Exit fullscreen mode
export default function DeleteButton(props) {
  return (
      <button
        onClick={() => {
          if (confirm("Are you sure?") {
            props.onClick()
          }
        }}
        className="bg-red text-white rounded"
      > 
         {props.children}
      </button>
  )
}
Enter fullscreen mode Exit fullscreen mode

This is my preferred approach, as it keeps things simple, and components contained and independent of each other. And your boss and QA not hating you is a nice bonus.

Conditionally applying styles

Image description

This feature is nice, but libraries like classnames, and clsx already solved this a long time ago.
If you are using Tailwind, you might be worried that your code won't look as nice if you have a bunch of classes on your element tags.

We are not in the business of making code look pretty, and satisfying your aesthetic wants for numbers of characters on screen per file.

We are in the business of building maintainable solutions that are as simple as possible, and easy to maintain.

Stop trying to "make code pretty". It doesn't have to be pretty, but it should always be expressive.

Overriding styles from parent components.

Image description

Do not do this. Do not pass random styles around to components.

Not even once.

Unless you are making a component library akin to headlessui you should never just pass random styles to components.

A better solution is to create an interface from the child component via props, where you can specify which version of the child component you want. If we use the button example from above, and the classnames library:

import classnames from "classnames"

export default function Button(props) {
  return (
      <button
        onClick={() => {
          if (confirm("Are you sure?") {
            props.onClick()
          }
        }}
        className={classnames(
          {
            "bg-blue": !props.variant
            "bg-red": props.variant === "delete"
          }
          "text-white rounded"
       )}
      > 
         {props.children}
      </button>
  )
}
Enter fullscreen mode Exit fullscreen mode

This way the styling stays internal to the child component, consistent across the application, but still flexible based on the variant property.

Final Thoughts

As the title says, I am not sure what StyleX is supposed to solve, since most of the things that it solves have better solutions in other places.

If you're starting a new project, just use Tailwind, and you're already winning.

If you already have a codebase in CSS, I don't see the need to switch to StyleX, or how it would "evolve" your styling. If you have some of the issues described above, try adopting a methodology like BEM, and using CSS Modules for scope isolation. It's easier then rewriting everything in StyleX, and you'll have more maintainable code.

If you have an existing large codebase using CSS-in-JS:

  1. You are suffering more than any of us.
  2. StyleX might actually make sense for you, but I still wouldn't recommend it.

You would definitely see some perf improvements, and the syntax seems quite similar so transition should be easy. But StyleX is quite young, so you will probably experience some issues with the transition.

Even though I crapped on StyleX throught this article, I always think it's cool when people are trying something new. And I think it's great that people behind StyleX are trying to make something to make our professional lives easier.

I'm keeping an open mind about this topic, so feel free to try to change it in the comments!

Top comments (2)

Collapse
 
baptistefkt profile image
Baptiste Firket

Thank you for sharing. However, I respectfully disagree with the notion that StyleX is intended to replace or kill Tailwind. From my perspective, it seems more like an enhanced iteration of CSS-in-JS libraries, catering to those who may not resonate with Tailwind's approach (yes, we do exist!).

Collapse
 
noblica profile image
Dušan Perković • Edited

Hey Baptiste, thanks for the comment.
A lot of people are touting it as the Tailwind killer, hence why I structured the article that way. But at the end I do say that it might make sense as a replacement for CSS-in-JSS.

But I'm curious, why do you not resonate with Tailwind's approach specifically? I'm trying to get more perspective on why people might dislike it.