Skip to content

feat: update dark mode mixin - #1596

Merged
chrisschnaars merged 4 commits into
nextfrom
chrisschnaars/dark-mode-fixes
Aug 19, 2026
Merged

feat: update dark mode mixin#1596
chrisschnaars merged 4 commits into
nextfrom
chrisschnaars/dark-mode-fixes

Conversation

@chrisschnaars

@chrisschnaars chrisschnaars commented Aug 17, 2026

Copy link
Copy Markdown
Contributor
🎫 Resolve ISSUE_ID

🎯 What does this PR do?

Fixes dark-mode CSS in Card, Callout, Glossary, and Image bleeding in from the wrong place when they're rendered inside an app that has more than one independent data-color-mode scope on the page.

The core issue: our dark-mode mixin, when used inside a nested selector, compiles to an ancestor-descendant selector — [data-color-mode='dark'] & { }. That matches when any ancestor, at any depth, carries data-color-mode='dark'] — not the nearest one. That's fine as long as a page only ever has one data-color-mode declaration. It breaks once a consumer has two independent scopes that can disagree.

That's exactly what happens in readme's SuperHub: the admin shell sets its own theme preference on <html data-color-mode>, while the hub content the admin is previewing carries its own, independent preference on .rm-ReadMe[data-color-mode]. Because our selector doesn't care about proximity, a Card/Callout/Glossary/Image rendered inside a light .rm-ReadMe still matched the dark <html> ancestor and rendered dark — an admin browsing in dark mode would see dark-mode cards and callouts even while previewing a project whose hub is set to light.

The fix: a new mixin, when-color-mode-dark($root), that anchors to a specific wrapper's own attribute (#{$root}[data-color-mode='dark'] & { }) instead of matching any ancestor — so only that one wrapper's declared mode matters, no matter what any other ancestor says. Card, Callout (all 5 variants), and Image now use it via their existing @include; Glossary, which hand-inlined the same buggy selector, was fixed the same way directly. All four are anchored to '.rm-ReadMe', readme's hub color-mode root — see the comment on when-color-mode-dark for why that's currently hardcoded (readme is this package's only real consumer today) and what to do instead if that ever stops being true. Added a warning comment to the existing dark-mode mixin so this doesn't get reached for by default the next time someone adds dark-mode CSS to a component that needs to be scoped this way.

Also includes two small demo-app changes:

  • example/Root.tsx's theme-toggle wrapper now carries the .rm-ReadMe class, so the local demo (npm start) actually exercises the anchored selector above — without it, toggling the demo's theme selector would have silently stopped affecting these four components.
  • example/Header.tsx / example/styles/header.scss: replaced the theme <select> dropdown with a segmented icon-button group (sun/moon/desktop), unrelated to the bug fix but bundled in since it touches the same theme-toggle UI.

🧪 QA tips

  • npm start, open the demo, toggle the theme selector between Light / Dark / System — confirm Card, Callout (all variants), the image frame background, and the Glossary tooltip all follow the toggle correctly in each state.
  • In readme: set the admin's own theme to Dark, open a project whose hub is set to Light, and preview a doc with a Card/Callout — confirm they render light, not dark. (This is the scenario that was broken before this fix.)
  • Confirm nothing regressed for the case where .rm-ReadMe and <html> agree (both light or both dark) — should look identical to before.

📸 Screenshot or Loom

@chrisschnaars
chrisschnaars marked this pull request as draft August 17, 2026 21:10
@greptile-apps

greptile-apps Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; both previously reported issues are resolved in the current code.

Reviews (2): Last reviewed commit: "style: infer demo theme options from a r..." | Re-trigger Greptile

Comment thread components/Glossary/style.scss
Comment thread example/Header.tsx Outdated
@chrisschnaars
chrisschnaars requested a review from runnabro August 18, 2026 21:30
@chrisschnaars
chrisschnaars marked this pull request as ready for review August 18, 2026 21:37
@chrisschnaars
chrisschnaars merged commit ee22605 into next Aug 19, 2026
9 checks passed
@chrisschnaars
chrisschnaars deleted the chrisschnaars/dark-mode-fixes branch August 19, 2026 13:21
rafegoldberg pushed a commit that referenced this pull request Aug 19, 2026
## Version 15.1.0
### ✨ New & Improved

* update dark mode mixin ([#1596](#1596)) ([ee22605](ee22605))

### 🛠 Fixes & Updates

* **mdxish:** bound loose-emphasis normalization scans to stay linear ([#1594](#1594)) ([09f5022](09f5022))

<!--SKIP CI-->
@rafegoldberg

Copy link
Copy Markdown
Collaborator

This PR was released!

🚀 Changes included in v15.1.0

chrisschnaars added a commit that referenced this pull request Sep 3, 2026
…dCompiler (#1608)

## Summary
- `tailwindCompiler`/`TailwindStyle` accept a new `darkModeRootSelector`
option. When supplied, the generated `dark:` custom-variant is scoped to
that root's own `[darkModeDataAttribute]`, self or descendant, instead
of matching the attribute on *any* ancestor.
- Omitted (the default), behavior is byte-for-byte unchanged from today.
- Mirrors `when-color-mode-dark($root)`
(`styles/mixins/when-color-mode-dark.scss`, added in #1596) — same
rationale: a page can have two independent `data-color-mode` scopes
(e.g. readme's SuperHub admin shell vs. the hub content it's
previewing), and the current ancestor-matching selector can't tell them
apart. No default root selector is provided, for the same reason
`when-color-mode-dark` doesn't have one — see that mixin's doc comment,
and the new doc comment on `tailwindCompiler`'s `darkModeRootSelector`
param, for the full reasoning.

## Test plan
- [x] `__tests__/utils/tailwind-compiler.test.ts` (new) — unit-tests the
generated `@custom-variant dark (...)` string directly (spies on
`tailwindcss.compile` since the real compile pipeline needs this
package's production string-import loaders, which aren't wired into
vitest — same reason the existing `TailwindStyle.test.tsx` mocks
`tailwindCompiler` rather than exercising real output)
- [x] `__tests__/components/TailwindStyle.test.tsx` — new case
confirming `darkModeRootSelector` threads through to `tailwindCompiler`
- [x] `npx vitest run` on both files — 9/9 passing
- [x] `npx eslint` — clean
- [x] `npx tsc --noEmit` — no new errors

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
rafegoldberg pushed a commit that referenced this pull request Sep 3, 2026
## Version 15.4.0
### ✨ New & Improved

* support scoping the dark: variant to a root selector in tailwindCompiler ([#1608](#1608)) ([df0f409](df0f409)), closes [#1596](#1596)

<!--SKIP CI-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants