Skip to content

Shadcn: move theme tokens into tailwind.css, fix device dark mode, new theme switcher - #741

Open
hdprajwal wants to merge 2 commits into
masterfrom
shadcn-dark-mode-consolidation
Open

Shadcn: move theme tokens into tailwind.css, fix device dark mode, new theme switcher#741
hdprajwal wants to merge 2 commits into
masterfrom
shadcn-dark-mode-consolidation

Conversation

@hdprajwal

Copy link
Copy Markdown
Contributor

Summary: Move all the color values into tailwind.css so Tailwind and shadcn own the theme, fix dark mode when the brightness preference is "follow device", and rebuild the page brightness control in the settings panel.

Changes

  • User experience
    • Dark mode now works when brightness is set to "follow device". Before this, nothing put a dark class on <html> in that mode, so the shadcn theme tokens stayed on their light values while the rest of the page went dark. Only Button is installed today and its default variant happens to look right either way, so nothing visibly broke yet.
    • The navbar logo, the large logo on the intro page, and the map tiles now update when you change your device theme while the page is open. Before, they kept the old version until something else caused a re-render.
    • The page no longer flashes white before it loads on a dark device.
    • The page brightness control is now three buttons: system, light and dark. It used to be one button you clicked through. You can pick one directly, and it shows which one is active.
    • The old control sat inside a hardcoded blue pill that was wider than the control inside it, so you saw two mismatched pills. That is gone.
  • Logical changes
    • src/app/tailwind.css now holds every color value, in a :root block for light and a .dark block for dark. It also adds tokens shadcn has no name for (--primary-hover, --accent-subtle, --highlight) and the named colors used for data and status (--red through --purple), all exposed through @theme inline.
    • src/app/colors.css went from 108 lines to a 10 line alias block. It maps the old --color-* names onto the new tokens so the CSS and inline styles that are not migrated yet keep working. It shrinks as components move to Tailwind.
    • usePageBrightness always puts an explicit light or dark class on <html>, including in "follow device" mode, and listens for device theme changes.
    • index.html has a small inline script that sets that class before the first paint.
    • New ThemeSwitcher in src/shared/ui/, plus a small useControllableState hook so it works either controlled or uncontrolled.
    • localStorage reads and writes are wrapped in try/catch. This hook runs in the outermost provider, so an error there would blank the whole app instead of just losing the setting.
    • The dark: variant now matches the .dark element itself as well as its children, so dark: classes work on <html>.
  • Data
    • None
  • Refactors
    • Deleted ThemeToggle.css, 109 lines. The switcher is all Tailwind classes now.
    • TextInput.test.tsx mocked all of lucide-react with a single icon. Changed it to a partial mock, because it broke as soon as another icon showed up anywhere in its imports.

Out of scope/Future work:

  • The token values are the app's current hex colors on purpose, so this PR does not change how anything looks. I checked every old --color-* name against the production build and each one resolves to the same value it had before, in both light and dark. Swapping them for Tailwind's palette is a separate change.
  • --card and --popover are the same as --background in dark mode. Nothing renders them today. Once a shadcn popover, dropdown or card lands they will need a lighter value or they will be invisible against the page.
  • --destructive is #df7e7c, which is too low contrast to use as text. Nothing uses it yet.
  • Tailwind only writes out @theme variables whose names it finds in the source. Every one the old CSS needs is in the build today and I checked each one, but it is worth knowing about. @theme static would make it certain.
  • The dark: variant uses :where(), so it has no specificity of its own and depends on the order Tailwind writes the rules. That is correct in the current build.
  • useMediaQuery is left alone.

Test Plan

How to test the changes in this PR: run npm run dev and open the settings menu on any page. Try all three brightness options and check the page follows each one. Set it to system, then change your OS theme while the page is still open, and check that both the page and the navbar logo follow. Reload on a dark device and check there is no white flash. Then look at the Data page and the About page in both light and dark for anything that changed by accident.

Checklist

Summary

  • Clear description of what and why
  • Scope kept focused; note follow-ups if any

Testing

  • npm run lint
  • npm run build
  • npm run test -- 69 files, 436 tests passing
    • Tests added or updated for changed logic -- 16 new tests covering the brightness hook, the switcher, and the app wiring
  • npm run dev -- tried out the website directly, in light, dark and system, and on the Intro, Data and About pages

Changes

Visual changes

  • The page brightness control changed. No screenshots here since it is quicker to see live: open the settings menu on any page and try the three options.

Internal changes

  • Logical changes
  • Refactors, moving files around
  • Non-obvious changes are explained in code comments: why the inline script in index.html cannot be removed, why the switcher buttons set data-slot, and why colors.css is only a temporary alias layer

Docs

  • Code is self-documenting, or comments are added where needed

@hdprajwal
hdprajwal requested a review from a team as a code owner July 31, 2026 04:15
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Pages preview

@hdprajwal
hdprajwal requested a review from conradarcturus July 31, 2026 04:17
Comment on lines +50 to +52
(themeKey: Theme) => {
setTheme(themeKey);
},

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: compress lines when it makes the code cleaner and it still readable

Suggested change
(themeKey: Theme) => {
setTheme(themeKey);
},
(themeKey: Theme) => setTheme(themeKey),

@conradarcturus conradarcturus left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor comments, nonetheless thanks for keeping this going! There is a lot too this migration 0_o.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants