feat(phase-03): runtime-free theming system, prebuilt themes, breakpoint + reduced-motion tokens#3
Merged
Merged
Conversation
…int + reduced-motion tokens
- @lumen/react: ThemeProvider, DirectionProvider, getThemeInitScript and the
hooks useTheme/useColorScheme/useDirection/useReducedMotion/useBreakpoint.
The provider only writes <html data-theme> + color-scheme; CSS cascades the
rest, so theme swaps are a single repaint. localStorage / sessionStorage /
custom adapter / null storage backends, "system" via prefers-color-scheme,
optional disableTransitionOnChange, CSP nonce support, and an inline init
IIFE for FOUC prevention. DirectionProvider wraps Radix's so primitives
inherit dir.
- @lumen/themes: now CSS-only. Ships /terminal (Arshad's GitHub Dark Terminal
language: JetBrains Mono, muted blue glow, tighter radii) and /high-contrast
(WCAG AAA, light + high-contrast-dark selectors).
- @lumen/tokens: --lumen-breakpoint-{sm,md,lg,xl,2xl},
--lumen-shadow-component-focus, and a prefers-reduced-motion override that
zeroes the semantic motion durations so token-driven transitions disable
automatically.
- Playground: theme switcher (system/light/dark/terminal/high-contrast), RTL
toggle, Radix Popover demo proving direction inheritance, reduced-motion
banner, and an inline FOUC-prevention script in index.html.
- Tests: 24 Vitest cases against simulated matchMedia / storage / DOM.
- CI: publint + attw now also cover @lumen/themes; publint path corrected.
- PROGRESS.md updated; changeset queues @lumen/react, @lumen/themes, and
@lumen/tokens for a minor bump.
https://claude.ai/code/session_01Ezcxw3b6qGuKxiBuJm3iLi
Turbo runs typecheck and build in parallel within a package. The tokens build script's `pnpm clean` step deletes `src/generated/` mid-typecheck, so CI's clean run failed `tsc` with `Cannot find module './generated/base.js'`. Promote `build:tokens` (Style Dictionary) to a turbo task and make both `@lumen/tokens#typecheck` and `@lumen/tokens#build` depend on it. Verified locally with a `rm -rf packages/*/dist packages/*/.turbo packages/tokens/src/generated` sweep before each step. https://claude.ai/code/session_01Ezcxw3b6qGuKxiBuJm3iLi
There was a problem hiding this comment.
Pull request overview
Implements Phase 03 “runtime-free” theming across the monorepo: a React-side Theme/Direction API that only mutates <html> state, CSS-only theme packages, and new token capabilities (breakpoints, focus shadow, reduced-motion override).
Changes:
- Added
@lumen/reacttheming runtime (ThemeProvider/DirectionProvider, init script, and hooks) plus Vitest/jsdom coverage. - Converted
@lumen/themesto CSS-only exports and addedterminal+high-contraststylesheets. - Extended
@lumen/tokenswith breakpoints + focus shadow tokens and appended a reduced-motion override to generated base CSS; updated playground + CI accordingly.
Reviewed changes
Copilot reviewed 44 out of 46 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tsconfig.json | Drops TS project reference to themes after moving it to CSS-only. |
| pnpm-lock.yaml | Adds deps for jsdom/testing-library and Radix popover/direction. |
| packages/tokens/tokens/semantic/shadows.json | Adds semantic focus shadow token. |
| packages/tokens/tokens/semantic/breakpoints.json | Introduces breakpoint tokens (sm–2xl). |
| packages/tokens/tokens/primitives/shadows.json | Adds primitive focus-ring shadow. |
| packages/tokens/style-dictionary.config.mjs | Emits breakpoint tokens and appends reduced-motion override CSS. |
| packages/themes/package.json | Switches @lumen/themes to CSS-only exports and removes TS build. |
| packages/themes/src/terminal/index.css | Adds Terminal theme CSS variables. |
| packages/themes/src/high-contrast/index.css | Adds High Contrast (light + dark selector) theme CSS variables. |
| packages/react/vitest.config.ts | Moves tests to jsdom and adds setup file. |
| packages/react/tsup.config.ts | Adds ./theme entrypoint and externals. |
| packages/react/tsconfig.json | Sets composite: false for DTS generation compatibility. |
| packages/react/theme/package.json | Adds node10 resolution shim for @lumen/react/theme. |
| packages/react/src/theme/* | New theming/direction providers, hooks, storage adapter, and tests. |
| apps/playground/index.html | Inlines theme init IIFE for FOUC prevention demo. |
| apps/playground/src/main.tsx | Imports new prebuilt themes CSS. |
| apps/playground/src/App.tsx | Adds theme switcher, RTL toggle, reduced-motion banner, Radix popover demo. |
| apps/playground/package.json | Adds @lumen/themes and Radix popover dependency. |
| .github/workflows/ci.yml | Runs publint/attw for themes; fixes publint path for react package. |
| PROGRESS.md | Marks Phase 03 complete and logs decisions. |
| .changeset/phase-03-theming.md | Minor bumps for react/themes/tokens with release notes. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+29
to
+31
| return ( | ||
| <DirectionContext.Provider value={value}> | ||
| <RadixDirectionProvider dir={dir}>{children}</RadixDirectionProvider> |
| new Function('window', 'document', script)(sandbox.window, sandbox.document); | ||
| expect(html.attrs['data-theme']).toBe('light'); | ||
| expect(html.style.colorScheme).toBe('light'); | ||
| }); |
Comment on lines
+30
to
+35
| render( | ||
| <DirectionProvider dir="rtl" scope="subtree"> | ||
| <span>hi</span> | ||
| </DirectionProvider>, | ||
| ); | ||
| expect(document.documentElement.getAttribute('dir')).toBeNull(); |
| ? 'function(){return null}' | ||
| : `function(){try{return window.${storage}.getItem(${JSON.stringify(storageKey)})}catch(e){return null}}`; | ||
|
|
||
| return `(function(){try{var d=document.documentElement;var a=${JSON.stringify(attribute)};var def=${JSON.stringify(defaultTheme)};var allowed=${allowed};var get=${storageGetter};var stored=get();var theme=stored||def;if(allowed&&allowed.indexOf(theme)===-1&&theme!=='system'){theme=def}var resolved=theme;if(theme==='system'){if(${enableSystem ? 'true' : 'false'}&&window.matchMedia){resolved=window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}else{resolved='light'}}d.setAttribute(a,resolved);d.style.colorScheme=(resolved==='dark'?'dark':'light')}catch(e){}})();`; |
| }; | ||
|
|
||
| const isAllowed = (theme: string, themes: readonly string[], enableSystem: boolean): boolean => { | ||
| if (enableSystem && theme === SYSTEM) return true; |
Reproduced the typecheck failure under Node 20 locally: tsup transpiles tsup.config.ts to JS, but the resulting `import` of `@lumen/config/tsup.config.base` then hits Node's ESM loader, and Node 20 can't load `.ts` files natively. Node 22's default --experimental-strip-types behavior handles it. Bumping CI (and engines.node) to 22 fixes the build without forcing tsx/jiti into every config import. Updated both ci.yml and release.yml. https://claude.ai/code/session_01Ezcxw3b6qGuKxiBuJm3iLi
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
hooks useTheme/useColorScheme/useDirection/useReducedMotion/useBreakpoint.
The provider only writes + color-scheme; CSS cascades the
rest, so theme swaps are a single repaint. localStorage / sessionStorage /
custom adapter / null storage backends, "system" via prefers-color-scheme,
optional disableTransitionOnChange, CSP nonce support, and an inline init
IIFE for FOUC prevention. DirectionProvider wraps Radix's so primitives
inherit dir.
language: JetBrains Mono, muted blue glow, tighter radii) and /high-contrast
(WCAG AAA, light + high-contrast-dark selectors).
--lumen-shadow-component-focus, and a prefers-reduced-motion override that
zeroes the semantic motion durations so token-driven transitions disable
automatically.
toggle, Radix Popover demo proving direction inheritance, reduced-motion
banner, and an inline FOUC-prevention script in index.html.
@lumen/tokens for a minor bump.
https://claude.ai/code/session_01Ezcxw3b6qGuKxiBuJm3iLi