Intro to Meta's StyleX
If you're working at a large company or on an open-source project that requires strict control over CSS styles and their extensibility, the StyleX library from Meta that might be a good choice for you.
StyleX provides a more rigid approach compared to CSS modules and Tailwind, but it can be beneficial when you need well-defined and locked-in styles. Let's apply StyleX to our current unstyled application.
Installation and Configuration
StyleX has installation instructions for Next.js in the docs that will always be up to date, but we'll run through them here as well.
The first step is to install dependencies. The @stylexjs/open-props
and @stylexjs/stylex
packages are required for the application, and we'll also add some dev dependencies:
pnpm add @stylexjs/open-props @stylexjs/stylex
pnpm add -D rimraf @stylexjs/babel-plugin @stylexjs/eslint-plugin @stylexjs/nextjs-plugin
This will create some new configuration files in our project.
Babel Setup
StyleX is developed as a compiler extension, which is configured via the .bablerc.js
file. The Next.js Babel presets will still be in place, but we'll add the StyleX Babel plugin on top of it. This will switch the compilation from the faster SWC to the slightly slower Babel because SWC cannot handle JavaScript plugins.
Here's how it looks:
// inside .babelrc.js
module.exports = {
presets: ["next/babel"],
plugins: [
[
"@stylexjs/babel-plugin",
{
dev: process.env.NODE_ENV === "development",
test: process.env.NODE_ENV === "test",
runtimeInjection: false,
genConditionalClasses: true,
treeshakeCompensation: true,
unstable_moduleResolution: {
type: "commonJS",
rootDir: __dirname,
},
},
],
],
};
ESLint Configuration
The ESLint configuration makes it so StyleX extensions without freaking out. The configuration file will also be changed from JSON to JS format:
// inside .eslintrc.js
module.exports = {
extends: "next/core-web-vitals",
plugins: ["@stylexjs"],
rules: {
// The Eslint rule still needs work, but you can
// enable it to test things out.
"@stylexjs/valid-styles": "error",
},
};
Next.js Configuration
As of this recording, the NextJS installation instructions use JS for the configuration. However, an App Router application gives an MJS (ModuleJS) config. Inside of the next.config.mjs
file, we need to import the StyleX plugin and export its output by default:
// inside next.config.mjs
/** @type {import('next').NextConfig} */
import stylexPlugin from "@stylexjs/nextjs-plugin";
const nextConfig = {
transpilePackages: ["@stylexjs/open-props"],
};
const __dirname = new URL(".", import.meta.url).pathname;
export default stylexPlugin({
rootDir: __dirname,
})(nextConfig);
Now that the setup is complete, let's start styling our application with StyleX.
Styling the Application
The first thing we'll do is set up a CSS reset in the globals.css
file. This will ensure that we start from a clean slate:
/* inside globals.css */
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
We can now import globals.css
and stylex
into the layout.tsx
file:
// inside layout.tsx
import * as stylex from "@stylexjs/stylex";
import "./globals.css";
Now we can move on to adding light and dark mode.
Light and Dark Mode
At the bottom of layout.tsx
, we'll establish styles using StyleX. We'll set up classes for html
and reset
, where the html
class will define the default light mode styles with an option for dark mode and the reset
class will reset the default styles:
const DARK = "@media (prefers-color-scheme: dark)";
const styles = stylex.create({
html: {
fontFamily: "system-ui, sans-serif",
backgroundColor: "white",
color: "black",
[DARK]: {
backgroundColor: "black",
color: "white",
},
},
reset: {
minHeight: "100%",
margin: 0,
padding: 0,
},
});
The [DARK]
syntax acts as a media query that applies the dark mode styles when the user prefers a dark color scheme.
Styles are then added as props on the tags by spreading the stylex.props
function:
// inside layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html {...stylex.props(styles.html, styles.reset)} lang="en">
<body {...stylex.props(styles.reset)}>{children}</body>
</html>
);
}
The stylex.props
function injects the class names into the tags, allowing StyleX to have both class name and style output.
Previewing our changes in Polypane, we can see that the light and dark mode styles are applied correctly:
Apply Styles to the Home
Component
Over in the page.tsx
file, your task is to import StyleX and set up styling using the styles provided here:
const MOBILE = "@media screen and (max-width: 768px)";
const s = stylex.create({
main: {
margin: "0 auto",
maxWidth: 960,
fontFamily: "sans-serif",
display: "flex",
flexWrap: "wrap",
},
card: {
padding: "0.2rem",
width: {
default: "33%",
[MOBILE]: "100%",
},
maxWidth: {
default: "33%",
[MOBILE]: "100%",
},
},
});
Apply these styles to the <main>
and <div key={product.id}>
tags.
In the next lesson, we'll complete the example and see how everything comes together with StyleX.