Stylex Facebook - Is It a CSS Solution
Stylex Facebook - Is It a CSS Solution

In this post you will learn about StyleX library which was made by Facebook and released not so long ago.

There are already lots of posts about StyleX and how amazing it is. People are saying that it's a best library for styling React and it is a killer of TailwindCSS.

Official website says that StyleX is a simple, easy to use Javascript syntax and compiler for styling web apps.

From my perspective this is just another library of writing CSS in Javascript but it is done by Facebook with quite good API. This is why this library will for sure be popular.

Just to remind you from the history first we just wrote CSS as an additional language to write styles for our HTML. Then at some point we got the idea of writing CSS in Javascript. It helps us to easier change our CSS based on Javascript variables because we are writing all our styling directly inside specific component.

It makes a lot of sense but it is still much easier to just load CSS than to execute Javascript and calculate all these styles on the fly.

Additionally at some point later we got solutions like TailwindCSS.

tailwind

It's a library where you get lots of small helpers and you can combine them and avoid writing CSS at all and simply using hundreds of different classes inside your HTML.

StyleX is for sure not a replacement for TailwindCSS because they are too different.

Saying that StyleX is a killer of TailwindCSS is not correct. TailwindCSS allows you to write styles in a matter of seconds. StyleX on the other hand allows you to structure your styles, reuse them, and scale.

It's a CSS in Javascript library which can be built in CSS and it is type safe.

Configuration

Now we need to configure it. My typical project is generated with Vite. The first package that we want to install here is StyleX itself.

npm i @stylexjs/stylex

But it is not enough because you can't use StyleX just like a normal library. You need a compiler for all these styles inside your project. You can configure it with Webpack, Rollup or Babel but as people are typically using Vite for local development it makes a lot of sense to configure it together with Vite. We can easiely do that with additional package vite-plugin-stylex.

npm i vite-plugin-stylex

After installation we must tune our Vite config a bit.

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import styleX from "vite-plugin-stylex";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react(), styleX()],
});

Here we added styleX plugin in the list of existing plugins.

Another thing that I prepared in our small project is a Button component.

interface ButtonProps {
  text: string
}

const Button = ({text}: ButtonProps) => {
  return <button>{text}</button>
}

This is nothing more that a button where inside we are rendering text.

Writing basic styles

 import * as stylex from '@stylexjs/stylex'

 const styles = stylex.create({
  base: {
    fontSize: 18
  }
 })

const Button = ({text}: ButtonProps) => {
  return <button {...stylex.props(styles.base)}>{text}</button>
}

Here we created a list of styles with stylex.create. Every property holds multiple styles just like a class in plain CSS. All styles are written in camel case like in typical CSS in JS library.

In order to apply class we spread it with stylex.props and providing a property of styles inside.

basic

As you can see in browser our font size is bigger. Inside markup we are getting a unique class and on the right we see these styles. Most importantly these are inline styles which we get in the head of the page.

Let's add several more styles.

const styles = stylex.create({
  base: {
    fontSize: 18,
    backgroundColor: 'teal',
    color: 'white'
  }
})

As you can see extremely similar to plain CSS. You just need to remember that we use create to generate styles and props to spread them.

Working with specific styles

Here is an important point. If you previously used CSS in Javascript you always have questions there how to override a specific style. Inside StyleX it is done in extremely easy way.

It merges all styles partially and the last element in props always wins.

const styles = stylex.create({
  base: {
    fontSize: 18,
    backgroundColor: 'teal',
    color: 'white'
  },
  highlight: {
    backgroundColor: 'orange'
  }
})

const Button = ({text}: ButtonProps) => {
  return <button {...stylex.props(styles.base, styles.highlight)}>{text}</button>
}

Here we passed to props multiple styles. So here StyleX merges two objects but the question is how it overrides properties. On our case instead of backgroundColor in base it will apply backgroundColor in highlight.

Which essentially means that last element always wins.

override

As you can see we got a button with overridden background. But most importantly on the right we see just a single background and not lots of crossed out and not applies styles like in other CSS in JS libraries.

Hover & Focus

Now let's talk about things like hover and focus. It is completely possible to use them inside StyleX. Let's say that inside our base we want to change our color on hover.

const styles = stylex.create({
  base: {
    fontSize: 18,
    backgroundColor: {
      default: 'teal',
      ':hover': 'blue'
    },
    color: 'white'
  },
  highlight: {
    backgroundColor: 'orange'
  }
})

const Button = ({text}: ButtonProps) => {
  return <button {...stylex.props(styles.base, styles.highlight)}>{text}</button>
}

As you can see in order to apply a hover we converted our backgroundColor from string to an object. Then we provided a key default which gives us the same behavior as using a string. After this we can apply :hover that we want.

But most importantly our code won't work. Why is that? We still override the whole backgroundColor with highlight block and it doesn't merge them at all. The whole key backgroundColor is removed.

Let's remove the highlight part for now.

const Button = ({text}: ButtonProps) => {
  return <button {...stylex.props(styles.base)}>{text}</button>
}

hover

As you can see now our hover effect works correctly. So this is the way how you can apply hover or focus effects when they are needed.

Media queries

In exactly the same way you can apply different media queries.

const styles = stylex.create({
  base: {
    fontSize: 18,
    backgroundColor: {
      default: "teal",
      ":hover": "blue",
    },
    color: "white",
    width: {
      default: "100px",
      "@media (max-width: 800px)": "100%",
    },
  }
})

Here we applied a default width of our Button and we wrote a media query that it must take full width when it is smaller that 800px.

media

As you can see now our button takes full width when browser size is smaller that 800px.

Styles resetting

Now the question is how we can remove a style without overriding it with something else.

const styles = stylex.create({
  base: {
    fontSize: 18,
    backgroundColor: {
      default: 'teal',
      ':hover': 'blue'
    },
    color: 'white'
  },
  highlight: {
    backgroundColor: null
  }
})

const Button = ({text}: ButtonProps) => {
  return <button {...stylex.props(styles.base, styles.highlight)}>{text}</button>
}

In our case here inside backgroundColor of highlight I provided null as a value. It will reset our backgroundColor on inital. As you can see in browser our button don't have any background at all.

Styles with conditions

The next important question is how to apply some styles based on the conditions in our component.

interface ButtonProps {
  text: string;
  isHighlighted: boolean;
}

const Button = ({ text, isHighlighted}: ButtonProps) => {
  return (
    <button
      {...stylex.props(
        styles.base,
        isHighlighted && styles.highlighted,
      )}
    >
      {text}
    </button>
  );
};

Here I added isHighlighted as a prop of our Button component. Now we only want to apply our highlighted styles when we passed inside this property. We simply used here an & operator to apply styles with condition. If isHighlighted is false then we will provide false as a second parameter to props and it won't be applied.

conditions

As you can see when we provide isHighlighted prop to our component our styles are set to orange.

Variants for children

Another case that you often need is to implement different variants of the same component. Typically with button you have different styles with danger, primary, secondary props. For such case we use variants.

const styles = stylex.create({
  ...
  danger: {
    backgroundColor: "red",
  },
  primary: {
    backgroundColor: "green",
  },
});

interface ButtonProps {
  text: string;
  isHighlighted: boolean;
  variant: "danger" | "primary";
}

const Button = ({ text, isHighlighted, variant}: ButtonProps) => {
  return (
    <button
      {...stylex.props(
        styles.base,
        isHighlighted && styles.highlighted,
        styles[variant],
      )}
    >
      {text}
    </button>
  );
};

Here we created one more prop in our component which is called variant. It is either string danger or primary. Inside our markup we write styles[variant] which just applies different styles by selecting different key of the object. Now we can create different variants inside our styles.

variants

As you can see our button is red when we provide a variant danger to the component.

Overriding styles

Another important question is if we can override our styles from the outside. Sometimes it is not enough to style our component in a good way, we still want to override something from the parent.

// src/App.tsx
import * as stylex from "@stylexjs/stylex";

const styles = stylex.create({
  override: {
    backgroundColor: "black",
    color: "white",
  },
});

const App = () => {
  return (
    <div>
      <h1>Monsterlessons Academy</h1>
      <Button
        text="foo"
        isHighlighted
        variant="danger"
        style={styles.override}
      />
    </div>
  );
};

In our parent component App we created a bunch of styles in property override with StyleX. After this we provided them to the component in the key style.

interface ButtonProps {
  text: string;
  isHighlighted: boolean;
  variant: "danger" | "primary";
  style: stylex.StyleXStyles;
}

const Button = ({ text, isHighlighted, variant, style }: ButtonProps) => {
  return (
    <button
      {...stylex.props(
        styles.base,
        isHighlighted && styles.highlighted,
        styles[variant],
        style
      )}
    >
      {text}
    </button>
  );
};

Now inside our ButtonProps we accept style which is a stylex.StyleXStyles and we apply them as a last parameter in our stylex.props. It means that we will override all styles of our button.

override parent

As you can see in browser our override was applied and we successfully changed our styles of the button.

And actually if you want to improve your Javascript knowledge and prepare for the interview I highly recommend you to check my course Javascript Interview Questions.

📚 Source code of what we've done