StyleX

The components are StyleX leaves. Use the tokens in your own leaves, compile with Vite or Next, or run no compiler at all.

import * as stylex from "@stylexjs/stylex";
import { vlak, mq } from "@noorddev/vlak-react/tokens.stylex";

const styles = stylex.create({
  panel: {
    borderTopWidth: vlak.hairline,
    borderTopStyle: "solid",
    borderTopColor: vlak.divider,
    padding: vlak.pad,
    color: vlak.ink,
    backgroundColor: vlak.paper,
    [mq.phone]: { padding: 12 },
    [mq.reduce]: { transition: "none" },
  },
});

export function Panel(props: React.HTMLAttributes<HTMLDivElement>) {
  return <div {...props} {...stylex.props(styles.panel)} />;
}

vlak is a defineVars set that aliases the CSS custom properties; it is not a second scale. Change a token in CSS and the leaf follows. mq is a defineConsts set of media queries, so the compiler folds them and your breakpoints match the kit's.

vlak.*Aliases
paper, ink, gray--bg, --text, --text-secondary
divider, dividerSubtle, gridLine, tableAltthe four grays
controlBorder--control-border, 3:1 against the ground
radiusSm, radius, radiusChrome, radiusInthe radius family
pad, gutter, module--pad, --gutter, --grid-size
hit, controlH, controlFs, controlLabelthe control scale
durationSnap, duration, durationConfirm, ease, transitionmotion
textScale, hairline--text-scale and the literal 1px
mq.*Query
phonemax-width: 640px
mobileGridmax-width: 480px
at900, at899the rail boundary
railmin-width: 1024px
widemin-width: 1440px
reduceprefers-reduced-motion: reduce
touchhover: none
forcedColorsforced-colors: active

vlakFont and vlakMono export the two font stacks as strings.

StyleX resolves defineVars at compile time, so the file that defines them must pass through your compiler. @noorddev/vlak-react/tokens.stylex ships uncompiled for that reason; its hashes match the package's own compiled leaves. The rest of the package is already compiled and needs nothing.

// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import stylex from "@stylexjs/unplugin";

export default defineConfig({
  plugins: [
    // Finds packages that depend on @stylexjs/stylex, @noorddev/vlak-react
    // included, and compiles their StyleX along with yours.
    stylex.vite({ useCSSLayers: true }),
    react(),
  ],
});

@stylexjs/unplugin discovers installed packages that depend on @stylexjs/stylex and transforms them. If a package is missed, list it under externalPackages. The compiled CSS is appended to your bundle's CSS asset.

// next.config.mjs (what vlak.dev does)
// A Babel pre-loader runs @stylexjs/babel-plugin on app/ and components/
// only. SWC keeps everything else. Vlak arrives precompiled from
// node_modules, so it is not in the include list.
const stylexBabelOptions = {
  dev: false,
  runtimeInjection: false,
  treeshakeCompensation: true,
  unstable_moduleResolution: { type: "commonJS", rootDir: process.cwd() },
};

const stylexRule = (test, isTSX) => ({
  test,
  enforce: "pre",
  include: [path.join(here, "app"), path.join(here, "components")],
  use: [{
    loader: "babel-loader",
    options: {
      babelrc: false,
      configFile: false,
      plugins: [
        ["@babel/plugin-syntax-typescript", { isTSX }],
        "@babel/plugin-syntax-jsx",
        ["@stylexjs/babel-plugin", stylexBabelOptions],
      ],
    },
  }],
});

export default {
  output: "export",
  webpack: (config) => {
    config.module.rules.unshift(stylexRule(/\.tsx$/, true), stylexRule(/\.ts$/, false));
    return config;
  },
};
// postcss.config.cjs: extracts the compiled CSS into the file that holds "@stylex;"
module.exports = {
  plugins: {
    "@stylexjs/postcss-plugin": {
      include: ["app/**/*.{js,jsx,ts,tsx}", "components/**/*.{js,jsx,ts,tsx}"],
      babelConfig: {
        babelrc: false,
        parserOpts: { plugins: ["typescript", "jsx"] },
        plugins: [["@stylexjs/babel-plugin", stylexBabelOptions]],
      },
      useCSSLayers: false,
    },
  },
};

/* app/stylex.css */
@stylex;

Two halves: a Babel pass that rewrites stylex.* calls, and the PostCSS plugin that collects the CSS. Both read the same options so class hashes match. This site compiles its own leaves this way and consumes Vlak precompiled. The @stylexjs/nextjs-plugin package is the shorter road if you are not on a static export.

import "@noorddev/vlak-react/css";
import { Button } from "@noorddev/vlak-react";

The package works with no StyleX toolchain at all: the leaves are compiled at publish time and the stylesheet carries the result. Add a compiler only when you write leaves of your own.

npx @noorddev/vlak-cli add button dialog

components/vlak/
  button.tsx          the leaf: stylex.create + rs-* classes
  dialog.tsx
  cx.ts, rs.ts        shared helpers, installed once
  tokens.stylex.ts    the same vars, local to your compile
styles/vlak/
  button.css          the CSS projection, if you would rather not compile

add copies the source leaf, its registry dependencies, and the shared helpers once. From there the file is yours: edit it, and your compiler owns the output. Vendored leaves import a local tokens.stylex.ts, so the same rules about including it in the compile apply. The CSS projection lands next to it for pages that skip React.