From 3fee8a95d352049fbc37dc3c87e9f46f907fbe64 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Mon, 12 Oct 2026 17:04:48 +0900 Subject: [PATCH 1/7] refactor(core): default palette surfaces read the gray, white and alpha scales --- packages/core/src/assets/themes/default.css | 94 ++++++++++++++------- 1 file changed, 62 insertions(+), 32 deletions(-) diff --git a/packages/core/src/assets/themes/default.css b/packages/core/src/assets/themes/default.css index 0a76bc497..af56a5ab7 100644 --- a/packages/core/src/assets/themes/default.css +++ b/packages/core/src/assets/themes/default.css @@ -8,12 +8,12 @@ color-scheme: light; /* ── 1. BRAND ─────────────────────────────────────────────────────────── */ - --primary: rgba(23, 23, 23, 1); - --primary-foreground: rgba(250, 250, 250, 1); - --secondary: rgba(245, 245, 245, 1); - --secondary-foreground: rgba(23, 23, 23, 1); + --primary: var(--primitive-gray-900); + --primary-foreground: var(--primitive-gray-50); + --secondary: var(--primitive-gray-100); + --secondary-foreground: var(--primitive-gray-900); --accent: rgb(237, 237, 237); - --accent-foreground: rgba(10, 10, 10, 1); + --accent-foreground: var(--primitive-gray-950); /* ── 2. SHELL GRADIENT — not used (flat palette) ──────────────────────── */ @@ -26,19 +26,19 @@ --sidebar-ring: var(--ring); /* ── 4. SYSTEM ────────────────────────────────────────────────────────── */ - --background: rgba(250, 250, 250, 1); + --background: var(--primitive-gray-50); --foreground: var(--primitive-gray-950); - --card: rgba(255, 255, 255, 1); - --card-foreground: rgba(10, 10, 10, 1); - --popover: rgba(255, 255, 255, 1); - --popover-foreground: rgba(10, 10, 10, 1); - --muted: rgba(245, 245, 245, 1); + --card: var(--primitive-white); + --card-foreground: var(--primitive-gray-950); + --popover: var(--primitive-white); + --popover-foreground: var(--primitive-gray-950); + --muted: var(--primitive-gray-100); --muted-foreground: var(--primitive-gray-500); --border: var(--primitive-gray-200); --input: var(--primitive-gray-200); - --sidebar: rgba(250, 250, 250, 1); - --sidebar-foreground: rgba(10, 10, 10, 1); - --sidebar-border: rgba(229, 229, 229, 1); + --sidebar: var(--primitive-gray-50); + --sidebar-foreground: var(--primitive-gray-950); + --sidebar-border: var(--primitive-gray-200); /* ── 5. PALETTE ───────────────────────────────────────────────────────── */ --destructive: rgba(220, 38, 38, 1); @@ -65,27 +65,27 @@ color-scheme: dark; /* ── 1. BRAND (dark) ──────────────────────────────────────────────────── */ - --primary: rgba(229, 229, 229, 1); - --primary-foreground: rgba(23, 23, 23, 1); - --secondary: rgba(38, 38, 38, 1); - --secondary-foreground: rgba(250, 250, 250, 1); + --primary: var(--primitive-gray-200); + --primary-foreground: var(--primitive-gray-900); + --secondary: var(--primitive-gray-800); + --secondary-foreground: var(--primitive-gray-50); --accent: rgb(48, 48, 48); - --accent-foreground: rgba(250, 250, 250, 1); + --accent-foreground: var(--primitive-gray-50); /* ── 4. SYSTEM (dark) ─────────────────────────────────────────────────── */ - --background: rgba(10, 10, 10, 1); + --background: var(--primitive-gray-950); --foreground: var(--primitive-gray-50); - --card: rgba(23, 23, 23, 1); - --card-foreground: rgba(250, 250, 250, 1); - --popover: rgba(38, 38, 38, 1); - --popover-foreground: rgba(250, 250, 250, 1); - --muted: rgba(38, 38, 38, 1); + --card: var(--primitive-gray-900); + --card-foreground: var(--primitive-gray-50); + --popover: var(--primitive-gray-800); + --popover-foreground: var(--primitive-gray-50); + --muted: var(--primitive-gray-800); --muted-foreground: var(--primitive-gray-400); - --border: rgba(255, 255, 255, 0.1); - --input: rgba(255, 255, 255, 0.15); - --sidebar: rgba(10, 10, 10, 1); - --sidebar-foreground: rgba(250, 250, 250, 1); - --sidebar-border: rgba(255, 255, 255, 0.1); + --border: var(--primitive-white-a2); + --input: var(--primitive-white-a3); + --sidebar: var(--primitive-gray-950); + --sidebar-foreground: var(--primitive-gray-50); + --sidebar-border: var(--primitive-white-a2); /* ── 5. PALETTE (dark) ────────────────────────────────────────────────── */ --destructive: rgba(248, 113, 113, 1); @@ -112,8 +112,11 @@ * resolve to opaque hex. * * Gray is the Tailwind `neutral` family (the values this palette already used) - * and is the same in both modes, so it is declared once; the SYSTEM text and - * line tokens (4) read it. Surfaces stay literal: they are theme inputs. + * and is the same in both modes, so it is declared once. White and the white / + * black alpha scales are declared with it. The BRAND (1) and SYSTEM (4) tokens + * of this palette read these scales. Other palettes still set their own + * literal values; the scales are not a theme input yet. `--accent` and the + * shadows have no step and stay literal. */ :root { /* gray — both modes */ @@ -129,6 +132,33 @@ --primitive-gray-900: #171717; --primitive-gray-950: #0a0a0a; + /* white, and white / black alpha — both modes (Radix whiteA / blackA) */ + --primitive-white: #ffffff; + --primitive-white-a1: rgba(255, 255, 255, 0.05); + --primitive-white-a2: rgba(255, 255, 255, 0.1); + --primitive-white-a3: rgba(255, 255, 255, 0.15); + --primitive-white-a4: rgba(255, 255, 255, 0.2); + --primitive-white-a5: rgba(255, 255, 255, 0.3); + --primitive-white-a6: rgba(255, 255, 255, 0.4); + --primitive-white-a7: rgba(255, 255, 255, 0.5); + --primitive-white-a8: rgba(255, 255, 255, 0.6); + --primitive-white-a9: rgba(255, 255, 255, 0.7); + --primitive-white-a10: rgba(255, 255, 255, 0.8); + --primitive-white-a11: rgba(255, 255, 255, 0.9); + --primitive-white-a12: rgba(255, 255, 255, 0.95); + --primitive-black-a1: rgba(0, 0, 0, 0.05); + --primitive-black-a2: rgba(0, 0, 0, 0.1); + --primitive-black-a3: rgba(0, 0, 0, 0.15); + --primitive-black-a4: rgba(0, 0, 0, 0.2); + --primitive-black-a5: rgba(0, 0, 0, 0.3); + --primitive-black-a6: rgba(0, 0, 0, 0.4); + --primitive-black-a7: rgba(0, 0, 0, 0.5); + --primitive-black-a8: rgba(0, 0, 0, 0.6); + --primitive-black-a9: rgba(0, 0, 0, 0.7); + --primitive-black-a10: rgba(0, 0, 0, 0.8); + --primitive-black-a11: rgba(0, 0, 0, 0.9); + --primitive-black-a12: rgba(0, 0, 0, 0.95); + /* hues — light */ --primitive-blue-1: #f7f8f9; --primitive-blue-2: #f0f4fa; From 668e1bcf0caefd8cd70134a56efca0d6850fb4a6 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Mon, 12 Oct 2026 17:19:38 +0900 Subject: [PATCH 2/7] feat(core): add a --backdrop role for modal backdrops --- .../src__components__dialog__dialog.test.tsx.snap | 4 ++-- .../src__components__sheet__sheet.test.tsx.snap | 6 +++--- packages/core/src/assets/theme.bridge.css | 1 + packages/core/src/assets/theme.css | 2 +- packages/core/src/assets/themes/_template.css | 2 +- packages/core/src/assets/themes/default.css | 1 + packages/core/src/components/csv-importer/CsvImporter.tsx | 2 +- packages/core/src/components/dialog/dialog.tsx | 2 +- packages/core/src/components/sheet/sheet.tsx | 2 +- 9 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/core/__snapshots__/src__components__dialog__dialog.test.tsx.snap b/packages/core/__snapshots__/src__components__dialog__dialog.test.tsx.snap index 8a18066e3..2023c299b 100644 --- a/packages/core/__snapshots__/src__components__dialog__dialog.test.tsx.snap +++ b/packages/core/__snapshots__/src__components__dialog__dialog.test.tsx.snap @@ -2,6 +2,6 @@ exports[`Dialog > snapshots > closed dialog (trigger only) 1`] = `""`; -exports[`Dialog > snapshots > open dialog with header 1`] = `"
"`; +exports[`Dialog > snapshots > open dialog with header 1`] = `"
"`; -exports[`Dialog > snapshots > open dialog with header and footer 1`] = `"
"`; +exports[`Dialog > snapshots > open dialog with header and footer 1`] = `"
"`; diff --git a/packages/core/__snapshots__/src__components__sheet__sheet.test.tsx.snap b/packages/core/__snapshots__/src__components__sheet__sheet.test.tsx.snap index c7e5b7147..10e75cc78 100644 --- a/packages/core/__snapshots__/src__components__sheet__sheet.test.tsx.snap +++ b/packages/core/__snapshots__/src__components__sheet__sheet.test.tsx.snap @@ -4,8 +4,8 @@ exports[`Sheet > snapshots > closed sheet (trigger only) 1`] = `"
"`; -exports[`Sheet > snapshots > open sheet (left side) 1`] = `"
"`; +exports[`Sheet > snapshots > open sheet (left side) 1`] = `"
"`; -exports[`Sheet > snapshots > open sheet (right side, default) 1`] = `"
"`; +exports[`Sheet > snapshots > open sheet (right side, default) 1`] = `"
"`; -exports[`Sheet > snapshots > open sheet with header and footer 1`] = `"
"`; +exports[`Sheet > snapshots > open sheet with header and footer 1`] = `"
"`; diff --git a/packages/core/src/assets/theme.bridge.css b/packages/core/src/assets/theme.bridge.css index 31d9cc0b5..ba691f90c 100644 --- a/packages/core/src/assets/theme.bridge.css +++ b/packages/core/src/assets/theme.bridge.css @@ -105,6 +105,7 @@ --color-border: var(--border); --color-input: var(--input); --color-ring: var(--ring); + --color-backdrop: var(--backdrop); --color-chart-1: var(--chart-1); --color-chart-2: var(--chart-2); --color-chart-3: var(--chart-3); diff --git a/packages/core/src/assets/theme.css b/packages/core/src/assets/theme.css index fac3798e7..7ab38be85 100644 --- a/packages/core/src/assets/theme.css +++ b/packages/core/src/assets/theme.css @@ -16,7 +16,7 @@ * 4. PALETTE — optional: radius, charts, shadows, destructive. * * Inherited from default.css — do not duplicate in new palettes: - * --ring, --sidebar-*, --primitive-* (colour scales), --status-*, --alert-*, + * --ring, --sidebar-*, --backdrop, --primitive-* (colour scales), --status-*, --alert-*, * --app-shell-type-* (typography roles). * The colour roles --{intent}-{role} inherit too; a palette overrides them * only to recolour the status components. Typography roles are inherited diff --git a/packages/core/src/assets/themes/_template.css b/packages/core/src/assets/themes/_template.css index a4dd7e390..6b007ff25 100644 --- a/packages/core/src/assets/themes/_template.css +++ b/packages/core/src/assets/themes/_template.css @@ -17,7 +17,7 @@ * `@tailor-platform/app-shell/styles` in the consuming app. * * DO NOT SET (inherited from `:root` in default.css): - * --ring, --sidebar-*, --primitive-* (colour scales), --status-*, --alert-*, + * --ring, --sidebar-*, --backdrop, --primitive-* (colour scales), --status-*, --alert-*, * --app-shell-type-* * The colour roles --{info|success|warning|danger|neutral}-{role} also inherit. * Override a role only to recolour the status components (Badge, Alert, ...) diff --git a/packages/core/src/assets/themes/default.css b/packages/core/src/assets/themes/default.css index af56a5ab7..9f5c6b17f 100644 --- a/packages/core/src/assets/themes/default.css +++ b/packages/core/src/assets/themes/default.css @@ -24,6 +24,7 @@ --sidebar-accent: var(--accent); --sidebar-accent-foreground: var(--accent-foreground); --sidebar-ring: var(--ring); + --backdrop: var(--primitive-black-a7); /* ── 4. SYSTEM ────────────────────────────────────────────────────────── */ --background: var(--primitive-gray-50); diff --git a/packages/core/src/components/csv-importer/CsvImporter.tsx b/packages/core/src/components/csv-importer/CsvImporter.tsx index 3cc9b9253..9e1d4c509 100644 --- a/packages/core/src/components/csv-importer/CsvImporter.tsx +++ b/packages/core/src/components/csv-importer/CsvImporter.tsx @@ -711,7 +711,7 @@ export function CsvImporter({ return ( - + Date: Mon, 12 Oct 2026 17:19:44 +0900 Subject: [PATCH 3/7] docs: document --backdrop and the default palette reading the neutral scales --- .changeset/neutral-surfaces-scale.md | 7 +++++++ decisions/semantic-color-roles.md | 11 +++++++++++ docs-manifest.json | 10 +++++----- docs-src/concepts/styling-theming.docs.outline.md | 5 +++++ docs/concepts/styling-theming.md | 5 +++++ 5 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 .changeset/neutral-surfaces-scale.md diff --git a/.changeset/neutral-surfaces-scale.md b/.changeset/neutral-surfaces-scale.md new file mode 100644 index 000000000..5c75d8a71 --- /dev/null +++ b/.changeset/neutral-surfaces-scale.md @@ -0,0 +1,7 @@ +--- +"@tailor-platform/app-shell": minor +--- + +Add a `--backdrop` colour token and the `bg-backdrop` utility for the dim layer behind modals. `Dialog`, `Sheet` and the CsvImporter drawer use it instead of `bg-black/50`. The value is the same (black at 50%), so nothing changes on screen. Override `--backdrop` on `:root` to change every backdrop at once. + +In the default palette, the surface, border and brand colours now read the internal gray scale, a white step, and white / black alpha steps. All values are unchanged. The `cream` and `bloom` palettes are not changed. diff --git a/decisions/semantic-color-roles.md b/decisions/semantic-color-roles.md index 23927ead4..1c48ad4ea 100644 --- a/decisions/semantic-color-roles.md +++ b/decisions/semantic-color-roles.md @@ -108,3 +108,14 @@ The component variant names (`Badge` and `Alert` `error`, Badge `neutral`) are p - Alert description text is the same colour as the Alert title in the default palette. - Consumers who override `--status-*` or `--alert-*` keep working for their own `bg-status-*` and `bg-alert-*` utilities, which read those tokens. The shipped components (Badge, Alert, CsvImporter, MetricCard — its trend colours were literal `green-600` / `red-600` before) read the roles, not `--status-*` or `--alert-*`. To recolour them, override the roles (`--{intent}-*`); overriding `--status-*` or `--alert-*` no longer changes the components. `--status-*` and `--alert-*` remain for existing consumer code and are not used by components any more. - cream, bloom and `_template.css` values are not edited. The header comments now list `--{intent}-{role}` among the inherited tokens. + +## Update 2026-10-12: default surfaces, white and alpha scales + +The default palette's surfaces and brand tokens read scale steps. For the default palette, this supersedes "all surfaces stay literal" and "Gray has no alpha steps" under Hue scales, and the second item under "Out of scope". + +- New primitives, declared once with gray because they are the same in both modes: `--primitive-white` and `--primitive-{white,black}-a1` to `-a12`. The alpha values are Radix `whiteA` / `blackA` (0.05, 0.1, 0.15, 0.2, 0.3, then 0.1 steps to 0.9, and 0.95). They are not bridged, like the other primitives. +- In `default.css`, every BRAND and SYSTEM colour with an exact step now reads it. Light: background and sidebar 50, secondary and muted 100, sidebar border 200, primary 900, text 950, card and popover white. Dark: primary 200, secondary, popover and muted 800, card 900, background and sidebar 950, foregrounds 50, border and sidebar border white a2, input white a3. No values change: every token resolves to the same colour as before in light and dark mode. +- White is its own step. The light page is gray 50 and cards are white; putting cards on gray 50 would remove the difference. +- New role `--backdrop`, `var(--primitive-black-a7)` (50%) in both modes, bridged as `bg-backdrop`. `Dialog`, `Sheet` and the CsvImporter drawer use it instead of `bg-black/50`; the value is the same. The name follows the CSS `::backdrop` pseudo-element and the Base UI `Backdrop` part. It is not `overlay`, because `--z-overlay` already names the modal layer, which holds both the backdrop and the popup. +- Still literal: `--accent` (light 237, dark 48) has no step; moving it to gray 200 / 700 changes the hover surface, so it belongs to the theme step. The shadows (alpha varies by size, and the light tint is slate), the chart colours and `--destructive` also stay literal. +- `cream`, `bloom` and `_template.css` keep their own literal values. Their lines (black 8% in light, white 8% and 12% in dark) do not match an alpha step. Moving the themes onto scales is the theme step, together with the Theme Generator. The header comments now list `--backdrop` among the inherited tokens. diff --git a/docs-manifest.json b/docs-manifest.json index 163a15912..7911ef6dc 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -608,7 +608,7 @@ "hashes": { "typeSurface": "b1614970f950ceb6", "outline": "bda40376d6e3e008", - "snapshot": "b845d4c54453b0f0", + "snapshot": "ac70f5a7004d3bab", "outputMd": "ec6a0ac098d1d87a", "examples": "934c52d4e8761404" } @@ -1186,7 +1186,7 @@ "hashes": { "typeSurface": "e760949efcbe4c38", "outline": "464d91acc48f7c9d", - "snapshot": "40d15c3f1003fdb0", + "snapshot": "2c9114c5a81198be", "outputMd": "1f81ebd562c50371", "examples": "b0eebb8c5e2219dc" } @@ -1287,9 +1287,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "e63d837190e71ff1", + "outline": "7369a064bd4d422b", "snapshot": null, - "outputMd": "112b43c697d30369", + "outputMd": "fcfff0b2584c2e74", "examples": "4d19f4f688e0f0ac" } }, @@ -1659,7 +1659,7 @@ "packages/core/skills/app-shell-patterns/references/concepts/modules-and-resources.md": "bf15b5460f22d165", "packages/core/skills/app-shell-patterns/references/concepts/routing-navigation.md": "8034c7f885994b29", "packages/core/skills/app-shell-patterns/references/concepts/sidebar-navigation.md": "dc6b3afc049b4148", - "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "112b43c697d30369", + "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "fcfff0b2584c2e74", "packages/core/skills/app-shell-patterns/references/components/action-panel.md": "71794d4b561e4d4b", "packages/core/skills/app-shell-patterns/references/components/activity-card.md": "4f386e49380fb0b0", "packages/core/skills/app-shell-patterns/references/components/ai-chat.md": "824d1aa36bb7f194", diff --git a/docs-src/concepts/styling-theming.docs.outline.md b/docs-src/concepts/styling-theming.docs.outline.md index c2d5d721e..ed2312cea 100644 --- a/docs-src/concepts/styling-theming.docs.outline.md +++ b/docs-src/concepts/styling-theming.docs.outline.md @@ -142,9 +142,14 @@ Colors follow shadcn-style semantic naming: a surface token and its matching `-f | `--border` | hairlines, dividers | `border-border` | | `--input` | form control borders | `border-input` | | `--ring` | focus rings | `ring-ring` / `outline-ring` | +| `--backdrop` | dim layer behind a modal | `bg-backdrop` | Surfaces are named by **role**, not by depth — there is no numbered `surface-1/2/3` ladder, and no third tier of body text below `muted-foreground`. Stack depth with `background` → `card` → `muted` plus a border or shadow, and reach for `popover` when the surface actually floats. +`--backdrop` is the dim layer behind `Dialog` and `Sheet` (black at 50% in both modes). Use `bg-backdrop` for your own modal layers instead of `bg-black/50`. Palettes inherit it; override it on `:root` to change every backdrop at once. + +In the default palette these tokens read the internal gray scale, white, and white alpha steps, so their values match the gray used by text and lines. The other palettes set their own values. + #### Brand & action | Token | Use | Tailwind | diff --git a/docs/concepts/styling-theming.md b/docs/concepts/styling-theming.md index 61a21f8dc..1ad9a592a 100644 --- a/docs/concepts/styling-theming.md +++ b/docs/concepts/styling-theming.md @@ -159,9 +159,14 @@ Colors follow shadcn-style semantic naming: a surface token and its matching `-f | `--border` | hairlines, dividers | `border-border` | | `--input` | form control borders | `border-input` | | `--ring` | focus rings | `ring-ring` / `outline-ring` | +| `--backdrop` | dim layer behind a modal | `bg-backdrop` | Surfaces are named by **role**, not by depth — there is no numbered `surface-1/2/3` ladder, and no third tier of body text below `muted-foreground`. Stack depth with `background` → `card` → `muted` plus a border or shadow, and reach for `popover` when the surface actually floats. +`--backdrop` is the dim layer behind `Dialog` and `Sheet` (black at 50% in both modes). Use `bg-backdrop` for your own modal layers instead of `bg-black/50`. Palettes inherit it; override it on `:root` to change every backdrop at once. + +In the default palette these tokens read the internal gray scale, white, and white alpha steps, so their values match the gray used by text and lines. The other palettes set their own values. + #### Brand & action | Token | Use | Tailwind | From 37599915af55eb8d9b08215ac74b23592609ebc3 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Mon, 12 Oct 2026 17:54:56 +0900 Subject: [PATCH 4/7] test(core): pin the default neutral token values and the --backdrop bridge --- .../core/src/assets/semantic-contrast.test.ts | 53 +++++++++++++++++++ packages/core/src/assets/theme-bridge.test.ts | 7 +++ 2 files changed, 60 insertions(+) diff --git a/packages/core/src/assets/semantic-contrast.test.ts b/packages/core/src/assets/semantic-contrast.test.ts index 61974dd16..f6e4be462 100644 --- a/packages/core/src/assets/semantic-contrast.test.ts +++ b/packages/core/src/assets/semantic-contrast.test.ts @@ -152,3 +152,56 @@ describe.each(modes)("semantic colour contrast (%s)", (mode) => { }); }); }); + +describe("default palette neutral tokens", () => { + // Pinned when the BRAND and SYSTEM tokens moved from literals onto the gray, + // white and alpha scales. That move was meant to keep every value, so a step + // change that alters one of these colours fails here. + const expected: Record> = { + light: { + "--primary": "#171717", + "--primary-foreground": "#fafafa", + "--secondary": "#f5f5f5", + "--secondary-foreground": "#171717", + "--accent-foreground": "#0a0a0a", + "--background": "#fafafa", + "--card": "#ffffff", + "--card-foreground": "#0a0a0a", + "--popover": "#ffffff", + "--popover-foreground": "#0a0a0a", + "--muted": "#f5f5f5", + "--border": "#e5e5e5", + "--input": "#e5e5e5", + "--sidebar": "#fafafa", + "--sidebar-foreground": "#0a0a0a", + "--sidebar-border": "#e5e5e5", + "--backdrop": "rgba(0, 0, 0, 0.5)", + }, + dark: { + "--primary": "#e5e5e5", + "--primary-foreground": "#171717", + "--secondary": "#262626", + "--secondary-foreground": "#fafafa", + "--accent-foreground": "#fafafa", + "--background": "#0a0a0a", + "--card": "#171717", + "--card-foreground": "#fafafa", + "--popover": "#262626", + "--popover-foreground": "#fafafa", + "--muted": "#262626", + "--border": "rgba(255, 255, 255, 0.1)", + "--input": "rgba(255, 255, 255, 0.15)", + "--sidebar": "#0a0a0a", + "--sidebar-foreground": "#fafafa", + "--sidebar-border": "rgba(255, 255, 255, 0.1)", + "--backdrop": "rgba(0, 0, 0, 0.5)", + }, + }; + + describe.each(["light", "dark"] as const)("%s mode", (mode) => { + const vars = variablesFor(mode); + it.each(Object.entries(expected[mode]))("%s resolves to %s", (token, value) => { + expect(parseColor(`var(${token})`, vars)).toEqual(parseColor(value, vars)); + }); + }); +}); diff --git a/packages/core/src/assets/theme-bridge.test.ts b/packages/core/src/assets/theme-bridge.test.ts index 86fb5ef0c..f8036da02 100644 --- a/packages/core/src/assets/theme-bridge.test.ts +++ b/packages/core/src/assets/theme-bridge.test.ts @@ -156,4 +156,11 @@ describe("theme bridge", () => { } }); }); + + it("defines --backdrop in the default palette and bridges it as bg-backdrop", () => { + // Dialog, Sheet and the CsvImporter drawer use `bg-backdrop`; without both + // lines the backdrop compiles to an undefined variable and turns transparent. + expect(defaultTheme).toMatch(/^\s*--backdrop:\s*var\(--primitive-black-a7\);/m); + expect(bridge).toMatch(/^\s*--color-backdrop:\s*var\(--backdrop\);/m); + }); }); From f599e2c0547977fde1aaa9b4d390addbf3c33539 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Mon, 12 Oct 2026 17:54:59 +0900 Subject: [PATCH 5/7] docs: correct the token step list and computed-value notes for the neutral scales --- .changeset/neutral-surfaces-scale.md | 2 +- decisions/semantic-color-roles.md | 2 +- docs-manifest.json | 12 ++++++------ docs-src/concepts/styling-theming.docs.outline.md | 2 +- docs-src/guides/migrations.docs.outline.md | 2 +- docs/concepts/styling-theming.md | 2 +- docs/migrations.md | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.changeset/neutral-surfaces-scale.md b/.changeset/neutral-surfaces-scale.md index 5c75d8a71..d6b8db91a 100644 --- a/.changeset/neutral-surfaces-scale.md +++ b/.changeset/neutral-surfaces-scale.md @@ -4,4 +4,4 @@ Add a `--backdrop` colour token and the `bg-backdrop` utility for the dim layer behind modals. `Dialog`, `Sheet` and the CsvImporter drawer use it instead of `bg-black/50`. The value is the same (black at 50%), so nothing changes on screen. Override `--backdrop` on `:root` to change every backdrop at once. -In the default palette, the surface, border and brand colours now read the internal gray scale, a white step, and white / black alpha steps. All values are unchanged. The `cream` and `bloom` palettes are not changed. +In the default palette, the surface, border and brand colours now read the internal gray scale, a white step, and white alpha steps. All colours are unchanged. If your code reads these tokens with `getComputedStyle(...).getPropertyValue()`, the string is now the step's notation, for example `#171717` instead of `rgba(23, 23, 23, 1)`. The `cream` and `bloom` palettes are not changed. diff --git a/decisions/semantic-color-roles.md b/decisions/semantic-color-roles.md index 1c48ad4ea..66a413434 100644 --- a/decisions/semantic-color-roles.md +++ b/decisions/semantic-color-roles.md @@ -114,7 +114,7 @@ The component variant names (`Badge` and `Alert` `error`, Badge `neutral`) are p The default palette's surfaces and brand tokens read scale steps. For the default palette, this supersedes "all surfaces stay literal" and "Gray has no alpha steps" under Hue scales, and the second item under "Out of scope". - New primitives, declared once with gray because they are the same in both modes: `--primitive-white` and `--primitive-{white,black}-a1` to `-a12`. The alpha values are Radix `whiteA` / `blackA` (0.05, 0.1, 0.15, 0.2, 0.3, then 0.1 steps to 0.9, and 0.95). They are not bridged, like the other primitives. -- In `default.css`, every BRAND and SYSTEM colour with an exact step now reads it. Light: background and sidebar 50, secondary and muted 100, sidebar border 200, primary 900, text 950, card and popover white. Dark: primary 200, secondary, popover and muted 800, card 900, background and sidebar 950, foregrounds 50, border and sidebar border white a2, input white a3. No values change: every token resolves to the same colour as before in light and dark mode. +- In `default.css`, every BRAND and SYSTEM colour with an exact step now reads it. Light: background, sidebar and primary foreground 50, secondary and muted 100, sidebar border 200, primary and secondary foreground 900, the other foregrounds 950, card and popover white. Dark: primary 200, secondary, popover and muted 800, card and primary foreground 900, background and sidebar 950, the other foregrounds 50, border and sidebar border white a2, input white a3. No values change: every token resolves to the same colour as before in light and dark mode. - White is its own step. The light page is gray 50 and cards are white; putting cards on gray 50 would remove the difference. - New role `--backdrop`, `var(--primitive-black-a7)` (50%) in both modes, bridged as `bg-backdrop`. `Dialog`, `Sheet` and the CsvImporter drawer use it instead of `bg-black/50`; the value is the same. The name follows the CSS `::backdrop` pseudo-element and the Base UI `Backdrop` part. It is not `overlay`, because `--z-overlay` already names the modal layer, which holds both the backdrop and the popup. - Still literal: `--accent` (light 237, dark 48) has no step; moving it to gray 200 / 700 changes the hover surface, so it belongs to the theme step. The shadows (alpha varies by size, and the light tint is slate), the chart colours and `--destructive` also stay literal. diff --git a/docs-manifest.json b/docs-manifest.json index 7911ef6dc..024b4e8b4 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -1049,9 +1049,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "45dcfe4383a87ea9", + "outline": "36cdcfab060a0d1c", "snapshot": null, - "outputMd": "2fa0b6a249d8bd4f", + "outputMd": "86fe04e370ecdff5", "examples": null } }, @@ -1287,9 +1287,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "7369a064bd4d422b", + "outline": "18061fd0dd60d63d", "snapshot": null, - "outputMd": "fcfff0b2584c2e74", + "outputMd": "bce2991c993061b6", "examples": "4d19f4f688e0f0ac" } }, @@ -1659,7 +1659,7 @@ "packages/core/skills/app-shell-patterns/references/concepts/modules-and-resources.md": "bf15b5460f22d165", "packages/core/skills/app-shell-patterns/references/concepts/routing-navigation.md": "8034c7f885994b29", "packages/core/skills/app-shell-patterns/references/concepts/sidebar-navigation.md": "dc6b3afc049b4148", - "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "fcfff0b2584c2e74", + "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "bce2991c993061b6", "packages/core/skills/app-shell-patterns/references/components/action-panel.md": "71794d4b561e4d4b", "packages/core/skills/app-shell-patterns/references/components/activity-card.md": "4f386e49380fb0b0", "packages/core/skills/app-shell-patterns/references/components/ai-chat.md": "824d1aa36bb7f194", @@ -1738,7 +1738,7 @@ "packages/core/skills/app-shell-patterns/references/patterns/interaction-toast.md": "f68bdab855730f48", "packages/core/skills/app-shell-patterns/references/patterns/list-dense-scan.md": "916baa3cd2b9cbf1", "packages/core/skills/app-shell-patterns/references/pages/document-detail.md": "717ef4e7ae835aff", - "packages/core/skills/app-shell-patterns/references/migrations.md": "013c3fef2303b096", + "packages/core/skills/app-shell-patterns/references/migrations.md": "1918f794d1bd4e99", "packages/core/skills/app-shell-patterns/SKILL.md": "12527965a47cf42f" } } diff --git a/docs-src/concepts/styling-theming.docs.outline.md b/docs-src/concepts/styling-theming.docs.outline.md index ed2312cea..1d5cd1ee5 100644 --- a/docs-src/concepts/styling-theming.docs.outline.md +++ b/docs-src/concepts/styling-theming.docs.outline.md @@ -148,7 +148,7 @@ Surfaces are named by **role**, not by depth — there is no numbered `surface-1 `--backdrop` is the dim layer behind `Dialog` and `Sheet` (black at 50% in both modes). Use `bg-backdrop` for your own modal layers instead of `bg-black/50`. Palettes inherit it; override it on `:root` to change every backdrop at once. -In the default palette these tokens read the internal gray scale, white, and white alpha steps, so their values match the gray used by text and lines. The other palettes set their own values. +In the default palette, the surface, text and line tokens in this table read the internal gray scale, a white step, and white alpha steps (dark-mode lines), so their values match the gray used across the palette. `--backdrop` reads a black alpha step, and `--ring` is mixed from `--primary`. The other palettes set their own values. #### Brand & action diff --git a/docs-src/guides/migrations.docs.outline.md b/docs-src/guides/migrations.docs.outline.md index cca666b24..12ed007ca 100644 --- a/docs-src/guides/migrations.docs.outline.md +++ b/docs-src/guides/migrations.docs.outline.md @@ -184,7 +184,7 @@ body { Toggle dark mode and confirm a real surface changes: inspect a `Card` and watch its computed `background-color` go from `rgb(255, 255, 255)` to `rgb(23, 23, 23)` on the default palette. -Reading the token directly also works — `getComputedStyle(document.documentElement).getPropertyValue("--card")` returns the winning declaration, so a stale copy shows up as its own value. Just compare against the authored notation: AppShell writes `rgba(23, 23, 23, 1)`, not `#171717`, and the computed value preserves that form. +Reading the token directly also works — `getComputedStyle(document.documentElement).getPropertyValue("--card")` returns the winning declaration, so a stale copy shows up as its own value. Compare against the value the default palette resolves to: in dark mode `--card` returns `#171717`, the gray step it reads. (Releases before the default palette moved onto its colour scales returned `rgba(23, 23, 23, 1)`.) A copy in your own CSS shows up in the notation you wrote. ## 1.5.0: `loader` removed from file-based page definitions diff --git a/docs/concepts/styling-theming.md b/docs/concepts/styling-theming.md index 1ad9a592a..122488282 100644 --- a/docs/concepts/styling-theming.md +++ b/docs/concepts/styling-theming.md @@ -165,7 +165,7 @@ Surfaces are named by **role**, not by depth — there is no numbered `surface-1 `--backdrop` is the dim layer behind `Dialog` and `Sheet` (black at 50% in both modes). Use `bg-backdrop` for your own modal layers instead of `bg-black/50`. Palettes inherit it; override it on `:root` to change every backdrop at once. -In the default palette these tokens read the internal gray scale, white, and white alpha steps, so their values match the gray used by text and lines. The other palettes set their own values. +In the default palette, the surface, text and line tokens in this table read the internal gray scale, a white step, and white alpha steps (dark-mode lines), so their values match the gray used across the palette. `--backdrop` reads a black alpha step, and `--ring` is mixed from `--primary`. The other palettes set their own values. #### Brand & action diff --git a/docs/migrations.md b/docs/migrations.md index 4ffc91542..df276264a 100644 --- a/docs/migrations.md +++ b/docs/migrations.md @@ -184,7 +184,7 @@ body { Toggle dark mode and confirm a real surface changes: inspect a `Card` and watch its computed `background-color` go from `rgb(255, 255, 255)` to `rgb(23, 23, 23)` on the default palette. -Reading the token directly also works — `getComputedStyle(document.documentElement).getPropertyValue("--card")` returns the winning declaration, so a stale copy shows up as its own value. Just compare against the authored notation: AppShell writes `rgba(23, 23, 23, 1)`, not `#171717`, and the computed value preserves that form. +Reading the token directly also works — `getComputedStyle(document.documentElement).getPropertyValue("--card")` returns the winning declaration, so a stale copy shows up as its own value. Compare against the value the default palette resolves to: in dark mode `--card` returns `#171717`, the gray step it reads. (Releases before the default palette moved onto its colour scales returned `rgba(23, 23, 23, 1)`.) A copy in your own CSS shows up in the notation you wrote. ## 1.5.0: `loader` removed from file-based page definitions From 2b4765affcf55dedc316c652ca2c38867e49dcb8 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Mon, 12 Oct 2026 18:00:53 +0900 Subject: [PATCH 6/7] test(core): pin --foreground and --muted-foreground with the other neutral tokens --- packages/core/src/assets/semantic-contrast.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core/src/assets/semantic-contrast.test.ts b/packages/core/src/assets/semantic-contrast.test.ts index f6e4be462..c9e431aba 100644 --- a/packages/core/src/assets/semantic-contrast.test.ts +++ b/packages/core/src/assets/semantic-contrast.test.ts @@ -159,6 +159,8 @@ describe("default palette neutral tokens", () => { // change that alters one of these colours fails here. const expected: Record> = { light: { + "--foreground": "#0a0a0a", + "--muted-foreground": "#737373", "--primary": "#171717", "--primary-foreground": "#fafafa", "--secondary": "#f5f5f5", @@ -178,6 +180,8 @@ describe("default palette neutral tokens", () => { "--backdrop": "rgba(0, 0, 0, 0.5)", }, dark: { + "--foreground": "#fafafa", + "--muted-foreground": "#a3a3a3", "--primary": "#e5e5e5", "--primary-foreground": "#171717", "--secondary": "#262626", From 60965fed3b467ce0efbb034003b7dedac245718e Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Mon, 12 Oct 2026 18:17:59 +0900 Subject: [PATCH 7/7] docs(decisions): keep the update to what this change does --- decisions/semantic-color-roles.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/decisions/semantic-color-roles.md b/decisions/semantic-color-roles.md index 66a413434..20a488f88 100644 --- a/decisions/semantic-color-roles.md +++ b/decisions/semantic-color-roles.md @@ -117,5 +117,5 @@ The default palette's surfaces and brand tokens read scale steps. For the defaul - In `default.css`, every BRAND and SYSTEM colour with an exact step now reads it. Light: background, sidebar and primary foreground 50, secondary and muted 100, sidebar border 200, primary and secondary foreground 900, the other foregrounds 950, card and popover white. Dark: primary 200, secondary, popover and muted 800, card and primary foreground 900, background and sidebar 950, the other foregrounds 50, border and sidebar border white a2, input white a3. No values change: every token resolves to the same colour as before in light and dark mode. - White is its own step. The light page is gray 50 and cards are white; putting cards on gray 50 would remove the difference. - New role `--backdrop`, `var(--primitive-black-a7)` (50%) in both modes, bridged as `bg-backdrop`. `Dialog`, `Sheet` and the CsvImporter drawer use it instead of `bg-black/50`; the value is the same. The name follows the CSS `::backdrop` pseudo-element and the Base UI `Backdrop` part. It is not `overlay`, because `--z-overlay` already names the modal layer, which holds both the backdrop and the popup. -- Still literal: `--accent` (light 237, dark 48) has no step; moving it to gray 200 / 700 changes the hover surface, so it belongs to the theme step. The shadows (alpha varies by size, and the light tint is slate), the chart colours and `--destructive` also stay literal. -- `cream`, `bloom` and `_template.css` keep their own literal values. Their lines (black 8% in light, white 8% and 12% in dark) do not match an alpha step. Moving the themes onto scales is the theme step, together with the Theme Generator. The header comments now list `--backdrop` among the inherited tokens. +- `--accent`, the shadows, the chart colours and `--destructive` stay literal. They have no matching step. +- `cream`, `bloom` and `_template.css` keep their own literal values. The header comments now list `--backdrop` among the inherited tokens.