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):
getPriority('--anything') returns 1 (correct — custom properties are intentionally prioritized above shorthands at 0)
- Rules are grouped by
Math.floor(priority / 1000) → priority 1 maps to priorityLevel 0
- PriorityLevel 0 also contains all shorthand/base properties (priority 0)
- 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;
- Since priority-0 rules sort before priority-1 rules,
pri is always 0 for this group
- 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.
Describe the issue
Bug: CSS custom property styles escape
@layerwhenuseCSSLayers: trueSummary
When
useCSSLayers: trueis 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):getPriority('--anything')returns 1 (correct — custom properties are intentionally prioritized above shorthands at 0)Math.floor(priority / 1000)→ priority 1 maps to priorityLevel 0priis always 0 for this groupImpact
Unlayered CSS always beats layered CSS in the cascade, regardless of layer ordering. This means:
@layerrules (e.g. theme overrides, design system layers) can never override StyleX-generated custom property assignmentsuseCSSLayers, which exists to allow external CSS to participate in cascade ordering with StyleX outputReproduction
External CSS in any
@layercannot override--card-padding:Suggested Fix
The simplest fix: when
useCSSLayersis true, wrap all groups in layers, including priorityLevel 0. Thepri > 0guard was likely intended to skipdefineVarsoutput (:rootdeclarations), but those are filtered intoconstantRulesearlier in the function and don't reach this code path.Environment
@stylexjs/babel-plugin: (checked against source in npm)@stylexjs/unplugin: used with Vite +useCSSLayers: trueExpected behavior
Steps to reproduce
Reproduction
Using Vite with
useCSSLayers: true.Test case
Reproduction
External CSS in any
@layercannot override--card-padding:Additional comments
See P2270190221 for full internal output of XDS's storybook generated CSS to see the issue in prod.