Skip to content

Exploring StyleX: A First Look Review

StyleX is Meta's compiler for styling web apps that powers Facebook, Instagram and other Meta apps. Let's explore the features and limitations of StyleX.

Published
Mar 29, 2024
Updated
Mar 29, 2024
Reading time
4 min read

Things I like 👍

Works at compile-time

StyleX compiles all styles into a static CSS file, eliminating the need for runtime style injection. Furthermore, the stylex.props() function is also resolved at compile-time, unless it's provided with dynamic values.

// This is resolved at compile-time ...
const className = stylex.props(styles.card);

// ... into something like this
const className = { className: "x1p1rrjz" };

Atomic CSS

StyleX generates atomic CSS classes, where each unique property-value combination is created only once and then reused throughout the entire application. This approach helps maintain a minimal CSS output.

Co-location of styles

With StyleX, styles can be conveniently co-located with your components in the same file. This eliminates the need to constantly switch between files when working on a component.

const MyComponent = () => {
  return <div {...stylex.props(styles.base)} />;
};

const styles = stylex.create({
  base: {
    color: "red",
  },
});

Order of styles

StyleX allows for the merging of multiple styles, with the last style always taking precedence. For instance, if two styles both define the color property, the one applied last will be used.

<div {...stylex.props(styles.blue, styles.red)}>This will be red</div>
<div {...stylex.props(styles.red, styles.blue)}>This will be blue</div>

Things I don't like 👎

Variables Format

StyleX provides support for variables, also known as design tokens, through the stylex.defineVars() function in a *.stylex.ts file. While it would be convenient to nest these design tokens, StyleX does not support this feature. Variables must be defined as a flat object. Additionally, re-exporting these variables is not supported.

// ✅ Tokens must be flat
export const tokens = stylex.defineVars({
  fontSizeM: "1rem",
  fontSizeL: "2rem",
  spaceM: "16px",
  spaceL: "20px",
});

// ❌ Tokens cannot be nested
export const tokens = stylex.defineVars({
  font: {
    size: {
      m: "1rem",
      l: "2rem",
    },
  },
  space: {
    m: "16px",
    l: "20px",
  },
});

// ❌ Tokens cannot be re-exported as a single object
export const tokens = {
  font: fontTokens,
  size: sizeTokens,
};

Props API

The stylex.props() function manages both static styles through the className prop and dynamic styles via the style prop. Although it's beneficial to have a single API that handles both scenarios, it can be verbose and less than ideal when the vast majority of styles are static.

// StyleX
<div {...stylex.props(styles.card)} />

// Others like Panda CSS, Vanilla Extract, ...
<div className={card} />

Styles Object

In StyleX, styles cannot be passed directly to the stylex.create() function. Instead, they must be encapsulated within an object with additional keys. While this approach may seem like unnecessary boilerplate when defining a single style, it does promote consistency as styles are always wrapped in an object.

const styles = stylex.create({
  card: {      // <- Nesting is necessary
    boxShadow: "...",
  },
});

No ampersand selector

In StyleX, styles are strictly applied to the element to which the class is added. The use of the & selector to target other elements is not supported. While this design choice promotes encapsulation and reduces side effects, there are scenarios where targeting other elements could be beneficial. For instance, when constructing a Stack component or a Flow component.

// ❌ Not possible with StyleX
const styles = stylex.create({
  flow: {
    "& > * + *": {
      marginTop: "1.5em",
      marginBottom: 0,
    },
  },
});

Vanilla Extract does support the & selector, but the styles must be applied directly to the element itself. For instance, :hover > & is permissible, whereas & > :hover is not. However, for the latter case, it's possible to utilize the globalStyle API. This seems to be a reasonable compromise.

export const myStyle = style({
  // Child elements cannot be styled here ...
});

globalStyle(`${myStyle} > :hover`, {
  // ... but here they can
});

No global styles

StyleX is designed to support scoped styles for components exclusively. Consequently, it doesn't allow for global CSS resets or styling of the body element (unless you have control over the element, which is typically not the case in a standard React + Vite project).

While you could resort to using plain CSS files for these tasks, this approach doesn't provide access to the design tokens.

Limited API

StyleX has a very limited API, which can be a good thing as well. But libraries like Vanilla Extract and Panda CSS offer much more flexibility for styling components (maybe too much?).