Unveiling StyleX and Next-Gen Styling Libraries - Transforming CSS in Modern Web Dev
Facebook encountered challenges with CSS while undergoing a complete React rewrite of its web frontend three years ago. They needed a solution to manage CSS effectively amidst various options like build-time vs run-time, CSS in JavaScript, and utility-first systems like Tailwind.
To address this, Facebook introduced StyleX, a new CSS platform, as the third pillar of their application architecture alongside GraphQL/Relay and React. They aimed for StyleX to rectify past mistakes, particularly the scaling issues with their previous CSS module-based approach. Lazy loading caused problems with selector precedence, leading to unexpected styling variations across different routes. StyleX resolves this with “Deterministic Resolution,” ensuring consistent styling regardless of route changes.
Introducing the StyleX Button Component
Discovering the innovative approach to styling with an example of a StyleX Button
component.
import * as stylex from "@stylexjs/stylex";
const styles = stylex.create({
base: {
appearance: "none",
borderWidth: 0,
borderStyle: "none",
backgroundColor: "blue",
color: "white",
borderRadius: 4,
paddingBlock: 4,
paddingInline: 8,
},
});
export default function Button({
onClick,
children,
}: Readonly<{
onClick: () => void;
children: React.ReactNode;
}>) {
return (
<button {...stylex.props(styles.base)} onClick={onClick}>
{children}
</button>
);
}
At the core of StyleX is the co-location of styles with their respective components, a significant boon for developers favoring the clarity of Emotion-style CSS. This approach enhances developer experience and readability while still benefiting from compile-time optimizations, unlike run-time systems like Emotion. While StyleX lacks the simplicity of Tailwind’s shorthand styles, it compensates with enhanced styling control. Additionally, although Tailwind’s shortcuts are absent, StyleX supports design tokens, offering flexibility for creating custom shortcuts if desired.
A Look into Design Tokens and Theming within StyleX
Having the capability to finely adjust styling is crucial, yet a comprehensive design system must also incorporate support for design tokens and theming. StyleX excels in providing robust, type-safe support for both.
Now, let’s initiate the process:
import * as stylex from '@stylexjs/stylex';
export const buttonTokens = stylex.defineVars({
bgColor: 'red',
textColor: 'yellow',
paddingBlock: '6px',
cornerRadius: '5px',
paddingInline: '10px'
});
Notably, StyleX allows us the flexibility to utilize names such as "bgColor"
instead of being confined to particular CSS attributes. This versatility enables us to seamlessly integrate these tokens into our Button
component, as demonstrated below:
import * as stylex from "@stylexjs/stylex";
import type { StyleXStyles, Theme } from "@stylexjs/stylex/lib/StyleXTypes";
import "./ButtonTokens.stylex";
import { buttonTokens } from "./ButtonTokens.stylex";
export default function Button({
onClick,
children,
style,
theme,
}: {
onClick: () => void;
children: React.ReactNode;
style?: StyleXStyles;
theme?: Theme<typeof buttonTokens>;
}) {
return (
<button {...stylex.props(theme, styles.base, style)} onClick={onClick}>
{children}
</button>
);
}
const styles = stylex.create({
base: {
appearance: "none",
borderStyle: "none",
borderWidth: 0,
paddingInline: buttonTokens.paddingInline,
paddingBlock: buttonTokens.paddingBlock,
color: buttonTokens.textColor,
backgroundColor: buttonTokens.bgColor,
borderRadius: buttonTokens.cornerRadius,
},
});
Presently, we are constructing the styles for our Button
by amalgamating hardcoded attributes such as "borderWidth"
with themed attributes like "color,"
which derives from the "textColor"
design token.
Moreover, we facilitate the utilization of this theme by introducing a "theme"
property, serving as the foundation for our "stylex.props"
.
From the consumer perspective, we can generate a theme using "createTheme"
, aligning it with the button tokens:
const DARK_MODE = '@media (prefers-color-scheme: dark)';
const corpTheme = stylex.createTheme(buttonTokens, {
bgColor: {
default: 'black',
[DARK_MODE]: 'white'
},
textColor: {
default: 'white',
[DARK_MODE]: 'black'
},
cornerRadius: '4px',
paddingBlock: '4px',
paddingInline: '8px'
});
Furthermore, StyleX offers the flexibility of employing an object syntax to define theme values, enabling the incorporation of media queries. For instance, in dark mode, we can invert the button colors seamlessly.
Subsequently, within our page code, we have the option to directly pass the theme to the component:
<Button onClick={onClick} **theme={THEME}**>
Corp Button
</Button>
Alternatively, we can encapsulate the buttons within a container where the theme is specified:
<div {...stylex.props(THEME)}>
<Button onClick={onClick}>Corp Button</Button>
</div>
Utilizing CSS
variables, any Button
nested within that div will seamlessly inherit the specified theme.
Moreover, this functionality seamlessly integrates with React Server Components and Server-Side Rendering, as everything is computed at compile time, and the classes are injected into the code as strings.
Unlocking the Potential of Conditional and Dynamic Styles with StyleX
Expanding the Horizons of Build-Time CSS: Embracing Conditional and Dynamic Styles in StyleX. Let’s Introduce an emphasis
Flag to Our Original Button Component:
import * as stylex from "@stylexjs/stylex";
const styles = stylex.create({
...,
emphasized: {
fontWeight: "bold",
},
});
export default function Button({
onClick,
children,
emphasized,
}: Readonly<{
onClick: () => void;
children: React.ReactNode;
emphasized?: boolean;
}>) {
return (
<button
{...stylex.props(styles.base, emphasized && styles.emphasized)}
onClick={onClick}
>
{children}
</button>
);
}
Harnessing Conditional and Dynamic Styles to Elevate Your Components. Adding an Emphasis Flag Unveils a World of Possibilities.
We’ve merely scratched the surface of StyleX’s capabilities. Dynamic styles, generated at runtime for values like positions or colors, are effortlessly achievable. Moreover, features like variants are seamlessly supported by incorporating additional stylex.create definitions, enabling easy selection based on props.
Furthermore, the StyleX team has integrated OpenProps into StyleX, providing an extensive array of spacing options, colors, animations, and beyond, all readily accessible.
Upshot
StyleX dramatically minimized Facebook.com’s CSS to just 130KB initially, seamlessly covering all features without encountering loading order complexities. After three years of refinement, it has grown to 170KB, yet remains battle-tested and now available as an open-source solution. While Tailwind excels for small teams, StyleX shines in the realm of larger projects and multi-team collaborations, providing indispensable tooling for building comprehensive design systems across organizations. Meta’s decision to open-source StyleX marks a significant step forward, addressing a crucial need in the development landscape.