Back to Scaling Node.js Applications guides

A Quick Look into StyleX: Meta's Solution for Scalable CSS

Stanley Ulili
Updated on November 26, 2025

Styling web apps is still a big, ongoing challenge in modern web development. We want our styles to be easy to write, easy to keep clean, fast to load, and able to grow with our apps. Meta, the company behind Facebook, Instagram, and WhatsApp, had to solve this problem at huge scale and created a powerful CSS framework called StyleX.

StyleX sits in a unique spot compared to tools like Tailwind CSS or classic CSS-in-JS libraries. It feels like CSS-in-JS because styles live close to components, but it ships fast, tiny, atomic CSS like a static framework. It was designed specifically for very large apps with many teams, so it focuses on consistency, predictability, and very small bundles.

This article explains what makes StyleX different by treating it as a build-time compiler. You’ll see simple and advanced examples, from basic styles to type-safe theming. By the end, you’ll understand how StyleX works, what problems it solves, and whether it’s a good fit for your next big project.

Understanding the core philosophy: StyleX as a compiler

To truly grasp the power of StyleX, the first thing to understand is that it's not a runtime library in the traditional sense. While you write your styles in JavaScript files, right alongside your components, those styles don't get processed in the user's browser. Instead, StyleX is fundamentally a compiler that does its magic at build time. This approach is the key to unlocking its incredible performance.

What is atomic CSS?

At the heart of StyleX's output is the principle of Atomic CSS. Unlike traditional CSS where you might create a class like .card that contains a dozen style declarations, Atomic CSS breaks everything down into the smallest possible units.

An atomic CSS class contains just one style declaration. For example:

  • .c1 { color: red; }
  • .c2 { margin-top: 10px; }
  • .c3 { border-radius: 8px; }

An element that needs all three of these styles would then have all three classes applied: <div class="c1 c2 c3"></div>.

The benefits of this approach are significant, especially in large applications:

  1. Maximum reusability: If another element anywhere in your application needs a red color, it can reuse the .c1 class. This means you don't create duplicate CSS code.
  2. Predictable bundle size: Because styles are reused so efficiently, the size of your CSS file grows very slowly as your application gets bigger. Once all your common styles are defined, adding new components often adds very little or no new CSS. The size of the CSS bundle plateaus.
  3. No style conflicts: Since each class does only one thing, the order of classes in the HTML doesn't matter, and specificity wars become a thing of the past. The final styles are determined by the classes applied, not complex CSS rules.

How the StyleX compiler works

StyleX leverages the Atomic CSS principle through its build-time compilation process. Here's a breakdown of what happens behind the scenes:

  1. Authoring: You write styles within your JavaScript or TypeScript files using the stylex.create() function. This feels intuitive and keeps your styling logic colocated with your component logic.
  2. Compilation: When you build your application (for example, with Vite or Next.js), the StyleX plugin (a Babel plugin) scans your entire codebase for these stylex.create() calls.
  3. Extraction and generation: The compiler extracts every single style declaration (for example, color: 'blue', padding: '16px'). For each unique property-value pair, it generates a unique, deterministic, and highly-minified atomic class name (like .x1e2nbdu). It gathers all of these classes into a single, static .css file.
  4. Transformation: The original JavaScript code is transformed. The calls to stylex.props() are replaced with the appropriate className prop, containing a string of the generated atomic class names.

The end result is the best of both worlds. You get a fantastic developer experience, but what's shipped to the browser is a highly optimized, static CSS file and HTML that simply references those classes. There is zero JavaScript runtime overhead required to calculate and inject styles, leading to faster paint times and a better user experience.

The generated CSS file shows individual atomic classes, demonstrating the core principle of StyleX's build-time compilation.

Defining and applying styles

The core API of StyleX is minimal and easy to learn, especially if you're familiar with CSS. All styles begin with the stylex.create() function, and you apply them using stylex.props().

Basic style definitions with stylex.create()

You import StyleX from the library and use it to define an object of "style namespaces." Here's an example of a simple component with a container and a button:

component.tsx
import * as stylex from '@stylexjs/stylex';

const styles = stylex.create({
  container: {
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'center',
    height: '100vh',
    gap: '20px',
  },
  button: {
    borderRadius: '18px',
    borderStyle: 'none',
    paddingBlock: '10px',
    paddingInline: '16px',
    backgroundColor: 'red',
    color: 'white',
    cursor: 'pointer',
    fontSize: '1rem',
    fontWeight: 600,
  },
});

You call stylex.create() and pass it an object. The keys of this object are your style namespaces (like container and button), and the values are objects containing the CSS properties. Notice that the property names are in camelCase (like backgroundColor) just like in standard JavaScript style objects, but they map directly to their CSS equivalents.

Applying styles with stylex.props()

Once your styles are defined, you apply them to your JSX elements using the stylex.props() function. This utility function takes one or more style objects from your styles constant and returns the necessary props to be spread onto an element:

component.tsx
function App() {
  return (
    <div {...stylex.props(styles.container)}>
      <button {...stylex.props(styles.button)}>
        Subscribe
      </button>
    </div>
  );
}

A browser inspector view of an element styled with StyleX, showing the resulting string of atomic class names generated by `stylex.props()`.

The stylex.props() function does the crucial work of resolving your style objects (like styles.container) into the final string of atomic CSS classes that the compiler generated. This is why you use this function instead of just setting className directly.

Handling pseudo-classes and media queries

Static styles are great, but real applications need to be responsive and interactive. StyleX has an elegant syntax for handling pseudo-classes (like :hover) and media queries.

You change the value of a CSS property from a simple string to an object. This object must contain a default key, and can then contain additional keys for your selectors. Here's an example that enhances the button to change color on hover and adapt to dark mode:

component.tsx
const styles = stylex.create({
  button: {
default: 'red',
':hover': '#cc0000',
color: 'white', // ... other styles }, container: {
default: 'white',
'@media (prefers-color-scheme: dark)': 'black',
// ... other styles } });

This code snippet clearly shows the object-based syntax for defining default and conditional styles for a single CSS property like `backgroundColor`.

In this example, the backgroundColor of the button is red by default but changes to #cc0000 when the user hovers over it. The backgroundColor of the container is white by default but switches to black if the user's operating system is set to dark mode.

This syntax is incredibly powerful because it keeps all the logic for a single property grouped together, making the styles easy to read and reason about.

The developer experience: more than just performance

While performance is a major selling point, StyleX also offers a superior developer experience, especially when dealing with complex styling scenarios.

Predictable style merging

A common issue in CSS is managing how styles override each other. StyleX makes this completely deterministic. When you pass multiple style objects to stylex.props(), they are merged in order, with the last style object taking precedence. This works exactly like JavaScript's Object.assign() or object spread syntax:

component.tsx
const styles = stylex.create({
  red: {
    color: 'red',
    fontSize: '24px',
  },
  blue: {
    color: 'blue',
    fontWeight: 700,
  }
});

// In your component:
<h1 {...stylex.props(styles.red, styles.blue)}>Hello World</h1>

The final h1 will be blue, have a fontSize of 24px, and a fontWeight of 700. The color: 'blue' from styles.blue overwrote the color: 'red' from styles.red because it came later in the list. This simple, predictable rule eliminates any guesswork.

Effortless conditional styling

Because your styles are defined in JavaScript, you can use the full power of the language for conditional logic. This is far more powerful and cleaner than juggling class names in a template string:

component.tsx
import { useState } from 'react';
import * as stylex from '@stylexjs/stylex';

// ... styles definition for 'red' and 'blue'

function App() {
  const [useRed, setUseRed] = useState(true);

  return (
    <div>
Color me!
<button onClick={() => setUseRed(!useRed)}>Toggle Color</button> </div> ); }

Here, the code uses the logical AND (&&) operator. If useRed is true, the styles.red object is passed to stylex.props(). If it's false, styles.red evaluates to false (which StyleX ignores) and styles.blue is passed instead. This pattern is clean, readable, and leverages standard JavaScript.

Building robust design systems with type safety

StyleX truly distances itself from many other styling solutions through its deep integration with TypeScript. This allows you to create incredibly robust and type-safe component APIs, which is essential for maintaining a consistent design system across a large team.

Creating a flexible but constrained component API

Imagine you're building a Card component for your company's component library. You want to provide some sensible default styles but also allow the teams using your component to make some customizations. However, you don't want them to change core layout properties that could break the component.

You can achieve this by creating a typed interface for the style prop you expose. Instead of accepting any StyleX style, you can create a TypeScript type that only includes the properties you want to allow:

Card.tsx
import type { StyleXStyles } from '@stylexjs/stylex';

interface Props {
  // ... other props
borderColor?: 'red' | 'blue' | 'green';
color?: string;
} export function BasicCard({ children, title, style }: Props) { return ( <div {...stylex.props(styles.base, style)}> {/* ... */} </div> ); }

If a developer tries to use your BasicCard and pass a style that isn't borderColor or color, TypeScript will throw an error immediately. You've created a safe, well-defined API. You've even gone a step further and restricted the possible values for borderColor to a specific set of colors from your design system.

A TypeScript error highlights an invalid style being passed to a component, demonstrating StyleX's ability to enforce design constraints at the type level.

Blacklisting styles with StyleXStylesWithout

Alternatively, you might want to allow most CSS properties but explicitly forbid a few. StyleX provides a utility type for this as well, called StyleXStylesWithout:

Card.tsx
import type { StyleXStylesWithout } from '@stylexjs/stylex';

interface Props {
  // ... other props
// Disallow changing these properties
margin: unknown;
padding: unknown;
display: unknown;
}

This type would allow consumers to change color, backgroundColor, borderRadius, and other properties, but TypeScript would throw an error if they tried to change margin, padding, or display, protecting the component's core layout.

Advanced theming with variables

The final piece of the puzzle for a scalable design system is theming. StyleX has a first-class theming system built on CSS Variables, which is both powerful and type-safe.

Defining your design tokens with stylex.defineVars()

The best practice is to define all your design tokens (reusable values for colors, spacing, font sizes, and so on) in a central file, often named tokens.stylex.ts. You use the stylex.defineVars() function to do this:

src/tokens.stylex.ts
import * as stylex from '@stylexjs/stylex';

export const colors = stylex.defineVars({
  primaryText: { default: 'black', '@media (prefers-color-scheme: dark)': 'white' },
  background: { default: 'white', '@media (prefers-color-scheme: dark)': 'black' },
  accent: { default: 'blue', '@media (prefers-color-scheme: dark)': 'lightblue' },
});

export const spacing = stylex.defineVars({
  none: '0px',
  small: '4px',
  medium: '8px',
  large: '16px',
});

A code example showing `stylex.defineVars()` used to create design tokens for colors and spacing, including dark mode variants.

This creates a set of CSS variables that you can now use throughout your application.

Overriding tokens with stylex.createTheme()

Once you have your base set of variables, you can create any number of themes that override them. This is done with the stylex.createTheme() function. It takes two arguments: the variable object you want to override, and an object containing the new values:

src/themes.ts
import * as stylex from '@stylexjs/stylex';
import { colors } from './tokens.stylex';

export const dracula = stylex.createTheme(colors, {
    primaryText: 'purple',
    background: '#282a36',
    accent: 'pink',
});

export const cyberpunk = stylex.createTheme(colors, {
    primaryText: '#00ff41',
    background: '#0d0d0d',
    accent: '#ff00ff',
});

Crucially, this is also type-safe. If you try to override a variable that doesn't exist in the original colors object, or provide a value that doesn't match the expected type, TypeScript will catch the error.

Applying themes to your application

Applying a theme is as simple as applying any other style. You pass the theme object to stylex.props() on a container element. The theme will then apply to that element and all its children:

component.tsx
import { dracula } from './themes';

function App() {
  // Apply the Dracula theme to the entire container
  return (
    <div {...stylex.props(dracula, styles.container)}>
        {/* All components inside here will now use the Dracula theme colors */}
    </div>
  );
}

This system makes it trivial to support multiple themes, dark/light modes, and other design variations in a clean, maintainable, and type-safe way.

Final thoughts

StyleX is a carefully engineered CSS framework that tackles styling at scale by merging the component-colocated ergonomics of CSS-in-JS with the speed and small bundle sizes of static, atomic CSS. It compiles styles at build time to avoid runtime overhead, stays efficient and predictable through atomic class generation, integrates deeply with TypeScript for type-safe theming and design systems, and lets you write colocated styles in JavaScript without sacrificing performance.

While utility-first tools like Tailwind CSS are great for rapid prototyping and many small to medium projects, StyleX really shines in large, multi-team applications, reusable component libraries, and products that need a strict, type-safe design system with powerful theming. In these environments, it offers a level of performance, maintainability, and consistency that is hard to match.