Skip to content

Instantly share code, notes, and snippets.

@PatrickJS
Last active May 31, 2026 22:58
Show Gist options
  • Select an option

  • Save PatrickJS/f35efaa74339b9f1307543e88ce2f5ad to your computer and use it in GitHub Desktop.

Select an option

Save PatrickJS/f35efaa74339b9f1307543e88ce2f5ad to your computer and use it in GitHub Desktop.

Deeper StyleX integration should look like a compiler feature, not a runtime styling dependency.

The right model is:

tsx source
  -> parse framework graph
  -> parse StyleX calls
  -> validate static style facts
  -> merge styles into chunk/route/component plan
  -> emit CSS + class/style DOM ops + manifest metadata

So authors write normal app code:

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

const styles = stylex.create({
  card: {
    padding: 16,
    borderRadius: 8,
    backgroundColor: 'white',
  },
  selected: {
    borderColor: 'blue',
  },
  progress: (value: number) => ({
    '--progress': `${value}%`,
  }),
});

export function CatalogCard() {
  const selected = useSignal(false);
  const progress = useComputed(() => selected.value ? 100 : 25);

  return (
    <article
      sx={[
        styles.card,
        selected.value && styles.selected,
        styles.progress(progress.value),
      ]}
    />
  );
}

Then the compiler emits:

  • static atomic CSS
  • class attrs for static/conditional styles
  • style CSS variable ops for dynamic StyleX functions
  • reactive DOM bindings for selected.value and progress.value
  • CSS deps in chunk_manifest.json

No browser StyleX runtime should be needed.

Where this gets interesting for this framework:

  1. StyleX becomes another compiler IR Add StyleFacts / StylePlan alongside FrameworkGraph, OptimizationPlan, and EmitPlan.

  2. Chunk optimizer owns CSS too A component chunk should know its JS symbols and StyleX class dependencies. Route/component/symbol chunking should produce matching CSS preload/prefetch hints.

  3. Signals drive styles without VDOM Static class decisions become compiled class ops. Dynamic StyleX functions become CSS custom property updates through dom_ops.js.

  4. Themes become resumable metadata stylex.defineVars() and stylex.createTheme() map well to a typed theme manifest. The server can serialize active theme ids in resume_manifest.json; the loader only applies a class/vars, not style objects.

  5. JSX types get cleaner Our JSX host element types can add sx?: StyleXStyles | StyleXStyles[], while custom components can explicitly accept style props. StyleX docs also call out sx for host JSX and stylex.attrs() for non-React environments, which fits us because we compile JSX ourselves.

The key rule: do not bolt StyleX on as a Vite/Babel sidecar that has its own worldview. Make it a first-class compiler pass so styles, chunks, DOM ops, signals, themes, and resumability all share the same manifest model.

Sources: StyleX defining styles, stylex.props / sx, stylex.defineVars.

Using chat-handoff to turn this side-chat into a main-thread implementation handoff.

Context: The side-chat explored deeper compiler-level styling integration for Tailwind v4 and StyleX. The agreed direction is that StyleX should be the underlying atomic CSS engine/model, while Tailwind v4 becomes a syntax dialect that lowers into the same framework-owned StyleGraph.

Decision: Use one compiler style pipeline for Tailwind strings, StyleX-style objects, variants, conditional class arrays, and runtime class passthroughs.

Tailwind should not remain a separate runtime/build system. Author-facing Tailwind utility syntax should compile into StyleX-like atomic CSS rules, style chunks, and style manifests.

Target model:

Tailwind class strings
StyleX/framework style.create(...)
class={[designSystem.styles, variants(...), appStyles.override, props.class]}
theme tokens
  -> StyleGraph
  -> atomic CSS engine
  -> styles.css / style chunks / style_manifest.json / DOM ops

Constraints:

  • StyleX is the engine/model: atomic CSS generation, deterministic order, conflict resolution, variants, CSS chunking.
  • Tailwind v4 is an authoring dialect: utility classes lower into the same atomic IR.
  • class={[...]} composition must be first-class, not treated as string concatenation.
  • Preserve ordering semantics so design-system styles, variants, app overrides, and props.class behave predictably.
  • Dynamic style decisions from signal.value, computed.value, resources, or props should become compiled class/style DOM ops, not rerenders.
  • This should integrate with existing manifests, chunk optimizer, SSR resume payloads, source/dist viewer, and lazy loader.

Next action: Create a compiler style architecture spec and then implement the first slice:

  1. Add StyleGraphPass to packages/compiler.
  2. Define StyleGraph IR for:
    • Tailwind utility entries
    • StyleX/framework style.create(...) entries
    • variant calls
    • class array composition
    • conditional style entries
    • runtime passthrough classes like props.class
  3. Emit dist/style_manifest.json.
  4. Emit atomic CSS output, initially one dist/styles.css.
  5. Add generated DOM ops for dynamic class bindings.
  6. Add tests proving mixed usage works:
class={[
  "rounded-md border p-4 hover:border-accent",
  designSystem.card,
  variants.tone(status.value),
  pinned.value && appStyles.pinned,
  props.class,
]}

Do not carry forward:

  • Do not bolt raw Tailwind and raw StyleX beside each other as parallel systems.
  • Do not require a Tailwind build step for examples right now.
  • Do not flatten class arrays too early; preserve structure for static extraction, dynamic reads, ordering, and diagnostics.
  • Do not make StyleX a runtime dependency story first. The important integration is compiler-level style IR and atomic CSS emission.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment