npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@octanejs/stylex

v0.1.56

Published

StyleX bindings for the octane renderer — reuses StyleX's framework-agnostic compiler core and routes the generated atomic CSS through octane's stylesheet channel (client dedupe + SSR render().css).

Readme

@octanejs/stylex

StyleX for the octane UI framework.

Installation

npm install @octanejs/stylex
pnpm add @octanejs/stylex

StyleX is a build-time CSS-in-JS system: a compiler turns stylex.create({...}) into atomic class names and extracts the CSS, and stylex.props(...) produces the props you apply to an element. StyleX can resolve that composition at build time; otherwise its small merge runs at runtime. So — unlike the zustand/motion runtime bindings — the octane integration is the compiler pass, shipped in two parts:

  • @octanejs/stylex — the authoring surface you import in components. It re-exports @stylexjs/stylex (so the runtime props/attrs and all the types work) and is registered as a StyleX import source so the compiler finds your stylex.* call sites.
  • @octanejs/stylex/vite — the Vite plugin that runs the StyleX compiler over octane's compiled .tsrx output and emits one static atomic stylesheet (virtual:stylex.css). CSS rules are extracted; the JavaScript works with class names and inline values. Whether a props merge remains depends on what StyleX can resolve at compile time.

Setup

Add the plugin after octane() and import the generated sheet once:

// vite.config.ts
import { octane } from '@octanejs/vite-plugin';
import { stylex } from '@octanejs/stylex/vite';
import { knownAttributeSpreads } from '@octanejs/stylex/compiler/contract';

export default {
  plugins: [octane({ knownAttributeSpreads }), stylex()],
};
// app entry
import 'virtual:stylex.css';

For signal-aware native sx authoring in .tsrx, select the matching type-check provider in tsconfig.json. It delegates to Octane's normal virtual TSX compiler with the same native attribute contract:

{
  "tsrx": { "compiler": "@octanejs/stylex/compiler" }
}

Usage

import * as stylex from '@octanejs/stylex';

const styles = stylex.create({
  root: { padding: 16, color: 'tomato' },
  active: { color: 'blue' },
});

export function Card(props) @{
  <div {...stylex.props(styles.root, props.on && styles.active)}>{'Card'}</div>
}

stylex.props() returns { className?, style? }. Octane host elements take that directly — the spread handler maps both className and class to the class attribute and applies a style object — so there's no octane-specific props() variant to learn. stylex.attrs() (which returns { class, style } as a string) is also re-exported for raw-attribute contexts.

The compiler contract specializes props() and native sx, not attrs(). If an application already specializes @stylexjs/stylex.attrs, keep that runtime import and contract when adopting sx in the same file. An adapter type import supplies the native JSX extension without replacing the runtime namespace. A blanket import replacement can turn those existing spreads into generic host-prop work.

Native sx and signals

With the compiler contract above, a native sx expression accepts StyleX styles and signal values in dynamic style arguments:

import * as stylex from '@octanejs/stylex';
import type { SignalHandle } from 'octane/signals';

const styles = stylex.create({
  height: (height: number | null) => ({ height }),
});

export function Panel({ height$ }: { height$: SignalHandle<number | null> }) @{
  <div sx={styles.height(height$)} />
}

Octane reads the signal within the native binding; StyleX still decides the class names, units, null handling, and precedence. Stylesheets remain separate from that binding. A signal containing a complete StyleX style is supported by the same sx={appearance$} syntax. Component-owned sx props are ordinary props and are not rewritten.

Changing which style objects are applied recomputes their composition. That is expected: StyleX may compile locally known conditions into an object lookup, or retain stylex.props where a merge is needed. Octane applies the resulting fields directly and skips unchanged DOM writes. The compiler contract does not by itself eliminate a dynamic StyleX merge; a value-only fast path is a separate StyleX compiler optimization, not a second merge implementation in Octane.

Type checking unwraps only native signal handles inside configured .tsrx sx expressions. Dynamic style functions retain their original parameter types: passing a boolean signal to the numeric height function is still an error, as is passing a signal to styles.height(...) outside that context. Plain .tsx does not use the virtual TSRX type checker; use an explicit sampled value such as sx={styles.height(height$.get())} there. Sampling a payload property likewise requires .get(), for example sx={appearance$.get().selected}; appearance$.selected is a property of the handle, not its value.

How it works

octane() (enforce: 'pre') compiles .tsrx → JS, preserving the stylex.* calls. stylex() (enforce: 'post') then runs @stylexjs/babel-plugin over that output: each stylex.create/props/keyframes/defineVars/createTheme call is replaced with its compiled atomic form, and the extracted rules from every module are folded into virtual:stylex.css — deduped by content-hashed key and ordered by StyleX's baked-in cascade priority (so import order is irrelevant). In dev, editing a file re-aggregates the sheet (HMR full-reload). In a production build, where the virtual module can be loaded before every styled module has been transformed, the sheet is finalized in generateBundle once all rules are collected — so the shipped CSS always contains every rule regardless of module/transform order.

Options

stylex(options):

  • include — files to scan (default: .tsrx/.tsx/.jsx/.ts/.js).
  • importSources — specifiers treated as StyleX (default: @octanejs/stylex + @stylexjs/stylex).
  • dev — force dev/prod compilation (default: dev while Vite is serving).
  • useCSSLayers — emit @layer rules instead of the :not(#\#) specificity hack.
  • unstable_moduleResolution — StyleX cross-file token (.stylex.ts) resolution.
  • stylexOptions — escape hatch for any other @stylexjs/babel-plugin option.

Divergences

  • Native sx requires Octane's explicit compiler contract. The spread form remains supported without that shorthand.
  • Reactive sx currently requires an inline native template. Stored JSX, explicitly keyed native JSX descriptors, hoisted head tags, overlapping class/style writers, and scoped classes on that same host report a compiler error. Keyed @for rows are supported. Signal reads inside callbacks, constructors, or tagged templates are not lifted.
  • Renderer-free views require statically named dynamic style functions. Select between recipes with sx={wide$ ? styles.width(size$) : styles.height(size$)}; computed calls such as styles[recipe$](size$) cannot yet be proved safe by the renderer-free compiler, even though normal rendering supports them.
  • The plugin runs the StyleX compiler on octane's output, so StyleX's own source-scanning tools (the PostCSS plugin) are not used and not needed.

Status

Current scope, known divergences, and verification status are tracked in the generated bindings status table, sourced from this package's status.json.