Skip to content

Bug: CSS custom property styles escape @layer when useCSSLayers: true #1611

Description

@cixzhang

Describe the issue

Bug: CSS custom property styles escape @layer when useCSSLayers: true

Summary

When useCSSLayers: true is set, stylex.create() rules that set CSS custom properties (e.g. '--my-var': 'value') are emitted outside any @layer, making them impossible to override from external CSS layers.

Root Cause

In processStylexRules (babel-plugin/lib/index.js):

  1. getPriority('--anything') returns 1 (correct — custom properties are intentionally prioritized above shorthands at 0)
  2. Rules are grouped by Math.floor(priority / 1000) → priority 1 maps to priorityLevel 0
  3. PriorityLevel 0 also contains all shorthand/base properties (priority 0)
  4. The layer-wrapping check uses the first rule's raw priority in the group:
    const pri = group[0][2];
    return useLayers && pri > 0 ? `@layer ${layerName(index)}{...}` : collectedCSS;
  5. Since priority-0 rules sort before priority-1 rules, pri is always 0 for this group
  6. The entire priorityLevel-0 group — including custom property rules — is emitted unlayered

Impact

Unlayered CSS always beats layered CSS in the cascade, regardless of layer ordering. This means:

  • External @layer rules (e.g. theme overrides, design system layers) can never override StyleX-generated custom property assignments
  • This defeats the purpose of useCSSLayers, which exists to allow external CSS to participate in cascade ordering with StyleX output

Reproduction

// component.tsx
import * as stylex from '@stylexjs/stylex';

const styles = stylex.create({
  card: {
    '--card-padding': '16px',  // priority 1 → should be in a layer
    color: 'red',              // priority 2000+ → in a layer ✓
  },
});
/* Expected output with useCSSLayers: true */
@layer priority1 {
  .x1abc { --card-padding: 16px; }
  .x2def { color: red; }
}

/* Actual output */
.x1abc { --card-padding: 16px; }  /* ← unlayered! */
@layer priority1 {
  .x2def { color: red; }
}

External CSS in any @layer cannot override --card-padding:

/* This loses to the unlayered StyleX output, regardless of layer order */
@layer my-theme {
  .card { --card-padding: 20px; }
}

Suggested Fix

The simplest fix: when useCSSLayers is true, wrap all groups in layers, including priorityLevel 0. The pri > 0 guard was likely intended to skip defineVars output (:root declarations), but those are filtered into constantRules earlier in the function and don't reach this code path.

-    return useLayers && pri > 0 ? `@layer ${layerName(index)}{\\n${collectedCSS}\\n}` : collectedCSS;
+    return useLayers ? `@layer ${layerName(index)}{\\n${collectedCSS}\\n}` : collectedCSS;

Environment

  • @stylexjs/babel-plugin: (checked against source in npm)
  • @stylexjs/unplugin: used with Vite + useCSSLayers: true

Expected behavior

/* Expected output with useCSSLayers: true */
@layer priority1 {
  .x1abc { --card-padding: 16px; }
  .x2def { color: red; }
}
/* Actual output */
@layer priority1 {
  .x2def { color: red; }
}
.x1abc { --card-padding: 16px; }  /* ← unlayered! */

Steps to reproduce

Reproduction

// component.tsx
import * as stylex from '@stylexjs/stylex';

const styles = stylex.create({
  card: {
    '--card-padding': '16px',  // priority 1 → should be in a layer
    color: 'red',              // priority 2000+ → in a layer ✓
  },
});

Using Vite with useCSSLayers: true.

Test case

Reproduction

// component.tsx
import * as stylex from '@stylexjs/stylex';

const styles = stylex.create({
  card: {
    '--card-padding': '16px',  // priority 1 → should be in a layer
    color: 'red',              // priority 2000+ → in a layer ✓
  },
});
/* Expected output with useCSSLayers: true */
@layer priority1 {
  .x1abc { --card-padding: 16px; }
  .x2def { color: red; }
}

/* Actual output */
.x1abc { --card-padding: 16px; }  /* ← unlayered! */
@layer priority1 {
  .x2def { color: red; }
}

External CSS in any @layer cannot override --card-padding:

/* This loses to the unlayered StyleX output, regardless of layer order */
@layer my-theme {
  .card { --card-padding: 20px; }
}

Additional comments

See P2270190221 for full internal output of XDS's storybook generated CSS to see the issue in prod.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions