StyleX: A new perspective towards Styling Web Apps

StyleX: A new perspective towards Styling Web Apps

10 min read

Introduction

Have you ever wondered how Meta manages to create a consistent and beautiful user interface across its web platform? How does it handle the complexity of styling hundreds of components and thousands of variations? How does it ensure that the website is fast, responsive, and accessible?

The answer is StyleX, a custom CSS-in-JS solution that Meta developed for all its products. StyleX is a powerful and flexible styling system that allows developers to write modular, performant, and maintainable CSS code. In this blog, we will learn what StyleX is, how it works, and how it compares to other CSS-in-JS solutions. We will also see some examples of how to use StyleX to style React components and create dynamic themes.

How does StyleX work?

StyleX works by transforming the CSS code written in JavaScript files into plain CSS code that can be injected into the HTML document. StyleX uses a custom Babel plugin to parse the CSS code and generate the corresponding CSS class names and rules. StyleX also uses a custom Webpack plugin to extract the CSS code from the JavaScript files and bundle it into a single CSS file.

How to use StyleX?

To use StyleX in your project, you need to install the @stylexjs/stylex package from npm, and configure the compiler plugin for your bundler. StyleX supports Rollup, Webpack, and Parcel out of the box, and you can also use custom plugins for other bundlers. For example, if you are using Rollup, you can install the plugin like this:

npm install --save-dev @stylexjs/rollup-plugin

Then, you can configure the plugin in your rollup.config.js file, like this:

import plugin from '@stylexjs/rollup-plugin

StyleX is a CSS-in-JS library, which means that it allows you to write CSS styles inside your JavaScript components, using an object syntax and a create() API. For example, you can define a style object like this:

import * as stylex from '@stylexjs/stylex';

const styles = stylex.create({
  root: {
    width: '100%',
    maxWidth: 800,
    minHeight: 40,
  },
  child: {
    backgroundColor: 'black',
    marginBlock: '1rem',
  },
});

Then, you can use the props() function to apply the styles to your elements, using standard JavaScript expressions to combine and conditionally apply styles. For example, you can render a React component like this:

import * as React from 'react';
import * as stylex from '@stylexjs/stylex';

const styles = stylex.create({
  ...
});

function ReactDiv({ color, isActive, style }) {
  return <div {...stylex.props(
    styles.root, // apply styles unconditionally
    isActive && styles.active, // apply styles conditionally
    colorStyles[color], // choose a style variant based on a prop
    style, // styles passed as props
  )} />;
}

StyleX is framework agnostic, so you can use it with any framework that accepts className strings and style objects, such as SolidJS, Preact, or Qwik.

Why use StyleX?

StyleX is not the first CSS-in-JS library, nor the only one. There are many other popular and well-established libraries, such as Styled Components, Emotion, or Tailwind CSS. So why should you consider using StyleX instead of them? Here are some of the benefits that StyleX offers:

  • Scalable: StyleX minimizes the CSS output by using atomic CSS, which means that each style rule is represented by a single class name that corresponds to a single CSS declaration. For example, the style object { color: 'red', fontSize: 16 } would be translated into two class names, such as a1 and b2, that map to .a1 { color: red; } and .b2 { font-size: 16px; } in the CSS file. This way, the CSS size plateaus even as the number of components grows, since the same class names can be reused across different components. Moreover, styles remain readable and maintainable within growing codebases, as each style rule is defined and used locally within a component.

  • Predictable: StyleX avoids the specificity issues that often plague CSS-in-JS libraries, such as conflicting or overriding styles, by ensuring that class names on an element can only directly style that same element. This means that there is no cascade or inheritance of styles, and the order of the styles does not matter. The last style applied always wins, regardless of where it comes from. For example, if you have a component that renders <div className="a b c" />, and the styles for a, b, and c are defined as follows:

      const styles = stylex.create({
        a: {
          color: 'red',
        },
        b: {
          color: 'green',
        },
        c: {
          color: 'blue',
        },
      });
    

    Then, the final color of the div will be blue, regardless of the order of the class names or the order of the style definitions. This makes it easy to reason about and debug the styles of your components.

  • Composable: StyleX allows you to apply styles conditionally, merge and compose arbitrary styles across component and file boundaries, and use local constants and expressions to keep styles DRY. You can also repeat styles without worrying about performance, as StyleX will deduplicate and optimize the styles at compile time. For example, you can define a theme object that contains some common colors and sizes, and use it in your styles like this:

      const theme = {
        primary: '#0070f3',
        secondary: '#ff0080',
        small: 12,
        medium: 16,
        large: 24,
      };
    
      const styles = stylex.create({
        button: {
          padding: '0.5rem 1rem',
          borderRadius: '0.25rem',
          border: 'none',
          cursor: 'pointer',
        },
        primary: {
          color: 'white',
          backgroundColor: theme.primary,
        },
        secondary: {
          color: 'white',
          backgroundColor: theme.secondary,
        },
        small: {
          fontSize: theme.small,
        },
        medium: {
          fontSize: theme.medium,
        },
        large: {
          fontSize: theme.large,
        },
      });
    

    Then, you can use the styles to render different variants of buttons, like this:

      import * as React from 'react';
      import * as stylex from '@stylexjs/stylex';
    
      const styles = stylex.create({
        ...
      });
    
      function Button({ variant, size, children }) {
        return <button {...stylex.props(
          styles.button,
          styles[variant],
          styles[size],
        )}>
          {children}
        </button>;
      }
    
  • Fast: StyleX does not have any runtime style injection, as all styles are bundled in a static CSS file at compile time. This means that there is no flash of unstyled content, no style recalculation, and no extra network requests. StyleX also has an optimized runtime for merging class names, which is much faster than concatenating strings or using template literals. For example, the props() function will merge the class names using bitwise operations, which are very efficient in JavaScript. For example, if you have three style rules with the following class names: a1 b2 c3, d4 e5 f6, and g7 h8 i9, the props() function will merge them into a single class name like this:

      stylex.props('a1 b2 c3', 'd4 e5 f6', 'g7 h8 i9');
      // returns 'a1 b2 c3 d4 e5 f6 g7 h8 i9'
    

    This is much faster than doing something like this:

      `${'a1 b2 c3'} ${'d4 e5 f6'} ${'g7 h8 i9'}`;
      // returns 'a1 b2 c3 d4 e5 f6 g7 h8 i9'
    
  • Type-Safe: StyleX has type-safe APIs, type-safe styles, and type-safe themes. This means that you can use TypeScript or Flow to check the validity and correctness of your styles, and catch errors and typos at compile time. For example, if you try to use an invalid style rule, such as color: 'redish', or an undefined style object, such as styles.foo, you will get a type error. You can also use type annotations to define your theme object, and use it in your styles with autocomplete and type checking. For example, you can define a theme object like this:

      type Theme = {
        primary: string;
        secondary: string;
        small: number;
        medium: number;
        large: number;
      };
    
      const theme: Theme = {
        primary: '#0070f3',
        secondary: '#ff0080',
        small: 12,
        medium: 16,
        large: 24,
      };
    

    Then, you can use the theme object in your styles like this:

      import * as stylex from '@stylexjs/stylex';
    
      const styles = stylex.create({
        button: {
          padding: '0.5rem 1rem',
          borderRadius: '0.25rem',
          border: 'none',
          cursor: 'pointer',
        },
        primary: {
          color: 'white',
          backgroundColor: theme.primary, // autocomplete and type checking
        },
        secondary: {
          color: 'white',
          backgroundColor: theme.secondary, // autocomplete and type checking
        },
        small: {
          fontSize: theme.small, // autocomplete and type checking
        },
        medium: {
          fontSize: theme.medium, // autocomplete and type checking
        },
        large: {
          fontSize: theme.large, // autocomplete and type checking
        },
      });
    

Here are a few more advanced uses in StyleX to style a React component:

// Import the StyleX function from the StyleX package
import StyleX from "stylex";

// Define the styles for the component using the StyleX function
// The StyleX function takes an object literal as an argument, where each key is a CSS class name and each value is an object literal with CSS properties and values
// The StyleX function returns an object with the same keys, but the values are the generated CSS class names
const styles = StyleX.create({
  button: {
    // Use camelCase for CSS properties
    backgroundColor: "blue",
    color: "white",
    padding: 8,
    borderRadius: 4,
    // Use arrays for multiple values
    boxShadow: ["0 2px 4px rgba(0, 0, 0, 0.1)", "0 4px 8px rgba(0, 0, 0, 0.2)"],
    // Use JavaScript expressions for dynamic values
    // The StyleX function accepts a function as a value, which takes the props of the component as an argument and returns a CSS value
    fontSize: (props) => (props.size === "large" ? 18 : 14),
    // Use the StyleX.compose function to compose multiple styles
    // The StyleX.compose function takes an array of CSS class names as an argument and returns a single CSS class name that combines all the styles
    // The order of the styles in the array matters, as the later styles will override the earlier styles
    ":hover": StyleX.compose([
      // Use the StyleX.keyframes function to define animations
      // The StyleX.keyframes function takes an object literal as an argument, where each key is a percentage of the animation duration and each value is an object literal with CSS properties and values
      // The StyleX.keyframes function returns a CSS class name that can be used with the animation property
      StyleX.keyframes({
        "0%": { transform: "scale(1)" },
        "50%": { transform: "scale(1.1)" },
        "100%": { transform: "scale(1)" },
      }),
      // Use the StyleX.sheet function to define global styles
      // The StyleX.sheet function takes a template literal as an argument, where the CSS code can be written using the regular CSS syntax
      // The StyleX.sheet function returns a CSS class name that can be used with any element
      StyleX.sheet`
        .button:hover {
          cursor: pointer;
        }
      `,
    ]),
  },
});

// Create the React component using the styles object
// Use the styles object to access the CSS class names and apply them to the elements using the className attribute
function Button(props) {
  return <button className={styles.button}>{props.children}</button>;
}

How StyleX revolutionizes CSS

StyleX is not just another CSS-in-JS library. It is a radical departure from the traditional way of writing and thinking about CSS. StyleX challenges some of the core principles and features of CSS, such as the cascade, inheritance, specificity, selectors, media queries, and pseudo-classes. Instead, StyleX embraces a new paradigm of styling, based on atomic CSS, local scope, and JavaScript expressions.

This paradigm shift may not appeal to everyone, especially those who are used to or fond of the classic CSS syntax and semantics. Some may argue that StyleX is too verbose, too restrictive, or too complex. Some may even say that StyleX is not really CSS, but a different language altogether.

However, I believe that StyleX is the best solution for large-scale, modern web applications, such as Meta's products. StyleX offers many benefits that outweigh the drawbacks, such as scalability, predictability, composability, speed, and type safety. StyleX also leverages the power and flexibility of JavaScript, which is the lingua franca of the web. StyleX allows you to write styles that are dynamic, expressive, and reusable, without sacrificing performance or maintainability.

StyleX is not for everyone, and it is not for every project. But for those who are looking for a new way of styling web apps, StyleX is worth a try. StyleX is the result of years of research and experimentation by Meta's engineers, who have faced and solved some of the most challenging problems in web development. StyleX also became open source recently, which means that anyone can contribute, learn, and improve it.

Conclusion

In this blog post, I have introduced StyleX, the styling system that powers Meta's user interface. We have explained what StyleX is, how it works, and how you can use it in your own projects. I have also discussed why StyleX is different from other CSS-in-JS libraries, and how it revolutionizes the way we write and think about CSS.

I hope that you have enjoyed this blog post, and that you have learned something new and useful. If you found this blog helpful, I'd love for you to share it with your developer friends! And hey, while you're at it, why not follow me on Twitter. I try to bring meaningful content like these from time to time.

If you want to learn more about StyleX, you can check out these resources:

  • Facebook Did it Again: This is the YouTube video that inspired this blog, where Kyle, gives an overview of StyleX and its features.

  • StyleX Blogs: Official documentation of StyleX, where you can find more details and examples of how to use StyleX.

  • Releases 路 facebook/stylex 路 GitHub: GitHub repository of StyleX, where you can follow the development progress and updates of StyleX.

Thank you for reading! 馃檹