Skip to content

feat(phase-03): runtime-free theming system, prebuilt themes, breakpoint + reduced-motion tokens#3

Merged
arshad-shah merged 3 commits into
mainfrom
claude/phase-3-NqgD3
Apr 16, 2026
Merged

feat(phase-03): runtime-free theming system, prebuilt themes, breakpoint + reduced-motion tokens#3
arshad-shah merged 3 commits into
mainfrom
claude/phase-3-NqgD3

Conversation

@arshad-shah
Copy link
Copy Markdown
Owner

  • @lumen/react: ThemeProvider, DirectionProvider, getThemeInitScript and the
    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.
  • @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

…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
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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/react theming runtime (ThemeProvider/DirectionProvider, init script, and hooks) plus Vitest/jsdom coverage.
  • Converted @lumen/themes to CSS-only exports and added terminal + high-contrast stylesheets.
  • Extended @lumen/tokens with 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
@arshad-shah arshad-shah merged commit e9c34bf into main Apr 16, 2026
1 check passed
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.

3 participants