Repository navigation
UI Consistency: 1 issue found
apps/web/src/index.css (lines 1172-1173) — The new html.dark[data-theme-id] branch introduces the only raw .dark selector in the web stylesheet outside the @custom-variant dark/light definitions, which the project convention reserves for those definitions. It is functionally a specificity hack: :root { @variant dark } emits :root:is(.dark, .dark *) (0,2,0), which outranks html[data-theme-id] (0,1,1) and is the actual cause of themes losing in dark mode. Suggested replacing the duplicated branch with a variant-safe qualifier (html[data-theme-id]:root, specificity 0,2,1) plus a short comment explaining the specificity requirement. An inline review comment with a suggested fix has been posted.
Details
Note
Your check run agent prompt is: .macroscope/check-run-agents/ui-consistency.md
More information about how Check Run Agents work can be found in our Help Center.
Analysis
- Verified the root cause: the
:roottoken block (apps/web/src/index.css:1011) contains a nested@variant dark, which Tailwind emits as:root:is(.dark, .dark *)— specificity (0,2,0). The theme mapping block at line 1172 ishtml[data-theme-id]— specificity (0,1,1). Both are unlayered, so the dark block wins and themed tokens are ignored in dark mode. The PR's diagnosis is correct. - Verified
.darkis toggled ondocument.documentElement(apps/web/src/hooks/useTheme.tsapplyTheme, plus the pre-hydration script inapps/web/index.html) anddata-theme-idis set on the same element (apps/web/src/themePalette.tsapplyThemePalette), so the added selector does match and does fix the reported bug. - Checked precedent:
git grep '\.dark' apps/web/src/**/*.cssreturns only the two@custom-variantdefinitions plus the line added by this PR — this is the first raw.darkselector in the stylesheet. - Considered alternatives: duplicating the whole token block under
@variant darkis worse;:root[data-theme-id]only ties at (0,2,0) and would rely on source order.html[data-theme-id]:rootreaches (0,2,1) — type + attribute + pseudo-class — beating the dark variant in both appearances without referencing.dark. - No other findings: the change touches a single selector, adds no declarations, and does not alter theme tokens, scroll/mask behavior, or component primitives.
🔍 Agent activity — 1 tool call
💭 Looking at the diff, I see it's adding
html.dark[data-theme-id]as a selector alongsidehtml[data-theme-id]. This is a raw.darkusage, which according to the rules should only appear in the custom-variant definitions fordarkandlight, not in theme-only declarations that should use@variant darkinstead. I need to check the context in index.css to understand if this is creating a duplicate selector branch or if there's a specific reason for this pattern. The PR is adding a duplicate selector with higher specificity to ensure theme tokens override dark-mode declarations elsewhere in the stylesheet. By includinghtml.dark[data-theme-id]alongside the existing selector, each gets evaluated independently by the browser, but the higher specificity (0,3,1 vs 0,2,1) makes the theme block win when both match. However, the instructions say raw.darkshould only appear in the custom-variant definitions, so this usage violates that rule—a dark-only branch should use@variant darkinstead. But that approach wouldn't produce the same specificity boost, so I need to check how the custom variant is actually defined to understand if there's a better way to achieve this. Looking at the PR description to understand what's happening — it seems the dark mode variant is overriding the selected theme tokens due to CSS specificity or ordering. The@variant darkblock generates selectors with specificity that's taking precedence over the theme values, so I need to trace through how the dark variant is being applied at the root level and why it's interfering with theme selection. I'm seeing the specificity issue now — in dark mode, the:root:is(.dark, .dark *)selector has specificity (0,2,0) because:rootis (0,1,0) and:is()takes the highest of its arguments at (0,1,0), whilehtml[data-theme-id]only has (0,1,1), so the dark variant wins out and overrides the theme attribute. The proposed fix addshtml.dark[data-theme-id]with specificity (0,2,1) to outrank the dark variant, which does solve the specificity problem. But I'm noticing a few concerns: the change introduces a raw.darkselector outside of the variant definitions, which violates the project's convention that raw.darkshould only appear in thedarkandlightcustom-variant blocks. There's also a semantic question — does the app actually apply the.darkclass to thehtmlelement itself, or is it applied elsewhere? And more fundamentally, this feels like a specificity hack rather than a proper solution; the cleaner approach would be to use@variant darkinside the block for dark overrides or handle specificity in a way that aligns with the project's variant conventions. Actually, I'm reconsidering — usinghtml[data-theme-id]:rootinstead would give (0,2,1) specificity without any raw.dark, which beats the light mode declarations and stays consistent with the project's conventions. That's a smaller, cleaner fix. Now I'm checking whether there's a cascade layer issue — the:rootblock at line 1011 appears to be top-level, outside any@layerdeclarations, and same with thehtml[data-theme-id]block at 1172, so specificity alone determines which wins. That's fine. But I need to verify that the.darkclass is actually being applied to the<html>element, and whetherdata-theme-idis set on the document root. I'm also wondering if there's alightvariant that could create the opposite problem — in light mode, the theme block would drop to (0,1,1) specificity and lose to any root-level light variant declarations. The.darkclass is applied to the<html>element, and the selectorhtml.dark[data-theme-id]matches as expected. Now I'm checking whether this fix actually resolves the regression—in Tailwind 4, the@variant darkon:rootexpands to:root:is(.dark, .dark *), and since the&ersand represents:root, the specificity comes down to:rootat 0,1,0 plus the:is()pseudo-class which takes the maximum specificity of its arguments. The.dark *selector has specificity 0,1,0, so the total is 0,2,0. Meanwhile,html[data-theme-id]has specificity 0,1,1, which means the theme selector wins and breaks dark mode styling. The bug is confirmed. Looking at whether the PR's fix violates the project's rules: the guidelines explicitly state that raw.darkshould only appear in the custom variant definitions, and the addedhtml.dark[data-theme-id]is a raw.darkoutside those definitions. While one could argue the rule about theme-only declarations doesn't strictly apply here since these declarations are mode-agnostic, the absolute statement about where raw.darkbelongs is clearly violated. There's also a potential functional gap—the fix only addresses specificity but may not fully resolve the underlying issue. I'm checking whether the light-side is handled and whether the specificity bump is sufficient against other dark overrides. The[data-app-sidebar]variant dark selector has lower specificity than the html selector, so that's not a conflict. But I'm wondering if something changed recently that broke this, or if there's a pre-existing mechanism that made it work before—maybe the theme block was previously inside a layer or positioned differently in the cascade. Looking at how Tailwind v4 handles nested@variant darkinside plain CSS rules, it emits the:rootblock followed by the:root:is(.dark, .dark *)variant block, which is where the specificity issue arises. The real problem is that raw.darkselector violates the style guide's preference for source-level logic over clever selectors, though the emitted selector itself is valid. I'm also considering whether the fix is complete and whether there are edge cases where the dark class gets applied to subtrees—like a theme preview component in settings that uses a.darkwrapper div to preview dark mode styling. Since:rootcan't be a descendant, that wouldn't match, but something like[data-app-sidebar]:is(.dark *)could potentially be affected. The real question is whether to duplicate the selector as a maintenance concern. I could usehtml[data-theme-id]:rootto get a specificity of 0,2,1, which would beat the raw.darkrule without relying on source order. But:root[data-theme-id]ties at 0,2,0 with the:root:is(.dark,...)variant, so it would depend on which comes later in the file—that feels fragile. The CSS selector list applies each selector's specificity independently, so the dark variant declarations land at (0,2,1) correctly. The theme block only overrides tokens it explicitly sets, leaving others like--info-foregroundto inherit from the dark block—that's fine. I need to verify the fix doesn't break nested variants like when@variant darkis used inside a scoped selector, which would emit a compound selector with:is(). The raw.darkselector violates the project's theme-variant convention, so I should flag this as a concrete finding and suggest bumping specificity without using raw.darkwhile adding a comment explaining why. A reviewer might push back asking if@variant darkwould work, but that requires duplicating declarations, so the most idiomatic fix within the project's patterns is a different approach. Let me check if there's precedent in the codebase for raising specificity — I'll search for:rootusage and any existing raw.darkselectors in the web CSS outside of variant definitions to see how the project typically handles this. So the.darkclass being added here is the only raw instance outside the custom-variant definitions in the web CSS, which directly violates the project's convention. I need to figure out if this is actually necessary or if there's a different root cause—maybe the dark tokens in:rootwere supposed to work differently, or perhaps thehtml[data-theme-id]selector had different behavior in dark mode previously. Let me trace through the git history to see how this was handled before. The real issue comes down to specificity: the:root @variant darkblock needs higher specificity to override the theme tokens for descendants without resorting to a raw.darkselector. I'll post an inline comment suggesting we replace those lines withhtml[data-theme-id]:rootinstead, which raises the specificity appropriately while keeping the comment explaining why this approach works. This is a genuine convention violation—the project keeps.darkonly in custom-variant definitions, so having it duplicated here as a specificity hack is undocumented and inconsistent. I'll fail the check with this finding and post the review.
1. modify_pr — One finding: the dark-mode fix introduces a raw .dark selector outside the dark/light custom-variant definitions. Suggested a variant-safe specificity bump that keeps the same behavior.
Agent Credits: 120 credits