0.5.7 overlay toggle switch (and other translate/rotate utilities) render broken: Tailwind v4 @property rules unregistered inside shadow DOM
Symptom
In [email protected], overlay UI utilities that rely on Tailwind v4's translate / rotate / scale shorthand render incorrectly. The most visible symptom is the outline-render toggle switch: in the OFF state the white thumb pops out of the track and hangs below it. The same class of bug affects the resize handles, slider thumb, and expand arrow — anywhere the overlay CSS uses @apply -translate-* / translate-*.
Reproduction on Chrome 149 with a Tailwind v4 host (also reproduces with a non-Tailwind host, verified against a Panda CSS project — the host stylesheet is irrelevant since the overlay is inside a shadow DOM).
Downgrading to [email protected] restores correct rendering (v0.5.6 is on Tailwind v3, which uses preflight-seeded --tw-translate-* rather than @property).
Screenshots
Root cause
React Scan mounts its overlay UI in a shadow DOM and injects the bundled Tailwind stylesheet as a <style> element inside that shadow root (packages/scan/src/core/index.ts). Tailwind v3 → v4 in 0.5.7 changed how -translate-y-1/2 etc. compile: instead of the preflight-seeded --tw-translate-* variables, v4 emits 62 @property rules to declare the initial-value (0).
Chromium does not register @property rules declared inside a shadow-DOM stylesheet. The CSSOM parses them into CSSPropertyRules (visible via stylesheet.cssRules), but the custom-property registry is empty for that shadow tree. Consequently --tw-translate-x has no computed value, and
translate: var(--tw-translate-x) var(--tw-translate-y);collapses to translate: none.
Measurement on .react-scan-toggle > div::before in the OFF state (react-scan 0.5.7, Chrome 149):
| property | value |
|---|---|
translate |
none |
--tw-translate-x |
"" (empty) |
--tw-translate-y |
calc(calc(1 / 2 * 100%) * -1) |
For the ON state, the input:checked + div::before selector applies translate-x-full which explicitly sets --tw-translate-x: 100%, so translate resolves — that's why the ON state visually looks OK while OFF is broken.
Document-level @property registrations do cascade into a shadow tree, verified in a minimal repro on Chrome 149.
Spec: CSS Properties and Values API L1.
Proposed fix (branch available)
I have a one-file fix on my fork:
hidenari-pixel/react-scan@fix/shadow-dom-property-registration (diff)
Approach: in initRootContainer(), extract every @property --…{…} block from the bundled overlay CSS with a regex before injecting the stylesheet into the shadow root, and mount just those rules under a <style data-react-scan="property-registrations"> in document.head. The remainder still goes into the shadow root as before.
After the fix, the same ::before in the OFF state resolves to:
| property | value |
|---|---|
translate |
0px -50% |
--tw-translate-x |
0 |
--tw-translate-y |
calc(calc(1 / 2 * 100%) * -1) |
and 62/62 @property rules move from the shadow tree to document.head.
- Change is ~14 lines in
packages/scan/src/core/index.tsplus a changeset entry pnpm --filter react-scan buildsucceedspnpm test:e2e— 47/47 pass, including the outline toggle instrumentation specs from #459- Manual verification in
kitchen-sinkon Chrome 149: toggle thumb renders centered in both OFF and ON states
Note
I tried to open this as a PR but hit "An owner of this repository has limited the ability to open a pull request to users that are collaborators on this repository." Happy to open the PR the moment the interaction limit is lifted, or invited as a collaborator, or feel free to cherry-pick the commit directly:
git remote add hidenari-pixel https://github.com/hidenari-pixel/react-scan.git
git fetch hidenari-pixel fix/shadow-dom-property-registration
git cherry-pick 2407c8bRelated open work: PR #458 (React port of the toolbar UI) may incidentally sidestep this if it moves off Tailwind v4 utility classes, but it is still open and orthogonal.
Source: aidenybai/react-scan