feat(themes): theme foundation with mode/palette axes and AppearanceSwitcher#306
feat(themes): theme foundation with mode/palette axes and AppearanceSwitcher#306IzumiSy wants to merge 26 commits into
Conversation
ae33b86 to
ec264a9
Compare
dd07167 to
a6b3da5
Compare
|
/review |
|
✅ API Design Review completed successfully! |
There was a problem hiding this comment.
Generated by API Design Review for issue #306 · 652.2 AIC · ⌖ 13 AIC · ⊞ 26.2K
Comment /review to run again
interacsean
left a comment
There was a problem hiding this comment.
Just some thoughts on the css files
| html[data-theme="bloom"] { | ||
| color-scheme: light; | ||
|
|
||
| /* ───── BRAND — override to re-skin ───── */ |
There was a problem hiding this comment.
Is this comment valid? Is this brand-specific overrides of default css variables?
There was a problem hiding this comment.
Yep — that's the intent. default.css is the base token set, and branded palettes override only the palette-specific brand/system/palette values. I updated the file comment to make that layering clearer in 1ec3b56.
| --shell-gradient-tint: rgb(255, 255, 255); | ||
|
|
||
| /* ───── DERIVED from brand (do not edit; re-resolves in dark) ───── */ | ||
| --accent: color-mix(in srgb, var(--primary) 15%, var(--card)); |
There was a problem hiding this comment.
Is this DERIVED section always the same? My gut tells me sometimes these formulas won't always work the same for different color tones
There was a problem hiding this comment.
Good call. I removed the generic DERIVED block in 1ec3b56. --accent stays explicit per palette now, while the shared aliases (--ring, --sidebar-*) inherit from default.css; the gradient-only helpers were inlined into the structural gradient block.
| --shell-gradient-end: var(--shell-gradient-tint); | ||
|
|
||
| /* ───── SYSTEM — neutral surfaces/text (accessibility-locked) ───── */ | ||
| --background: rgba(250, 250, 250, 1); |
There was a problem hiding this comment.
Can these not inherit somehow from a default? It looks like many of these are defined in multiple files but are mostly the same values
There was a problem hiding this comment.
Yep — pushed this direction in 1ec3b56. --ring and --sidebar-* now inherit from default.css, and the branded palette files only keep palette-specific overrides.
| --popover-foreground: rgba(250, 250, 250, 1); | ||
| --muted: rgba(38, 38, 38, 1); | ||
| --muted-foreground: rgba(163, 163, 163, 1); | ||
| --border: rgba(255, 255, 255, 0.10000000149011612); |
There was a problem hiding this comment.
That caught our eyes, but the value itself was carried over from the old ones that were in theme.css, so maybe fine?
There was a problem hiding this comment.
Rounded those export-noise values in 1ec3b56 (0.10000000149011612 -> 0.1, 0.15000000596046448 -> 0.15).
e6453fa to
495dbf7
Compare
…nt axis - Add cream/bloom color palettes with CSS custom properties (theme.css) - Shell gradient, font-axis CSS, squircle corners, autofill overrides (globals.css) - Refactor ThemeProvider: Theme/ResolvedTheme/Font types, useFont hook, legacy ID migration, memoized context - Add defaultTheme/defaultFont props to AppShell - Export new types and hooks from package index - Add ThemeProvider unit tests
…rdcode storage keys, add system reactivity
- Add ThemeSwitcher dropdown menu with 5 color themes and 2 font options - Extract useFont hook from theme-context for independent font state access - Replace sidebar-layout's simple light/dark toggle with full ThemeSwitcher - Add themeSwitcher prop to SidebarLayout for customization/hiding - Export ThemeSwitcher and useFont from package index - Add component tests for ThemeSwitcher
Extract token definitions and structural overrides into individual files under src/assets/themes/ (light, dark, cream, bloom). Each branded theme (cream/bloom) is now fully self-contained with its own gradient, squircle, and transparency rules. theme.css becomes a lightweight hub: @import directives + @theme inline bridge. globals.css retains only theme-agnostic global rules.
- Add scripts/generate-fonts.mjs to produce fonts.generated.css and copy woff2 files to src/assets/fonts/ from fontsource packages - Remove fonts.css; globals.css no longer imports fonts directly (avoids Vite woff2 resolution warnings during build) - Add appendFonts Vite plugin that appends fonts.generated.css to dist/app-shell.css in closeBundle hook (instant, no font generation) - Committed generated assets so CI doesn't need to re-run generation - Add generate:fonts script to package.json
* feat(theme): split theming into mode and palette axes Introduce two independent theming axes: mode (light/dark/system) as the end-user accessibility preference via useMode, and palette/theme (default/cream/bloom) as a developer configuration via useTheme. Mode is persisted; palette is prop-driven. The <html> element carries mode as the .light/.dark class and palette as data-theme. The font axis is untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(theme): merge light/dark CSS and add brand-token tiers (#314) Merge light.css and dark.css into a single default.css holding both the light :root block and the .dark override. Organize every theme block (default, cream, bloom) into tiers: BRAND (primary/secondary + foregrounds, shell-gradient — the only re-skin knobs), DERIVED (accent/ring/sidebar-* via color-mix on var(--primary)), SYSTEM (neutral surfaces, accessibility- locked), and STATUS (semantic, fixed). Give cream and bloom dark variants that inherit default's status colors. Decouple the shell gradient from --background via a dedicated --shell-gradient-base token. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: outline rename feedback plan Co-authored-by: IzumiSy <982850+IzumiSy@users.noreply.github.com> * refactor(theme): rename mode APIs to color mode names Co-authored-by: IzumiSy <982850+IzumiSy@users.noreply.github.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: IzumiSy <982850+IzumiSy@users.noreply.github.com>
…e static - Remove font switching (useFont, FONT_OPTIONS, data-font CSS, FontPreview) - Rename ThemeSwitcher to AppearanceSwitcher (now only handles color mode) - Make theme/palette prop-only (remove setTheme, simplify useTheme to read-only) - Inline DOM updates into handlers instead of separate useEffect - Move resolveMode/getSystemDark to module scope (lint fix)
Clarify the two independent axes of the theming system: - ThemePalette (default/cream/bloom): developer-configured brand palette - ColorTheme (light/dark/system): user-facing color scheme preference Rename internal variables, props, constants, and test IDs to align with the new terminology. Public hook API (useTheme → theme/resolvedTheme/setTheme) remains unchanged for backward compatibility.
…alette from public exports
Remove runtime palette selection via prop in favor of static CSS imports. Each theme (default, cream, bloom) is now imported directly: import "@tailor-platform/app-shell/themes/cream"; Changes: - Remove defaultThemePalette prop from AppShell and ThemeProvider - Remove ThemePalette type, THEME_PALETTE_OPTIONS, data-theme logic - Change theme CSS selectors from [data-theme=x] to :root - Wrap default theme in @layer theme.defaults so custom themes always win - Move structural overrides (gradient, transparent chrome) out of @layer to ensure they override Tailwind utilities - Add subpath pattern export: "./themes/*" -> "./dist/themes/*.css" - Update examples and e2e to use CSS imports instead of prop
495dbf7 to
67eba3b
Compare
… icons (#323) * feat(theme): explicit accent tiers, palette template, and appearance icons Organise palette CSS into numbered tiers (Brand → Shell → Derived → System → Palette → Semantic → Structural) with `_template.css` as the new-palette checklist. Set explicit `--accent` per palette and mode for reliable sidebar/menu selection. Add `--alert-*` tokens to default palette CSS. Replace AppearanceSwitcher menu swatches with Sun/Moon/Monitor icons. Add Color demo page and styling-theming docs for palette authoring. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(alert): wire variants to semantic --alert-* tokens Use CSS variable tokens for all alert variants including neutral (--alert-neutral-* aliases muted). Lighter dark-mode foregrounds from default.css apply automatically; drop hardcoded Tailwind color classes. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
Foundational layer for the Tailor brand appearance system. Introduces color theme as the user preference axis, static theme palettes selected via CSS imports, and a new
AppearanceSwitchercomponent.Font delivery is intentionally not part of this PR. Inter is already handled on
main, and this branch no longer adds generated font CSS, copied woff2 assets, or a font generation build step.Screenshots
AppearanceSwitcher
Changes
Theming Architecture
<html>aslight/dark/systemdefaultColorThemeprop + localStorageappshell-ui-theme).light/.darkclassdefault/cream/bloom:rootEvery palette ships both light and dark variants. The active variant is chosen by the color theme axis.
Theme Palette Selection
Palettes are selected by importing CSS files. The default palette is included in
@tailor-platform/app-shell/styles; branded palettes override it through the./themes/*export.The subpath pattern
"./themes/*"maps to./dist/themes/*.css, so new palettes can be added without changingpackage.json.Naming Convention
The word "theme" has historically meant the light/dark/system preference in AppShell. To preserve compatibility,
useTheme()and theappshell-ui-themelocalStorage key remain in place.ColorTheme) is the axis that was previously just called "theme".Per-Theme CSS Architecture
Theme tokens now live in one file per palette:
src/assets/ themes/ default.css cream.css bloom.css theme.cssEach theme file declares CSS custom properties in four tiers:
var()andcolor-mixCSS Cascade Strategy
default.cssis imported inside@layer theme.defaults, giving it the lowest priority.:rootvariables override layered defaults regardless of source order.AppShell Globals
globals.cssis now theme-agnostic and limited to shared app behavior: Tailwind imports, border compatibility, z-index tokens, body styling, scrollbar styling, autofill override, and sidebar active-item utility styling.ThemeProvider
ColorThemetype:"light" | "dark" | "system"useTheme()returns{ theme, resolvedTheme, setTheme }appshell-ui-themesystemmode reacts tomatchMediachanges in real timeAppearanceSwitcher
SidebarLayoutdefineI18nLabels(en/ja)