DEBUGED USING CLAUDE SONNET 4.8.
Related packages
Describe the bug
When a styled-jsx rule leads with the styled-jsx scope placeholder & (e.g. & :global(.foo) or & .foo), the plugin emits an unmatchable selector and the styles silently never apply.
Internally the plugin scopes CSS with lightningcss. Per the CSS Nesting spec, lightningcss expands a top-level & into :scope. The plugin's scoping pass then prepends the generated scope class to that compound, producing:
.jsx-9fdf90a78b4c88e0:scope .foo { ... }
.jsx-HASH:scope requires a single element that is simultaneously the class .jsx-HASH and a scoping root (:scope resolves to :root/<html> in a plain stylesheet). A normal <div> never satisfies both, so every &-led rule is dead.
Expected output (what the legacy Babel styled-jsx transform produces) is the scope class replacing the &/:scope:
.jsx-9fdf90a78b4c88e0 .foo { ... }
Selectors that lead with a real class are unaffected, because no :scope is introduced:
/* source */ .wrapper :global(.foo) { ... }
/* output */ .wrapper.jsx-HASH .foo { ... } /* ✅ correct */
So the bug is specifically: a leading & (top-level scope placeholder) → :scope → class is appended (.jsx-HASH:scope) rather than substituted (.jsx-HASH). It affects both & :global(...) and & .localClass forms. In our codebase this silently broke 91 generated selectors across ~13 components.
Root cause is in the plugin's scopeSelector: for a lone :scope compound (originating from &) it inserts the scope class before the :scope pseudo-class instead of replacing the :scope token with the scope class.
Reproduction
https://stackblitz.com/edit/vitejs-vite-j1saqhrr?file=dist%2Fvite-starter.js
Steps to reproduce
- Create repro.tsx:
import css from "styled-jsx/css";
export const FieldStyle = css`
& :global(.foo) {
display: flex;
}
`;
- Build with Vite 8 (Rolldown) using the plugin:
// vite.config.ts
import { defineConfig } from "vite";
import styledJsx from "@rolldown/plugin-styled-jsx";
export default defineConfig({
plugins: [styledJsx()],
build: { lib: { entry: "repro.tsx", formats: ["es"] } },
});
- Inspect the emitted CSS string in the build output.
Actual:
.jsx-9fdf90a78b4c88e0:scope .foo { display: flex; }
→ never matches; styles do not apply at runtime.
Expected:
.jsx-9fdf90a78b4c88e0 .foo { display: flex; }
Contrast — replacing the leading & with a real class compiles correctly:
/* source: .x :global(.foo) */
.x.jsx-9fdf90a78b4c88e0 .foo { display: flex; } ✅
System Info
|
|
| @rolldown/plugin-styled-jsx |
0.1.1 (latest published; also affects 0.1.0) |
| lightningcss (plugin dep) |
1.32.0 |
| vite |
8.0.16 (Rolldown + Oxc) |
| @vitejs/plugin-react |
6.0.2 |
| styled-jsx |
5.1.7 |
| Node |
24.10.0 |
| OS |
Windows |
Used Package Manager
npm
Validations
DEBUGED USING CLAUDE SONNET 4.8.
Related packages
Describe the bug
When a styled-jsx rule leads with the styled-jsx scope placeholder
&(e.g.& :global(.foo)or& .foo), the plugin emits an unmatchable selector and the styles silently never apply.Internally the plugin scopes CSS with lightningcss. Per the CSS Nesting spec, lightningcss expands a top-level
&into:scope. The plugin's scoping pass then prepends the generated scope class to that compound, producing:.jsx-HASH:scoperequires a single element that is simultaneously the class.jsx-HASHand a scoping root (:scoperesolves to:root/<html>in a plain stylesheet). A normal<div>never satisfies both, so every&-led rule is dead.Expected output (what the legacy Babel styled-jsx transform produces) is the scope class replacing the
&/:scope:Selectors that lead with a real class are unaffected, because no
:scopeis introduced:So the bug is specifically: a leading
&(top-level scope placeholder) →:scope→ class is appended (.jsx-HASH:scope) rather than substituted (.jsx-HASH). It affects both& :global(...)and &.localClassforms. In our codebase this silently broke 91 generated selectors across ~13 components.Root cause is in the plugin's
scopeSelector: for a lone:scopecompound (originating from&) it inserts the scope class before the:scopepseudo-class instead of replacing the:scopetoken with the scope class.Reproduction
https://stackblitz.com/edit/vitejs-vite-j1saqhrr?file=dist%2Fvite-starter.js
Steps to reproduce
Actual:
→ never matches; styles do not apply at runtime.
Expected:
Contrast — replacing the leading
&with a real class compiles correctly:System Info
Used Package Manager
npm
Validations