Bug: CSS custom property styles escape `@layer` when `useCSSLayers: true`
- Dominant language
- JavaScript
- Stars
- 10.3k
- Forks
- 481
- Avg merge
- 3d 8h
- Merged PRs (30d)
- 13
Description
### 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](https://github.com/nicolo-ribaudo/comment-parser/blob/main/src/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:
```js
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
```js
// 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 ✓
},
});
```
```css
/* 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`:
```css
/* 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.
```diff
- 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
```css
/* Expected output with useCSSLayers: true */
@layer priority1 {
.x1abc { --card-padding: 16px; }
.x2def { color: red; }
}
```
```css
/* Actual output */
@layer priority1 {
.x2def { color: red; }
}
.x1abc { --card-padding: 16px; } /* ← unlayered! */
```
### Steps to reproduce
### Reproduction
```js
// 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
```js
// 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 ✓
},
});
```
```css
/* 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`:
```css
/* 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.
Contributor guide
Assessment
This issue has not been assessed yet.