From 1a49d9ed697d9534a3e1fcee2500d5f62145d674 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Wed, 30 Sep 2026 09:15:56 +0900 Subject: [PATCH 01/29] feat(core): add hue scales and semantic colour roles Add four hue scales (blue, green, amber, red: 12 steps, 12 alpha steps and a contrast colour) copied from the primitive lab output, and 40 semantic roles (5 intents x 8 roles) on top of them. The roles and the existing --status-* and --alert-* tokens are declared for `:root` and `.dark`, so a nested `.dark` wrapper resolves them against its own scales. --status-* and --alert-* now alias the roles. Roles are bridged to Tailwind as --color-semantic-*. The alert description colour follows the alert foreground at full strength: 85% of the text step over the surface is below 4.5:1 in light mode for info, success and danger. The chain through --alert-*-foreground is kept. Co-Authored-By: Claude Sonnet 5.5 --- packages/core/src/assets/theme-bridge.test.ts | 24 +- packages/core/src/assets/theme.bridge.css | 41 ++ packages/core/src/assets/themes/default.css | 363 +++++++++++++++--- 3 files changed, 366 insertions(+), 62 deletions(-) diff --git a/packages/core/src/assets/theme-bridge.test.ts b/packages/core/src/assets/theme-bridge.test.ts index a4a47ff30..255154e15 100644 --- a/packages/core/src/assets/theme-bridge.test.ts +++ b/packages/core/src/assets/theme-bridge.test.ts @@ -14,6 +14,13 @@ const here = dirname(fileURLToPath(import.meta.url)); const bridge = readFileSync(join(here, "theme.bridge.css"), "utf8"); const defaultTheme = readFileSync(join(here, "themes/default.css"), "utf8"); +/** + * Semantic colour roles: 5 intents x 8 roles. The intent list is explicit so + * `--semantic-shadow-*` (bridged as `--shadow-*`, not `--color-*`) is not + * mistaken for a colour role. + */ +const SEMANTIC_PREFIX = "semantic-(?:info|success|warning|danger|neutral)"; + /** Token names defined in the default palette for a given prefix, e.g. `alert`. */ function definedTokens(prefix: string): string[] { const matches = defaultTheme.matchAll(new RegExp(`^\\s*--(${prefix}-[a-z-]+):`, "gm")); @@ -29,7 +36,11 @@ function bridgedTokens(prefix: string): Map { } describe("theme bridge", () => { - describe.each(["alert", "status"])("--%s-* tokens", (prefix) => { + describe.each([ + ["alert", "alert"], + ["status", "status"], + ["semantic colour role", SEMANTIC_PREFIX], + ])("--%s-* tokens", (_label, prefix) => { const defined = definedTokens(prefix); const bridged = bridgedTokens(prefix); @@ -56,4 +67,15 @@ describe("theme bridge", () => { // both files — fails loudly. expect(bridgedTokens("alert").size).toBe(20); }); + + it("bridges all 40 semantic colour roles", () => { + // 5 intents x 8 roles, pinned for the same reason as the alert count. + expect(bridgedTokens(SEMANTIC_PREFIX).size).toBe(40); + }); + + it("does not treat --semantic-shadow-* as a colour role", () => { + expect(definedTokens(SEMANTIC_PREFIX).some((name) => name.startsWith("semantic-shadow"))).toBe( + false, + ); + }); }); diff --git a/packages/core/src/assets/theme.bridge.css b/packages/core/src/assets/theme.bridge.css index c0a2ebf65..473fae7d6 100644 --- a/packages/core/src/assets/theme.bridge.css +++ b/packages/core/src/assets/theme.bridge.css @@ -100,6 +100,47 @@ --color-alert-info-foreground-muted: var(--alert-info-foreground-muted); --color-alert-info-border: var(--alert-info-border); + --color-semantic-neutral-surface: var(--semantic-neutral-surface); + --color-semantic-neutral-surface-hover: var(--semantic-neutral-surface-hover); + --color-semantic-neutral-border: var(--semantic-neutral-border); + --color-semantic-neutral-solid: var(--semantic-neutral-solid); + --color-semantic-neutral-solid-hover: var(--semantic-neutral-solid-hover); + --color-semantic-neutral-text: var(--semantic-neutral-text); + --color-semantic-neutral-on-solid: var(--semantic-neutral-on-solid); + --color-semantic-neutral-indicator: var(--semantic-neutral-indicator); + --color-semantic-info-surface: var(--semantic-info-surface); + --color-semantic-info-surface-hover: var(--semantic-info-surface-hover); + --color-semantic-info-border: var(--semantic-info-border); + --color-semantic-info-solid: var(--semantic-info-solid); + --color-semantic-info-solid-hover: var(--semantic-info-solid-hover); + --color-semantic-info-text: var(--semantic-info-text); + --color-semantic-info-on-solid: var(--semantic-info-on-solid); + --color-semantic-info-indicator: var(--semantic-info-indicator); + --color-semantic-success-surface: var(--semantic-success-surface); + --color-semantic-success-surface-hover: var(--semantic-success-surface-hover); + --color-semantic-success-border: var(--semantic-success-border); + --color-semantic-success-solid: var(--semantic-success-solid); + --color-semantic-success-solid-hover: var(--semantic-success-solid-hover); + --color-semantic-success-text: var(--semantic-success-text); + --color-semantic-success-on-solid: var(--semantic-success-on-solid); + --color-semantic-success-indicator: var(--semantic-success-indicator); + --color-semantic-warning-surface: var(--semantic-warning-surface); + --color-semantic-warning-surface-hover: var(--semantic-warning-surface-hover); + --color-semantic-warning-border: var(--semantic-warning-border); + --color-semantic-warning-solid: var(--semantic-warning-solid); + --color-semantic-warning-solid-hover: var(--semantic-warning-solid-hover); + --color-semantic-warning-text: var(--semantic-warning-text); + --color-semantic-warning-on-solid: var(--semantic-warning-on-solid); + --color-semantic-warning-indicator: var(--semantic-warning-indicator); + --color-semantic-danger-surface: var(--semantic-danger-surface); + --color-semantic-danger-surface-hover: var(--semantic-danger-surface-hover); + --color-semantic-danger-border: var(--semantic-danger-border); + --color-semantic-danger-solid: var(--semantic-danger-solid); + --color-semantic-danger-solid-hover: var(--semantic-danger-solid-hover); + --color-semantic-danger-text: var(--semantic-danger-text); + --color-semantic-danger-on-solid: var(--semantic-danger-on-solid); + --color-semantic-danger-indicator: var(--semantic-danger-indicator); + --shadow-xs: var(--semantic-shadow-xs); --shadow-sm: var(--semantic-shadow-sm); --shadow-md: var(--semantic-shadow-md); diff --git a/packages/core/src/assets/themes/default.css b/packages/core/src/assets/themes/default.css index 96929f051..195928940 100644 --- a/packages/core/src/assets/themes/default.css +++ b/packages/core/src/assets/themes/default.css @@ -54,50 +54,205 @@ --semantic-shadow-md: 0 4px 6px -1px rgb(15 23 42 / 0.09), 0 2px 4px -2px rgb(15 23 42 / 0.06); --semantic-shadow-lg: 0 10px 15px -3px rgb(15 23 42 / 0.12), 0 4px 6px -4px rgb(15 23 42 / 0.09); - /* ── 6. SEMANTIC — product-wide; inherited by all palettes, do not override */ - --status-default: #737373; - --status-neutral: #0ea5e9; - --status-completed: #22c55e; - --status-attention: #f59e0b; - --status-danger: #ef4444; - --alert-neutral-background: var(--muted); - --alert-neutral-foreground: var(--foreground); + /* ── 6. HUE SCALES (light) — internal; consumers use the semantic roles below ── + * Radix-style 12-step + alpha scales for four hues. Names are prefixed to + * avoid colliding with a consumer's own copy of Radix Colors. Not bridged + * to Tailwind. See decisions/semantic-color-roles.md for how they were made. + * The "alpha" steps are translucent only where the generator could express + * them; some light steps (for example amber a2-a6) resolve to opaque hex. + */ + --palette-blue-1: #f7f8f9; + --palette-blue-2: #f0f4fa; + --palette-blue-3: #e5ecf9; + --palette-blue-4: #d6e4fd; + --palette-blue-5: #c5d9ff; + --palette-blue-6: #b0cbff; + --palette-blue-7: #98b9f7; + --palette-blue-8: #769ff0; + --palette-blue-9: #2563eb; + --palette-blue-10: #2057d1; + --palette-blue-11: #1e55cf; + --palette-blue-12: #152e63; + --palette-blue-a1: #3b7bbb04; + --palette-blue-a2: #136ffa0b; + --palette-blue-a3: #0758ef16; + --palette-blue-a4: #bed6ff99; + --palette-blue-a5: #c5d9ff; + --palette-blue-a6: #b0cbff; + --palette-blue-a7: #0155f364; + --palette-blue-a8: #004ee787; + --palette-blue-a9: #014ae9da; + --palette-blue-a10: #0140ccdf; + --palette-blue-a11: #013fcae1; + --palette-blue-a12: #001b55ea; + --palette-blue-contrast: #ffffff; + + --palette-green-1: #f5f9f6; + --palette-green-2: #eff5f0; + --palette-green-3: #e1f1e3; + --palette-green-4: #d1ead5; + --palette-green-5: #bfe2c5; + --palette-green-6: #a7d7b0; + --palette-green-7: #88c895; + --palette-green-8: #57b36f; + --palette-green-9: #15803d; + --palette-green-10: #00722f; + --palette-green-11: #047936; + --palette-green-12: #1e3c25; + --palette-green-a1: #26d05006; + --palette-green-a2: #1190260c; + --palette-green-a3: #04a1181a; + --palette-green-a4: #02991a2a; + --palette-green-a5: #04961d3d; + --palette-green-a6: #01911c55; + --palette-green-a7: #028e1e75; + --palette-green-a8: #028e26a7; + --palette-green-a9: #00752cea; + --palette-green-a10: #00722f; + --palette-green-a11: #007733fb; + --palette-green-a12: #012309e1; + --palette-green-contrast: #ffffff; + + --palette-amber-1: #f9f7f5; + --palette-amber-2: #fff2e1; + --palette-amber-3: #ffe7bd; + --palette-amber-4: #ffdca5; + --palette-amber-5: #ffd089; + --palette-amber-6: #ffbe5d; + --palette-amber-7: #f8ad4a; + --palette-amber-8: #e99300; + --palette-amber-9: #f59e0b; + --palette-amber-10: #e99300; + --palette-amber-11: #a35f00; + --palette-amber-12: #4f3515; + --palette-amber-a1: #d07b2606; + --palette-amber-a2: #fff2e1; + --palette-amber-a3: #ffe7bd; + --palette-amber-a4: #ffdca5; + --palette-amber-a5: #ffd089; + --palette-amber-a6: #ffbe5d; + --palette-amber-a7: #f78c00b4; + --palette-amber-a8: #e99300; + --palette-amber-a9: #f59a00f4; + --palette-amber-a10: #e99300; + --palette-amber-a11: #a35f00; + --palette-amber-a12: #3f2300ea; + --palette-amber-contrast: #4f3515; + + --palette-red-1: #faf7f6; + --palette-red-2: #f9f2f1; + --palette-red-3: #f9e5e2; + --palette-red-4: #ffd4ce; + --palette-red-5: #ffc5bd; + --palette-red-6: #f9b4ab; + --palette-red-7: #f19f95; + --palette-red-8: #e88479; + --palette-red-9: #dc2626; + --palette-red-10: #cc0615; + --palette-red-11: #ce0c17; + --palette-red-12: #641c18; + --palette-red-a1: #fb622f05; + --palette-red-a2: #e12e150a; + --palette-red-a3: #f0240619; + --palette-red-a4: #ffd4ce; + --palette-red-a5: #ffc5bd; + --palette-red-a6: #f71d0151; + --palette-red-a7: #e41b0368; + --palette-red-a8: #d7160084; + --palette-red-a9: #d70101d9; + --palette-red-a10: #cb000ff9; + --palette-red-a11: #cc000cf3; + --palette-red-a12: #540400e7; + --palette-red-contrast: #ffffff; +} + +/* + * 6. SEMANTIC — product-wide; inherited by all palettes, do not override. + * + * Declared for both `:root` and `.dark` so a nested `.dark` wrapper re-resolves + * the roles against its own hue scales. The values are identical in both + * selectors; only the hue scales differ per mode. + * + * Roles: `--semantic-{info|success|warning|danger|neutral}-{surface|surface-hover| + * border|solid|solid-hover|text|on-solid|indicator}`. Hue intents read the hue + * scales; `neutral` reads the system tokens. `--status-*` and `--alert-*` below + * are aliases of these roles, so both families agree by construction. + */ +:root, +.dark { + --semantic-neutral-surface: var(--muted); + --semantic-neutral-surface-hover: var(--accent); + --semantic-neutral-border: var(--border); + --semantic-neutral-solid: var(--secondary); + --semantic-neutral-solid-hover: var(--accent); + --semantic-neutral-text: var(--foreground); + --semantic-neutral-on-solid: var(--secondary-foreground); + --semantic-neutral-indicator: var(--muted-foreground); + --semantic-info-surface: var(--palette-blue-a3); + --semantic-info-surface-hover: var(--palette-blue-a4); + --semantic-info-border: var(--palette-blue-a6); + --semantic-info-solid: var(--palette-blue-9); + --semantic-info-solid-hover: var(--palette-blue-10); + --semantic-info-text: var(--palette-blue-11); + --semantic-info-on-solid: var(--palette-blue-contrast); + --semantic-info-indicator: var(--palette-blue-9); + --semantic-success-surface: var(--palette-green-a3); + --semantic-success-surface-hover: var(--palette-green-a4); + --semantic-success-border: var(--palette-green-a6); + --semantic-success-solid: var(--palette-green-9); + --semantic-success-solid-hover: var(--palette-green-10); + --semantic-success-text: var(--palette-green-11); + --semantic-success-on-solid: var(--palette-green-contrast); + --semantic-success-indicator: var(--palette-green-9); + --semantic-warning-surface: var(--palette-amber-a3); + --semantic-warning-surface-hover: var(--palette-amber-a4); + --semantic-warning-border: var(--palette-amber-a6); + --semantic-warning-solid: var(--palette-amber-9); + --semantic-warning-solid-hover: var(--palette-amber-10); + --semantic-warning-text: var(--palette-amber-12); + --semantic-warning-on-solid: var(--palette-amber-contrast); + --semantic-warning-indicator: var(--palette-amber-9); + --semantic-danger-surface: var(--palette-red-a3); + --semantic-danger-surface-hover: var(--palette-red-a4); + --semantic-danger-border: var(--palette-red-a6); + --semantic-danger-solid: var(--palette-red-9); + --semantic-danger-solid-hover: var(--palette-red-10); + --semantic-danger-text: var(--palette-red-11); + --semantic-danger-on-solid: var(--palette-red-contrast); + --semantic-danger-indicator: var(--palette-red-9); + + --status-default: var(--semantic-neutral-indicator); + --status-neutral: var(--semantic-info-solid); + --status-completed: var(--semantic-success-solid); + --status-attention: var(--semantic-warning-solid); + --status-danger: var(--semantic-danger-solid); + --alert-neutral-background: var(--semantic-neutral-surface); + --alert-neutral-foreground: var(--semantic-neutral-text); --alert-neutral-foreground-muted: var(--muted-foreground); - --alert-neutral-border: var(--border); - --alert-success-foreground: #15803d; - --alert-success-foreground-muted: color-mix( - in srgb, - var(--alert-success-foreground) 80%, - transparent - ); - --alert-success-background: color-mix(in srgb, #22c55e 10%, transparent); - --alert-success-border: color-mix(in srgb, #22c55e 20%, transparent); - --alert-warning-foreground: #a16207; - --alert-warning-foreground-muted: color-mix( - in srgb, - var(--alert-warning-foreground) 80%, - transparent - ); - --alert-warning-background: color-mix(in srgb, #eab308 10%, transparent); - --alert-warning-border: color-mix(in srgb, #eab308 20%, transparent); - --alert-error-foreground: var(--destructive); - --alert-error-foreground-muted: color-mix( - in srgb, - var(--alert-error-foreground) 80%, - transparent - ); - --alert-error-background: color-mix(in srgb, var(--destructive) 10%, transparent); - --alert-error-border: color-mix(in srgb, var(--destructive) 20%, transparent); - --alert-info-foreground: #1d4ed8; - --alert-info-foreground-muted: color-mix(in srgb, var(--alert-info-foreground) 80%, transparent); - --alert-info-background: color-mix(in srgb, #3b82f6 10%, transparent); - --alert-info-border: color-mix(in srgb, #3b82f6 20%, transparent); + --alert-neutral-border: var(--semantic-neutral-border); + --alert-success-background: var(--semantic-success-surface); + --alert-success-foreground: var(--semantic-success-text); + --alert-success-foreground-muted: var(--alert-success-foreground); + --alert-success-border: var(--semantic-success-border); + --alert-warning-background: var(--semantic-warning-surface); + --alert-warning-foreground: var(--semantic-warning-text); + --alert-warning-foreground-muted: var(--alert-warning-foreground); + --alert-warning-border: var(--semantic-warning-border); + --alert-error-background: var(--semantic-danger-surface); + --alert-error-foreground: var(--semantic-danger-text); + --alert-error-foreground-muted: var(--alert-error-foreground); + --alert-error-border: var(--semantic-danger-border); + --alert-info-background: var(--semantic-info-surface); + --alert-info-foreground: var(--semantic-info-text); + --alert-info-foreground-muted: var(--alert-info-foreground); + --alert-info-border: var(--semantic-info-border); } /* - * Dark variant. Overrides BRAND + SYSTEM (+ semantic foregrounds that differ). - * Shared aliases/ring re-resolve when BRAND changes; `--radius` and `--status-*` - * inherit from the light block. + * Dark variant. Overrides BRAND + SYSTEM + the hue scales. The semantic roles + * and aliases live in the shared block above and re-resolve against these + * scales. Shared aliases/ring re-resolve when BRAND changes; `--radius` + * inherits from the light block. */ .dark { color-scheme: dark; @@ -138,25 +293,111 @@ --semantic-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.35), 0 2px 4px -2px rgb(0 0 0 / 0.28); --semantic-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.42), 0 4px 6px -4px rgb(0 0 0 / 0.35); - /* ── 6. SEMANTIC (dark foreground adjustments) ──────────────────────── */ - --alert-success-foreground: #86efac; - --alert-success-foreground-muted: color-mix( - in srgb, - var(--alert-success-foreground) 85%, - transparent - ); - --alert-warning-foreground: #fde047; - --alert-warning-foreground-muted: color-mix( - in srgb, - var(--alert-warning-foreground) 85%, - transparent - ); - --alert-error-foreground: #fca5a5; - --alert-error-foreground-muted: color-mix( - in srgb, - var(--alert-error-foreground) 85%, - transparent - ); - --alert-info-foreground: #93c5fd; - --alert-info-foreground-muted: color-mix(in srgb, var(--alert-info-foreground) 85%, transparent); + /* ── 6. HUE SCALES (dark) ─────────────────────────────────────────────── */ + --palette-blue-1: #050a16; + --palette-blue-2: #0e1626; + --palette-blue-3: #11244c; + --palette-blue-4: #132e66; + --palette-blue-5: #1a397a; + --palette-blue-6: #22448b; + --palette-blue-7: #2b509f; + --palette-blue-8: #325ebb; + --palette-blue-9: #2563eb; + --palette-blue-10: #315dba; + --palette-blue-11: #88b5ff; + --palette-blue-12: #d1e2ff; + --palette-blue-a1: #000af60d; + --palette-blue-a2: #2c70f81e; + --palette-blue-a3: #246bfe45; + --palette-blue-a4: #226aff60; + --palette-blue-a5: #2d71ff75; + --palette-blue-a6: #3777fe87; + --palette-blue-a7: #407dfe9c; + --palette-blue-a8: #417efeb9; + --palette-blue-a9: #276afeeb; + --palette-blue-a10: #407dfeb8; + --palette-blue-a11: #88b5ff; + --palette-blue-a12: #d1e2ff; + --palette-blue-contrast: #ffffff; + + --palette-green-1: #050c06; + --palette-green-2: #101a12; + --palette-green-3: #152b1a; + --palette-green-4: #0f3c1d; + --palette-green-5: #124b24; + --palette-green-6: #155b2c; + --palette-green-7: #166c34; + --palette-green-8: #137f3c; + --palette-green-9: #15803d; + --palette-green-10: #1b6f37; + --palette-green-11: #71d088; + --palette-green-12: #acf7bb; + --palette-green-a1: #00b40003; + --palette-green-a2: #64fa8211; + --palette-green-a3: #5bfb7f23; + --palette-green-a4: #23fb6635; + --palette-green-a5: #28fe6c44; + --palette-green-a6: #2bfd7055; + --palette-green-a7: #28ff7366; + --palette-green-a8: #1dff737a; + --palette-green-a9: #21ff747b; + --palette-green-a10: #33fd776a; + --palette-green-a11: #89fea5cf; + --palette-green-a12: #b2ffc1f7; + --palette-green-contrast: #ffffff; + + --palette-amber-1: #0d0906; + --palette-amber-2: #1c150e; + --palette-amber-3: #2f1f0c; + --palette-amber-4: #412500; + --palette-amber-5: #4e2e00; + --palette-amber-6: #5d3c0f; + --palette-amber-7: #724d1e; + --palette-amber-8: #926426; + --palette-amber-9: #f59e0b; + --palette-amber-10: #e99300; + --palette-amber-11: #fbb050; + --palette-amber-12: #fde2c4; + --palette-amber-a1: #ca000004; + --palette-amber-a2: #fc9e4013; + --palette-amber-a3: #fc941827; + --palette-amber-a4: #fc81003a; + --palette-amber-a5: #ff8c0047; + --palette-amber-a6: #fd9c1857; + --palette-amber-a7: #fda7396d; + --palette-amber-a8: #ffac3d8e; + --palette-amber-a9: #ffa40bf5; + --palette-amber-a10: #fea000e9; + --palette-amber-a11: #ffb351fb; + --palette-amber-a12: #ffe4c6fd; + --palette-amber-contrast: #4f3515; + + --palette-red-1: #100807; + --palette-red-2: #1f1210; + --palette-red-3: #3c110e; + --palette-red-4: #530b0a; + --palette-red-5: #641210; + --palette-red-6: #76201c; + --palette-red-7: #8f312a; + --palette-red-8: #b94038; + --palette-red-9: #dc2626; + --palette-red-10: #b63d36; + --palette-red-11: #ff9083; + --palette-red-12: #ffd1ca; + --palette-red-a1: #e5000007; + --palette-red-a2: #fe675016; + --palette-red-a3: #fb2c1e35; + --palette-red-a4: #ff0e0a4c; + --palette-red-a5: #ff201b5e; + --palette-red-a6: #fd3b3271; + --palette-red-a7: #fe51448b; + --palette-red-a8: #fe554ab7; + --palette-red-a9: #ff2b2bdb; + --palette-red-a10: #fe5248b4; + --palette-red-a11: #ff9083; + --palette-red-a12: #ffd1ca; + --palette-red-contrast: #ffffff; + + /* Warning text: step 12 on the light surface, step 11 on the dark one. */ + --semantic-warning-text: var(--palette-amber-11); } From 73414a2e64ea14dc8bd33f70b7e3d328f62147d2 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Wed, 30 Sep 2026 09:16:04 +0900 Subject: [PATCH 02/29] test(core): check semantic colour contrast from default.css Parse default.css for light and dark, resolve var() chains and alpha, and assert WCAG 4.5:1 for on-solid vs solid and solid-hover, text vs surface over --card and --background, and the alert foreground and description over the alert background. Hover surfaces, branded palettes and the neutral intent are out of scope and listed in the file header. Co-Authored-By: Claude Sonnet 5.5 --- .../core/src/assets/semantic-contrast.test.ts | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 packages/core/src/assets/semantic-contrast.test.ts diff --git a/packages/core/src/assets/semantic-contrast.test.ts b/packages/core/src/assets/semantic-contrast.test.ts new file mode 100644 index 000000000..5957de443 --- /dev/null +++ b/packages/core/src/assets/semantic-contrast.test.ts @@ -0,0 +1,140 @@ +import { readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +/** + * WCAG 1.4.3 contrast checks for the semantic colour roles, computed from + * `themes/default.css` itself. Nothing is copied into the test, so a change to + * a hue step or an alias that breaks a pair fails here. + * + * Scope of the guarantee: the default state of each pair, on the default + * palette's `--card` and `--background`. Not covered on purpose: + * - hover states (`surface-hover`): subtle-badge hover text is below 4.5:1 for + * some roles, see decisions/semantic-color-roles.md; + * - branded palettes (cream, bloom) and their tinted shell backgrounds; + * - the neutral intent, whose values are the pre-existing system tokens; + * - the warning `solid` fill against the card (a fill, not a text pair). + */ +const here = dirname(fileURLToPath(import.meta.url)); +const css = readFileSync(join(here, "themes/default.css"), "utf8").replace(/\/\*[\s\S]*?\*\//g, ""); + +type Rgba = { r: number; g: number; b: number; a: number }; +type Mode = "light" | "dark"; + +/** Custom properties in effect on for a mode, applying rules in source order. */ +function variablesFor(mode: Mode): Map { + const vars = new Map(); + for (const rule of css.matchAll(/([^{}]+)\{([^{}]*)\}/g)) { + const selectors = rule[1].split(",").map((s) => s.trim()); + // matches both `:root` and `.dark`; light matches `:root` only. + const applies = selectors.some((s) => s === ":root" || (mode === "dark" && s === ".dark")); + if (!applies) continue; + for (const decl of rule[2].matchAll(/(--[a-z0-9-]+)\s*:\s*([^;]+);/g)) { + vars.set(decl[1], decl[2].replace(/\s+/g, " ").trim()); + } + } + return vars; +} + +function parseColor(value: string, vars: Map): Rgba { + const v = value.trim(); + const ref = /^var\((--[a-z0-9-]+)\)$/.exec(v); + if (ref) { + const target = vars.get(ref[1]); + if (target === undefined) throw new Error(`Undefined variable ${ref[1]}`); + return parseColor(target, vars); + } + const hex = /^#([0-9a-f]{3,8})$/i.exec(v); + if (hex) { + let h = hex[1]; + if (h.length === 3 || h.length === 4) h = [...h].map((c) => c + c).join(""); + if (h.length !== 6 && h.length !== 8) throw new Error(`Bad hex colour ${v}`); + return { + r: parseInt(h.slice(0, 2), 16), + g: parseInt(h.slice(2, 4), 16), + b: parseInt(h.slice(4, 6), 16), + a: h.length === 8 ? parseInt(h.slice(6, 8), 16) / 255 : 1, + }; + } + const fn = /^rgba?\(([^)]+)\)$/.exec(v); + if (fn) { + const [r, g, b, a = "1"] = fn[1].split(",").map((p) => p.trim()); + return { r: Number(r), g: Number(g), b: Number(b), a: Number(a) }; + } + throw new Error(`Unsupported colour syntax: ${v}`); +} + +function over(top: Rgba, base: Rgba): Rgba { + const a = top.a + base.a * (1 - top.a); + const mix = (t: number, b: number) => (t * top.a + b * base.a * (1 - top.a)) / a; + return { r: mix(top.r, base.r), g: mix(top.g, base.g), b: mix(top.b, base.b), a }; +} + +function luminance({ r, g, b }: Rgba): number { + const lin = (c: number) => { + const s = c / 255; + return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4; + }; + return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b); +} + +function contrast(a: Rgba, b: Rgba): number { + const [hi, lo] = [luminance(a), luminance(b)].toSorted((x, y) => y - x); + return (hi + 0.05) / (lo + 0.05); +} + +const MIN_TEXT = 4.5; +const modes: Mode[] = ["light", "dark"]; +const intents = ["info", "success", "warning", "danger"] as const; +const alertVariantFor = { info: "info", success: "success", warning: "warning", danger: "error" }; +const pages = ["--card", "--background"] as const; + +describe.each(modes)("semantic colour contrast (%s)", (mode) => { + const vars = variablesFor(mode); + const color = (name: string) => parseColor(`var(${name})`, vars); + /** Foreground drawn on a translucent surface that sits on a page colour. */ + const pair = (fg: string, surface: string, page: string) => { + const base = color(page); + const bg = over(color(surface), base); + return contrast(over(color(fg), bg), bg); + }; + + it("resolves the page colours to opaque values", () => { + for (const page of pages) expect(color(page).a).toBe(1); + }); + + describe.each(intents)("%s", (intent) => { + const role = (name: string) => `--semantic-${intent}-${name}`; + + it("on-solid text meets 4.5:1 on solid", () => { + expect(contrast(color(role("on-solid")), color(role("solid")))).toBeGreaterThanOrEqual( + MIN_TEXT, + ); + }); + + it("on-solid text meets 4.5:1 on solid-hover", () => { + expect(contrast(color(role("on-solid")), color(role("solid-hover")))).toBeGreaterThanOrEqual( + MIN_TEXT, + ); + }); + + it.each(pages)("text meets 4.5:1 on surface over %s", (page) => { + expect(pair(role("text"), role("surface"), page)).toBeGreaterThanOrEqual(MIN_TEXT); + }); + + const variant = alertVariantFor[intent]; + + it.each(pages)("alert foreground meets 4.5:1 on alert background over %s", (page) => { + expect( + pair(`--alert-${variant}-foreground`, `--alert-${variant}-background`, page), + ).toBeGreaterThanOrEqual(MIN_TEXT); + }); + + it.each(pages)("alert description meets 4.5:1 on alert background over %s", (page) => { + expect( + pair(`--alert-${variant}-foreground-muted`, `--alert-${variant}-background`, page), + ).toBeGreaterThanOrEqual(MIN_TEXT); + }); + }); +}); From 2f6c43d3755ea55ac23c31a8c78a7ca28c8b511f Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Wed, 30 Sep 2026 09:16:38 +0900 Subject: [PATCH 03/29] fix(core): read semantic colour roles in Badge Filled, subtle and outline-dot Badge variants now use --color-semantic-* utilities. Filled text uses on-solid roles (at least 4.66:1 against solid and solid-hover), so the warning badge text is dark instead of white on amber. Error and subtle-error follow the danger role instead of --destructive. A new test checks that every semantic utility a variant uses is bridged. Co-Authored-By: Claude Sonnet 5.5 --- .../core/src/components/badge/badge.test.tsx | 40 +++++++++++++++++++ packages/core/src/components/badge/badge.tsx | 26 ++++++------ 2 files changed, 53 insertions(+), 13 deletions(-) create mode 100644 packages/core/src/components/badge/badge.test.tsx diff --git a/packages/core/src/components/badge/badge.test.tsx b/packages/core/src/components/badge/badge.test.tsx new file mode 100644 index 000000000..0a0578016 --- /dev/null +++ b/packages/core/src/components/badge/badge.test.tsx @@ -0,0 +1,40 @@ +import { readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; +import { badgeVariants } from "./badge"; + +/** + * Badge is precompiled with `astw:` utilities. A `bg-semantic-*` class whose + * token is missing from the Tailwind bridge emits no CSS and fails silently, + * so check every semantic utility a variant uses against `theme.bridge.css`. + */ +const bridge = readFileSync( + join(dirname(fileURLToPath(import.meta.url)), "../../assets/theme.bridge.css"), + "utf8", +); + +const variants = [ + "success", + "warning", + "error", + "info", + "subtle-success", + "subtle-warning", + "subtle-error", + "subtle-info", +] as const; + +describe("badgeVariants semantic colour classes", () => { + it.each(variants)("%s uses only bridged semantic tokens", (variant) => { + const classes = badgeVariants({ variant }).split(/\s+/); + const tokens = classes + .map((c) => /^astw:(?:hover:)?(?:bg|text)-(semantic-[a-z-]+)$/.exec(c)?.[1]) + .filter((t): t is string => t !== undefined); + + expect(tokens.length).toBeGreaterThanOrEqual(3); + for (const token of tokens) { + expect(bridge).toContain(`--color-${token}:`); + } + }); +}); diff --git a/packages/core/src/components/badge/badge.tsx b/packages/core/src/components/badge/badge.tsx index 5054ad5b0..52ec48640 100644 --- a/packages/core/src/components/badge/badge.tsx +++ b/packages/core/src/components/badge/badge.tsx @@ -11,22 +11,22 @@ const badgeVariants = cva( default: "astw:border-transparent astw:bg-primary astw:text-primary-foreground astw:hover:bg-primary/80", success: - "astw:border-transparent astw:bg-status-completed astw:text-white astw:hover:bg-status-completed/90", + "astw:border-transparent astw:bg-semantic-success-solid astw:text-semantic-success-on-solid astw:hover:bg-semantic-success-solid-hover", warning: - "astw:border-transparent astw:bg-status-attention astw:text-white astw:hover:bg-status-attention/90", + "astw:border-transparent astw:bg-semantic-warning-solid astw:text-semantic-warning-on-solid astw:hover:bg-semantic-warning-solid-hover", error: - "astw:border-transparent astw:bg-destructive astw:text-destructive-foreground astw:hover:bg-destructive/80", + "astw:border-transparent astw:bg-semantic-danger-solid astw:text-semantic-danger-on-solid astw:hover:bg-semantic-danger-solid-hover", neutral: "astw:border-transparent astw:bg-secondary astw:text-secondary-foreground astw:hover:bg-secondary/80", - info: "astw:border-transparent astw:bg-status-neutral astw:text-white astw:hover:bg-status-neutral/90", + info: "astw:border-transparent astw:bg-semantic-info-solid astw:text-semantic-info-on-solid astw:hover:bg-semantic-info-solid-hover", "subtle-success": - "astw:border-transparent astw:bg-status-completed/10 astw:text-status-completed astw:hover:bg-status-completed/20", + "astw:border-transparent astw:bg-semantic-success-surface astw:text-semantic-success-text astw:hover:bg-semantic-success-surface-hover", "subtle-warning": - "astw:border-transparent astw:bg-status-attention/10 astw:text-status-attention astw:hover:bg-status-attention/20", + "astw:border-transparent astw:bg-semantic-warning-surface astw:text-semantic-warning-text astw:hover:bg-semantic-warning-surface-hover", "subtle-error": - "astw:border-transparent astw:bg-destructive/10 astw:text-destructive astw:hover:bg-destructive/20", + "astw:border-transparent astw:bg-semantic-danger-surface astw:text-semantic-danger-text astw:hover:bg-semantic-danger-surface-hover", "subtle-info": - "astw:border-transparent astw:bg-status-neutral/10 astw:text-status-neutral astw:hover:bg-status-neutral/20", + "astw:border-transparent astw:bg-semantic-info-surface astw:text-semantic-info-text astw:hover:bg-semantic-info-surface-hover", // Outline variants with status dots - matches Figma design "outline-success": "astw:gap-0.5 astw:pl-1.5 astw:pr-2 astw:border-border astw:bg-card astw:text-foreground", @@ -48,11 +48,11 @@ const badgeVariants = cva( // Status dot colors for outline variants const statusDotColors: Record = { - "outline-success": "astw:bg-status-completed", - "outline-warning": "astw:bg-status-attention", - "outline-error": "astw:bg-status-danger", - "outline-info": "astw:bg-status-neutral", - "outline-neutral": "astw:bg-status-default", + "outline-success": "astw:bg-semantic-success-indicator", + "outline-warning": "astw:bg-semantic-warning-indicator", + "outline-error": "astw:bg-semantic-danger-indicator", + "outline-info": "astw:bg-semantic-info-indicator", + "outline-neutral": "astw:bg-semantic-neutral-indicator", }; function StatusDot({ variant }: { variant: string }) { From a7af91070bd409557062615276d72b4eef0892bc Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Wed, 30 Sep 2026 09:16:55 +0900 Subject: [PATCH 04/29] fix(core): use semantic text roles for CsvImporter counts CsvImporter used --status-attention and --status-completed as text colours. Those tokens now alias the step 9 solids, which are fill colours and fall below 4.5:1 as text in dark mode (success 3.57:1, on the dark card). Use the warning and success text roles for the three text usages. Fills and borders keep the status tokens. Co-Authored-By: Claude Sonnet 5.5 --- packages/core/src/components/csv-importer/CsvImporter.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/components/csv-importer/CsvImporter.tsx b/packages/core/src/components/csv-importer/CsvImporter.tsx index ffe757ed8..dc786593a 100644 --- a/packages/core/src/components/csv-importer/CsvImporter.tsx +++ b/packages/core/src/components/csv-importer/CsvImporter.tsx @@ -364,10 +364,10 @@ function ReviewStep({ Total: {rawRows.length} rows {errorCount > 0 && Errors: {errorCount}} {warningCount > 0 && ( - Warnings: {warningCount} + Warnings: {warningCount} )} {validated && errorCount === 0 && !validating && ( - + {t("reviewNoErrors")} @@ -438,7 +438,7 @@ function ReviewStep({ "astw:text-xs astw:px-2", issue.level === "error" ? "astw:text-destructive" - : "astw:text-status-attention", + : "astw:text-semantic-warning-text", )} > {issue.message} From b18aaf1bba2870ce4f4023357ebde0c35d7cf85b Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Wed, 30 Sep 2026 09:17:48 +0900 Subject: [PATCH 05/29] docs(core): document semantic colour roles Add a Semantic color roles section to the styling and theming concept, describe --status-* and --alert-* as aliases of the roles, state which contrast pairs are checked, and correct the note on alert background alpha (some light-mode values are opaque). Say that Badge error and Alert error follow the danger role instead of --destructive, and that --status-* are fill colours, not text colours. docs/ and docs-manifest.json are regenerated with pnpm docs:sync. Co-Authored-By: Claude Sonnet 5.5 --- docs-manifest.json | 12 ++--- .../custom-components.docs.outline.md | 2 +- .../concepts/styling-theming.docs.outline.md | 51 +++++++++++++++---- docs/concepts/custom-components.md | 2 +- docs/concepts/styling-theming.md | 51 +++++++++++++++---- 5 files changed, 88 insertions(+), 30 deletions(-) diff --git a/docs-manifest.json b/docs-manifest.json index 718997a22..c69a03c45 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -372,9 +372,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "33796a2dd22b5c0d", + "outline": "9d8ffad15d9b3347", "snapshot": null, - "outputMd": "551e04b1abbfa101", + "outputMd": "88466a0159cc0216", "examples": null } }, @@ -1216,9 +1216,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "96d4bfc1c2681154", + "outline": "e3d84f4a19a8028f", "snapshot": null, - "outputMd": "0133fdae405f467c", + "outputMd": "2715f58f113da4de", "examples": "d3cca347b055a503" } }, @@ -1582,13 +1582,13 @@ }, "skill": { "packages/core/skills/app-shell-patterns/references/concepts/authentication.md": "ce27a191becaca3a", - "packages/core/skills/app-shell-patterns/references/concepts/custom-components.md": "551e04b1abbfa101", + "packages/core/skills/app-shell-patterns/references/concepts/custom-components.md": "88466a0159cc0216", "packages/core/skills/app-shell-patterns/references/concepts/file-based-routing.md": "caa1ea6264d3b543", "packages/core/skills/app-shell-patterns/references/concepts/graphql.md": "ba5107604e4f42e8", "packages/core/skills/app-shell-patterns/references/concepts/modules-and-resources.md": "bf15b5460f22d165", "packages/core/skills/app-shell-patterns/references/concepts/routing-navigation.md": "2f3ff3f095c2b1cc", "packages/core/skills/app-shell-patterns/references/concepts/sidebar-navigation.md": "96f1cf893c231861", - "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "0133fdae405f467c", + "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "2715f58f113da4de", "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/custom-components.docs.outline.md b/docs-src/concepts/custom-components.docs.outline.md index 87eb9ef55..d46ec6796 100644 --- a/docs-src/concepts/custom-components.docs.outline.md +++ b/docs-src/concepts/custom-components.docs.outline.md @@ -44,7 +44,7 @@ export function StepIndicator({ steps, current }: Props) { flex items-center gap-2 rounded-md px-3 py-2 bg-muted text-sm text-muted-foreground data-[active]:bg-primary data-[active]:text-primary-foreground - data-[completed]:text-status-completed + data-[completed]:text-semantic-success-text " > {i < current && } diff --git a/docs-src/concepts/styling-theming.docs.outline.md b/docs-src/concepts/styling-theming.docs.outline.md index ffe7b0f85..b542f5cd8 100644 --- a/docs-src/concepts/styling-theming.docs.outline.md +++ b/docs-src/concepts/styling-theming.docs.outline.md @@ -11,7 +11,7 @@ Styling is done with **Tailwind CSS v4** against `@tailor-platform/app-shell`'s **The tokens are the rails.** Consistency across customers, apps, and AI runs comes from the token system, not from rules written in prose. A hand-typed `#fff` or `padding: 13px` is not a "small deviation" — it is the mechanism by which consistency dies. -**Every token in this document is verified against the shipped CSS.** If a token is not listed here, assume it does not exist. Inventing a plausible-sounding token (`bg-surface-1`, `text-fg-muted`, `--space-4`) is the worst failure mode available: Tailwind emits **no CSS at all** for an unknown utility, so the class is silently dropped and the element renders unstyled — no error, no warning, nothing in the console. When unsure, read `node_modules/@tailor-platform/app-shell/dist/themes/default.css`; it is the ground truth. +**Every token in this document is verified against the shipped CSS.** If a token is not listed here, assume it does not exist. The hue scales (`--palette-*`) are internal: they are not bridged to Tailwind and are not listed. Inventing a plausible-sounding token (`bg-surface-1`, `text-fg-muted`, `--space-4`) is the worst failure mode available: Tailwind emits **no CSS at all** for an unknown utility, so the class is silently dropped and the element renders unstyled — no error, no warning, nothing in the console. When unsure, read `node_modules/@tailor-platform/app-shell/dist/themes/default.css`; it is the ground truth. ## Setup @@ -92,7 +92,7 @@ Theme tokens live in `packages/core/src/assets/themes/`. Copy `_template.css` to | **2. Shell gradient** | Branded palettes only | `--shell-gradient-base`, `--shell-gradient-tint` | | **3. System** | Tune or copy default | Surfaces: background, card, popover, muted, borders | | **4. Palette** | Optional | Radius, chart colors, shadows | -| **5. Semantic** | Do not duplicate | Status and alert tokens inherit from `default.css` | +| **5. Semantic** | Do not duplicate | Semantic roles, status and alert tokens inherit from `default.css` | | **6. Structural** | Branded palettes | Copy the structural override block from `bloom.css` or `cream.css` when needed | A palette is selected by CSS import, not by an AppShell prop. Import exactly one theme file after `@tailor-platform/app-shell/styles`; if you import none, the default palette from `styles` is used. @@ -158,7 +158,7 @@ There are no `-hover` or `-active` brand tokens. Express interaction states with #### Status -Five status colors, used for badge fills and status dots: +Five status colors, used for badge fills and status dots. They are aliases of the [semantic color roles](#semantic-color-roles) below, so status colors and Alert colors agree by default: | Token | Use | Tailwind | | -------------------- | ------------------------ | --------------------- | @@ -170,6 +170,35 @@ Five status colors, used for badge fills and status dots: Prefer `Badge` with a semantic variant (`success`, `warning`, `error`, `info`, `neutral`) over applying these directly — the variants already pair fill and foreground correctly. Reach for the raw token only on custom surfaces. +These are fill and indicator colors. Do not use them as text color: `--status-completed`, `--status-danger` and `--status-neutral` are below 4.5:1 on a dark card, and `--status-attention` is below 3:1 on a light one. For text, use `text-semantic-{intent}-text`. + +#### Semantic color roles + +Each intent has eight roles. The intents are `info`, `success`, `warning`, `danger` and `neutral`. The hue intents read internal color scales; `neutral` reads the system tokens. Every role is a token named `--semantic-{intent}-{role}` and a Tailwind color named `semantic-{intent}-{role}`: + +| Role | Tailwind | Use for | +| --------------- | -------------------------------- | ----------------------------------- | +| `surface` | `bg-semantic-info-surface` | Soft background | +| `surface-hover` | `bg-semantic-info-surface-hover` | Hover state of a soft background | +| `border` | `border-semantic-info-border` | Border on a soft background | +| `solid` | `bg-semantic-info-solid` | Filled background | +| `solid-hover` | `bg-semantic-info-solid-hover` | Hover state of a filled background | +| `text` | `text-semantic-info-text` | Text and icons on a soft background | +| `on-solid` | `text-semantic-info-on-solid` | Text on a filled background | +| `indicator` | `bg-semantic-info-indicator` | Dots and small marks | + +```tsx + + Paid + +``` + +In the default palette, `text` on `surface` and `on-solid` on `solid` and `solid-hover` are at least 4.5:1 for the four hue intents, in light and dark mode, on `--card` and `--background`. A unit test checks this from the shipped CSS. It does not cover hover surfaces, the `cream` and `bloom` palettes, or the `neutral` intent. Text on `surface-hover` is below 4.5:1 for some roles. + +Some light-mode values are opaque instead of translucent: the `warning` surface, surface-hover and border, the `info` border and the `danger` surface-hover. They do not blend with a tinted parent. All dark-mode values are translucent. + +`Badge` and `Alert` read these roles. `Badge` `error` and `subtle-error`, and `Alert` `error`, follow `--semantic-danger-*`. They no longer follow `--destructive`, which still drives `Button` and `text-destructive`. + #### Sidebar & charts `--sidebar`, `--sidebar-foreground`, `--sidebar-border`, `--sidebar-primary(-foreground)`, `--sidebar-accent(-foreground)`, `--sidebar-ring` → `bg-sidebar`, `text-sidebar-foreground`, and so on. These let a palette tint the shell independently of page content. @@ -178,14 +207,14 @@ Prefer `Badge` with a semantic variant (`success`, `warning`, `error`, `info`, ` #### Alerts -`--alert-{neutral,success,warning,error,info}-{background,foreground,foreground-muted,border}` — five variants × four slots, each bridged into Tailwind: +`--alert-{neutral,success,warning,error,info}-{background,foreground,foreground-muted,border}` — five variants × four slots, each bridged into Tailwind. In the default palette they are aliases of the semantic color roles (`error` reads the `danger` role); `foreground-muted` follows `foreground`, so overriding `foreground` changes both: -| Slot | Tailwind | Use for | -| ------------------ | ---------------------------------- | -------------------------- | -| `background` | `bg-alert-info-background` | Callout fill | -| `foreground` | `text-alert-info-foreground` | Primary text and icons | -| `foreground-muted` | `text-alert-info-foreground-muted` | Secondary/description text | -| `border` | `border-alert-info-border` | Callout border | +| Slot | Tailwind | Use for | +| ------------------ | ---------------------------------- | ---------------------- | +| `background` | `bg-alert-info-background` | Callout fill | +| `foreground` | `text-alert-info-foreground` | Primary text and icons | +| `foreground-muted` | `text-alert-info-foreground-muted` | Description text | +| `border` | `border-alert-info-border` | Callout border | Prefer the `Alert` component — it already pairs the four slots per variant and supplies the icon. Reach for the utilities directly only on custom surfaces `Alert` doesn't cover, such as a status-highlighted table row or an inline note: @@ -195,7 +224,7 @@ Prefer the `Alert` component — it already pairs the four slots per variant and ``` -The `background` and `border` slots are already semi-transparent tints (~10% and ~20%), so an opacity modifier compounds rather than replaces — `bg-alert-info-background/50` lands near 5% alpha, not 50%. +Most `background` and `border` values are translucent tints, so an opacity modifier compounds rather than replaces — `bg-alert-info-background/50` halves the tint's alpha instead of setting it to 50%. In light mode the `warning` background and border and the `info` border are opaque, so an opacity modifier does not blend them with a tinted parent. ```tsx // Good — semantic token pairs, and a variant where one exists diff --git a/docs/concepts/custom-components.md b/docs/concepts/custom-components.md index 873b33e3c..230d50837 100644 --- a/docs/concepts/custom-components.md +++ b/docs/concepts/custom-components.md @@ -44,7 +44,7 @@ export function StepIndicator({ steps, current }: Props) { flex items-center gap-2 rounded-md px-3 py-2 bg-muted text-sm text-muted-foreground data-[active]:bg-primary data-[active]:text-primary-foreground - data-[completed]:text-status-completed + data-[completed]:text-semantic-success-text " > {i < current && } diff --git a/docs/concepts/styling-theming.md b/docs/concepts/styling-theming.md index 8cf735cf9..531fd748c 100644 --- a/docs/concepts/styling-theming.md +++ b/docs/concepts/styling-theming.md @@ -11,7 +11,7 @@ Styling is done with **Tailwind CSS v4** against `@tailor-platform/app-shell`'s **The tokens are the rails.** Consistency across customers, apps, and AI runs comes from the token system, not from rules written in prose. A hand-typed `#fff` or `padding: 13px` is not a "small deviation" — it is the mechanism by which consistency dies. -**Every token in this document is verified against the shipped CSS.** If a token is not listed here, assume it does not exist. Inventing a plausible-sounding token (`bg-surface-1`, `text-fg-muted`, `--space-4`) is the worst failure mode available: Tailwind emits **no CSS at all** for an unknown utility, so the class is silently dropped and the element renders unstyled — no error, no warning, nothing in the console. When unsure, read `node_modules/@tailor-platform/app-shell/dist/themes/default.css`; it is the ground truth. +**Every token in this document is verified against the shipped CSS.** If a token is not listed here, assume it does not exist. The hue scales (`--palette-*`) are internal: they are not bridged to Tailwind and are not listed. Inventing a plausible-sounding token (`bg-surface-1`, `text-fg-muted`, `--space-4`) is the worst failure mode available: Tailwind emits **no CSS at all** for an unknown utility, so the class is silently dropped and the element renders unstyled — no error, no warning, nothing in the console. When unsure, read `node_modules/@tailor-platform/app-shell/dist/themes/default.css`; it is the ground truth. ## Setup @@ -92,7 +92,7 @@ Theme tokens live in `packages/core/src/assets/themes/`. Copy `_template.css` to | **2. Shell gradient** | Branded palettes only | `--shell-gradient-base`, `--shell-gradient-tint` | | **3. System** | Tune or copy default | Surfaces: background, card, popover, muted, borders | | **4. Palette** | Optional | Radius, chart colors, shadows | -| **5. Semantic** | Do not duplicate | Status and alert tokens inherit from `default.css` | +| **5. Semantic** | Do not duplicate | Semantic roles, status and alert tokens inherit from `default.css` | | **6. Structural** | Branded palettes | Copy the structural override block from `bloom.css` or `cream.css` when needed | A palette is selected by CSS import, not by an AppShell prop. Import exactly one theme file after `@tailor-platform/app-shell/styles`; if you import none, the default palette from `styles` is used. @@ -175,7 +175,7 @@ There are no `-hover` or `-active` brand tokens. Express interaction states with #### Status -Five status colors, used for badge fills and status dots: +Five status colors, used for badge fills and status dots. They are aliases of the [semantic color roles](#semantic-color-roles) below, so status colors and Alert colors agree by default: | Token | Use | Tailwind | | -------------------- | ------------------------ | --------------------- | @@ -187,6 +187,35 @@ Five status colors, used for badge fills and status dots: Prefer `Badge` with a semantic variant (`success`, `warning`, `error`, `info`, `neutral`) over applying these directly — the variants already pair fill and foreground correctly. Reach for the raw token only on custom surfaces. +These are fill and indicator colors. Do not use them as text color: `--status-completed`, `--status-danger` and `--status-neutral` are below 4.5:1 on a dark card, and `--status-attention` is below 3:1 on a light one. For text, use `text-semantic-{intent}-text`. + +#### Semantic color roles + +Each intent has eight roles. The intents are `info`, `success`, `warning`, `danger` and `neutral`. The hue intents read internal color scales; `neutral` reads the system tokens. Every role is a token named `--semantic-{intent}-{role}` and a Tailwind color named `semantic-{intent}-{role}`: + +| Role | Tailwind | Use for | +| --------------- | -------------------------------- | ----------------------------------- | +| `surface` | `bg-semantic-info-surface` | Soft background | +| `surface-hover` | `bg-semantic-info-surface-hover` | Hover state of a soft background | +| `border` | `border-semantic-info-border` | Border on a soft background | +| `solid` | `bg-semantic-info-solid` | Filled background | +| `solid-hover` | `bg-semantic-info-solid-hover` | Hover state of a filled background | +| `text` | `text-semantic-info-text` | Text and icons on a soft background | +| `on-solid` | `text-semantic-info-on-solid` | Text on a filled background | +| `indicator` | `bg-semantic-info-indicator` | Dots and small marks | + +```tsx + + Paid + +``` + +In the default palette, `text` on `surface` and `on-solid` on `solid` and `solid-hover` are at least 4.5:1 for the four hue intents, in light and dark mode, on `--card` and `--background`. A unit test checks this from the shipped CSS. It does not cover hover surfaces, the `cream` and `bloom` palettes, or the `neutral` intent. Text on `surface-hover` is below 4.5:1 for some roles. + +Some light-mode values are opaque instead of translucent: the `warning` surface, surface-hover and border, the `info` border and the `danger` surface-hover. They do not blend with a tinted parent. All dark-mode values are translucent. + +`Badge` and `Alert` read these roles. `Badge` `error` and `subtle-error`, and `Alert` `error`, follow `--semantic-danger-*`. They no longer follow `--destructive`, which still drives `Button` and `text-destructive`. + #### Sidebar & charts `--sidebar`, `--sidebar-foreground`, `--sidebar-border`, `--sidebar-primary(-foreground)`, `--sidebar-accent(-foreground)`, `--sidebar-ring` → `bg-sidebar`, `text-sidebar-foreground`, and so on. These let a palette tint the shell independently of page content. @@ -195,14 +224,14 @@ Prefer `Badge` with a semantic variant (`success`, `warning`, `error`, `info`, ` #### Alerts -`--alert-{neutral,success,warning,error,info}-{background,foreground,foreground-muted,border}` — five variants × four slots, each bridged into Tailwind: +`--alert-{neutral,success,warning,error,info}-{background,foreground,foreground-muted,border}` — five variants × four slots, each bridged into Tailwind. In the default palette they are aliases of the semantic color roles (`error` reads the `danger` role); `foreground-muted` follows `foreground`, so overriding `foreground` changes both: -| Slot | Tailwind | Use for | -| ------------------ | ---------------------------------- | -------------------------- | -| `background` | `bg-alert-info-background` | Callout fill | -| `foreground` | `text-alert-info-foreground` | Primary text and icons | -| `foreground-muted` | `text-alert-info-foreground-muted` | Secondary/description text | -| `border` | `border-alert-info-border` | Callout border | +| Slot | Tailwind | Use for | +| ------------------ | ---------------------------------- | ---------------------- | +| `background` | `bg-alert-info-background` | Callout fill | +| `foreground` | `text-alert-info-foreground` | Primary text and icons | +| `foreground-muted` | `text-alert-info-foreground-muted` | Description text | +| `border` | `border-alert-info-border` | Callout border | Prefer the `Alert` component — it already pairs the four slots per variant and supplies the icon. Reach for the utilities directly only on custom surfaces `Alert` doesn't cover, such as a status-highlighted table row or an inline note: @@ -212,7 +241,7 @@ Prefer the `Alert` component — it already pairs the four slots per variant and ``` -The `background` and `border` slots are already semi-transparent tints (~10% and ~20%), so an opacity modifier compounds rather than replaces — `bg-alert-info-background/50` lands near 5% alpha, not 50%. +Most `background` and `border` values are translucent tints, so an opacity modifier compounds rather than replaces — `bg-alert-info-background/50` halves the tint's alpha instead of setting it to 50%. In light mode the `warning` background and border and the `info` border are opaque, so an opacity modifier does not blend them with a tinted parent. ```tsx // Good — semantic token pairs, and a variant where one exists From d8832c585213d83ec750566cf01a658be59c8706 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Wed, 30 Sep 2026 09:18:34 +0900 Subject: [PATCH 06/29] docs: add decision record for semantic colour roles Record the context, the role table, the alias rules, the contrast contract (including the pairs that are not guaranteed), the provenance of the hue scales and the consequences for consumers. Links the platform-planning issues. Co-Authored-By: Claude Sonnet 5.5 --- decisions/semantic-color-roles.md | 90 +++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 decisions/semantic-color-roles.md diff --git a/decisions/semantic-color-roles.md b/decisions/semantic-color-roles.md new file mode 100644 index 000000000..ec94f66fb --- /dev/null +++ b/decisions/semantic-color-roles.md @@ -0,0 +1,90 @@ +# Decision: hue scales and semantic colour roles under `--status-*` and `--alert-*` + +> Status: **Decided for the first step. Hue scales and semantic roles are added; `--status-*` and hue `--alert-*` alias the roles; Badge reads the roles.** +> Scope: `packages/core/src/assets/themes/default.css`, the Tailwind bridge, Badge. `--destructive`, the gray scale and theme accent scales are not part of this step. + +## Context + +`--status-*` and `--alert-*` define the same intents (success, warning, error, info) twice, with different values. Neither has a contrast threshold, and some shipped pairs fail WCAG 1.4.3 (white text on the amber warning Badge, for example). Soft backgrounds and borders were built with `color-mix(... 10%, transparent)` per component, with no shared alpha scale. + +Tracking issues (tailor-inc/platform-planning): + +- #1394 alpha scale for soft backgrounds and borders (primary) +- #1891 `--status-*` and `--alert-*` double-define the same intents +- #1892 no contrast thresholds; shipping pairs fail WCAG 1.4.3 +- #1429 status colours are hard to tell apart under colour vision deficiency. This step does not solve it: labels still carry the meaning. + +## Decision + +### Hue scales (internal) + +Four hues, each with 12 steps, 12 alpha steps and a contrast colour, for light (`:root`) and dark (`.dark`): + +`--palette-{blue|green|amber|red}-{1..12}`, `--palette-{hue}-a{1..12}`, `--palette-{hue}-contrast` + +They are not bridged to Tailwind. Consumers use the semantic roles, not steps. The `--palette-` prefix avoids collisions with a consumer's own copy of Radix Colors. The word "palette" also names the theme files (default, cream, bloom) in the docs, so the prefix could be renamed later without touching the roles. + +### Semantic roles (public, bridged) + +`--semantic-{info|success|warning|danger|neutral}-{role}`, bridged as `--color-semantic-*` (5 intents x 8 roles = 40 utilities such as `bg-semantic-info-solid`). + +| role | hue source | neutral source | +| ------------- | ------------------------------- | ----------------------------- | +| surface | a3 | `var(--muted)` | +| surface-hover | a4 | `var(--accent)` | +| border | a6 | `var(--border)` | +| solid | 9 | `var(--secondary)` | +| solid-hover | 10 | `var(--accent)` | +| text | 11 (warning: 12 light, 11 dark) | `var(--foreground)` | +| on-solid | contrast | `var(--secondary-foreground)` | +| indicator | 9 | `var(--muted-foreground)` | + +The full set is bridged, including roles no component uses yet (neutral solid roles, hue `border`), so the set is one coherent vocabulary and `--alert-*` can alias every role. + +The roles and the aliases are declared for `:root, .dark`, so a nested `.dark` wrapper resolves them against its own scales. The only per-mode override is `--semantic-warning-text` (step 12 in light, step 11 in dark). + +### Aliases + +- `--status-default | neutral | completed | attention | danger` alias the neutral indicator and the info, success, warning and danger `solid` roles. +- `--alert-{success,warning,error,info}-{background,border,foreground}` alias `surface`, `border` and `text` (`error` reads the `danger` intent). +- `--alert-{h}-foreground-muted` aliases `--alert-{h}-foreground` at full strength. The first plan used 85% of the text colour; over the surface that is 4.26:1 (info), 3.78:1 (success) and 4.23:1 (danger) in light mode, and no opacity below about 97% passes. Keeping the alias on `--alert-{h}-foreground` also keeps the override chain: a consumer who overrides the foreground gets a matching description. +- `--alert-neutral-*` keeps its values, expressed through the neutral roles. + +### Badge + +Filled, subtle and outline-dot variants read the semantic roles with `astw:` utilities. `default` and `neutral` are unchanged. + +### Contrast contract + +A unit test (`semantic-contrast.test.ts`) computes contrast from `default.css` for light and dark and asserts at least 4.5:1 for: `on-solid` on `solid` and on `solid-hover`; `text` on `surface`; the alert foreground and description on the alert background. The last two are checked over `--card` and `--background`. + +Not guaranteed: + +- Hover surfaces. Light-mode text on `surface-hover` is 4.22:1 (danger, over both pages), 4.34:1 (success over `--background`) and 4.51:1 (success over `--card`). Subtle Badge hover can fall below 4.5:1 for danger and success. +- The `cream` and `bloom` palettes and their tinted shell backgrounds (for example `text` on `surface` over the bloom light shell `#efe8ff` is 4.16:1 for success and 4.15:1 for danger). +- The neutral intent, which keeps the pre-existing system tokens. +- Warning `solid` against the card as a fill: 2.15:1 in light mode. `--status-attention` is unchanged (`#f59e0b`), so `text-status-attention` is still low contrast on light surfaces. CsvImporter now uses the warning text role instead. + +## Provenance + +Seeds: blue `#2563eb` (Tailwind blue-600), green `#15803d` (green-700), amber `#f59e0b` (amber-500), red `#dc2626` (red-600). Each is the lightest Tailwind step with white text at 4.5:1 or more; amber is the documented exception, and its `contrast` colour is pinned to the light step 12 `#4f3515` in both modes (dark text on a bright solid). Scales come from Radix `generateRadixColors` with gray `#737373` and backgrounds `#fafafa` (light) and `#0a0a0a` (dark), generated by `primitive-lab-gen/gen.ts`. The values in `default.css` are copied from its output without edits. + +The "alpha" steps are translucent only where the generator could express them. In light mode, warning a3, a4 and a6, info a6 and danger a4 are opaque 6-digit hex. So the warning surface, surface-hover and border, the info border and the danger surface-hover do not blend with a tinted parent. Dark mode has no opaque values among a3, a4 and a6. Fixing the generator is a later step. + +## Out of scope + +- `--destructive` and `--destructive-foreground` aliasing. Themes override `--destructive`, and it is used as solid, text and border at once, so it needs its own change. Button is untouched. +- Gray scale and base-palette remap, theme accent scales, Theme Generator. +- Bridging hue scales to Tailwind. +- New Badge or Alert variants, icons in Badge. +- Semantic-role swatches on the `/showcase/colors` page. + +## Consequences + +- Names do not change: `bg-status-*`, `bg-alert-*` and `text-destructive` keep working. Values do. +- `--status-*` are now fill and indicator colours. In dark mode they are step 9 values (`#15803d`, `#dc2626`, `#2563eb`), which are below 4.5:1 as text on the dark card (success 3.57:1, danger 3.71:1, info 3.47:1). CsvImporter text and the custom-components example moved to `text-semantic-*-text`. Other consumers that use `text-status-*` in dark mode will see lower contrast. +- Badge `error` and `subtle-error`, and Alert `error`, no longer follow `--destructive`. They use the danger role in both modes. Button and `text-destructive` still follow `--destructive`, so in dark mode a Badge error (`#dc2626`) and a destructive Button (`#f87171`) differ. Consumers who override `--destructive` for Alert or Badge must override `--semantic-danger-*` or `--alert-error-*` instead. +- The warning Badge text is dark (`#4f3515`) instead of white. +- Alert description text is the same colour as the Alert title in the default palette. +- Consumers who override `--status-*` or `--alert-*` keep working; the aliases are only the default values. +- cream, bloom and `_template.css` values are not edited. The header comments now list `--semantic-*` among the inherited tokens. From fb53e9dc9d601b06c008fe05a75b4c5797b8e747 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Wed, 30 Sep 2026 09:18:34 +0900 Subject: [PATCH 07/29] docs(core): list semantic roles among inherited theme tokens Comment-only change in theme.css and _template.css. The inherited-token lists now include --semantic-* next to --status-* and --alert-*. Co-Authored-By: Claude Sonnet 5.5 --- packages/core/src/assets/theme.css | 2 +- packages/core/src/assets/themes/_template.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/assets/theme.css b/packages/core/src/assets/theme.css index e9c3b37c2..b496c5787 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-*, --status-*, --alert-*. + * --ring, --sidebar-*, --semantic-* (colour roles), --status-*, --alert-*. * * Light/dark mode is controlled separately via `.dark` on . * Palette selection is by CSS import; there is no runtime palette prop. diff --git a/packages/core/src/assets/themes/_template.css b/packages/core/src/assets/themes/_template.css index 4ea979ef0..0b0f02810 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-*, --status-*, --alert-* + * --ring, --sidebar-*, --semantic-* (colour roles), --status-*, --alert-* * * PREVIEW: `/showcase/colors` in the Vite example app. * ═══════════════════════════════════════════════════════════════════════════ From 36c72a429a873d704fe36f5d88baf1f0a66bdc01 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Wed, 30 Sep 2026 09:18:46 +0900 Subject: [PATCH 08/29] chore: add changeset for semantic colour roles Minor bump for @tailor-platform/app-shell: new public tokens and visible colour changes to status, alert and Badge colours. Co-Authored-By: Claude Sonnet 5.5 --- .changeset/calm-hues-agree.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .changeset/calm-hues-agree.md diff --git a/.changeset/calm-hues-agree.md b/.changeset/calm-hues-agree.md new file mode 100644 index 000000000..b3e64cc83 --- /dev/null +++ b/.changeset/calm-hues-agree.md @@ -0,0 +1,17 @@ +--- +"@tailor-platform/app-shell": minor +--- + +Add semantic colour roles (`--semantic-{info|success|warning|danger|neutral}-{surface|surface-hover|border|solid|solid-hover|text|on-solid|indicator}`), available as Tailwind colours. `--status-*` and `--alert-*` now alias these roles, and Badge reads them, so `text` on `surface` and `on-solid` on `solid` are at least 4.5:1 in the default palette, in light and dark mode. + +```tsx + + Paid + +``` + +Visible changes in the default palette: + +- `bg-status-*` and `bg-alert-*` keep their names but change values. `--status-*` are fill colours; do not use them as text (below 4.5:1 in dark mode). Use `text-semantic-{intent}-text`. +- Badge `error` and `subtle-error`, and Alert `error`, follow `--semantic-danger-*` instead of `--destructive`. Button and `text-destructive` still follow `--destructive`. +- The warning Badge text is dark instead of white. Alert description text now has the same colour as the Alert title. From 1fa66e12ed8916a5119c285dcc4dd79acec55dab Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Wed, 30 Sep 2026 09:20:41 +0900 Subject: [PATCH 09/29] test(core): hoist the linearize helper in the contrast test Satisfies unicorn/consistent-function-scoping in oxlint. No behaviour change. Co-Authored-By: Claude Sonnet 5.5 --- packages/core/src/assets/semantic-contrast.test.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/core/src/assets/semantic-contrast.test.ts b/packages/core/src/assets/semantic-contrast.test.ts index 5957de443..986479a82 100644 --- a/packages/core/src/assets/semantic-contrast.test.ts +++ b/packages/core/src/assets/semantic-contrast.test.ts @@ -71,12 +71,13 @@ function over(top: Rgba, base: Rgba): Rgba { return { r: mix(top.r, base.r), g: mix(top.g, base.g), b: mix(top.b, base.b), a }; } +function linearize(channel: number): number { + const s = channel / 255; + return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4; +} + function luminance({ r, g, b }: Rgba): number { - const lin = (c: number) => { - const s = c / 255; - return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4; - }; - return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b); + return 0.2126 * linearize(r) + 0.7152 * linearize(g) + 0.0722 * linearize(b); } function contrast(a: Rgba, b: Rgba): number { From d14e1107138c707c7153039e649cfe371611141b Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Wed, 30 Sep 2026 09:32:21 +0900 Subject: [PATCH 10/29] refactor(core): rename hue scales and semantic roles to the plan names Hue scales are now `--primitive-{hue}-*` and the roles are `--{intent}-{role}` with `--color-{intent}-{role}` utilities, as in semantic-pr1-plan.md. The previous `--palette-*` and `--semantic-*` names differed from the plan and made `--semantic-` cover both colour roles and the existing `--semantic-shadow-*` tokens. Co-Authored-By: Claude Opus 5.5 --- .changeset/calm-hues-agree.md | 10 +- decisions/semantic-color-roles.md | 14 +- docs-manifest.json | 12 +- .../custom-components.docs.outline.md | 2 +- .../concepts/styling-theming.docs.outline.md | 32 +- docs/concepts/custom-components.md | 2 +- docs/concepts/styling-theming.md | 32 +- .../core/src/assets/semantic-contrast.test.ts | 2 +- packages/core/src/assets/theme-bridge.test.ts | 7 +- packages/core/src/assets/theme.bridge.css | 80 +-- packages/core/src/assets/themes/default.css | 524 +++++++++--------- .../core/src/components/badge/badge.test.tsx | 9 +- packages/core/src/components/badge/badge.tsx | 26 +- .../components/csv-importer/CsvImporter.tsx | 6 +- 14 files changed, 378 insertions(+), 380 deletions(-) diff --git a/.changeset/calm-hues-agree.md b/.changeset/calm-hues-agree.md index b3e64cc83..0059f343a 100644 --- a/.changeset/calm-hues-agree.md +++ b/.changeset/calm-hues-agree.md @@ -2,16 +2,14 @@ "@tailor-platform/app-shell": minor --- -Add semantic colour roles (`--semantic-{info|success|warning|danger|neutral}-{surface|surface-hover|border|solid|solid-hover|text|on-solid|indicator}`), available as Tailwind colours. `--status-*` and `--alert-*` now alias these roles, and Badge reads them, so `text` on `surface` and `on-solid` on `solid` are at least 4.5:1 in the default palette, in light and dark mode. +Add semantic colour roles (`--{info|success|warning|danger|neutral}-{surface|surface-hover|border|solid|solid-hover|text|on-solid|indicator}`), available as Tailwind colours. `--status-*` and `--alert-*` now alias these roles, and Badge reads them, so `text` on `surface` and `on-solid` on `solid` are at least 4.5:1 in the default palette, in light and dark mode. ```tsx - - Paid - +Paid ``` Visible changes in the default palette: -- `bg-status-*` and `bg-alert-*` keep their names but change values. `--status-*` are fill colours; do not use them as text (below 4.5:1 in dark mode). Use `text-semantic-{intent}-text`. -- Badge `error` and `subtle-error`, and Alert `error`, follow `--semantic-danger-*` instead of `--destructive`. Button and `text-destructive` still follow `--destructive`. +- `bg-status-*` and `bg-alert-*` keep their names but change values. `--status-*` are fill colours; do not use them as text (below 4.5:1 in dark mode). Use `text-{intent}-text`. +- Badge `error` and `subtle-error`, and Alert `error`, follow `--danger-*` instead of `--destructive`. Button and `text-destructive` still follow `--destructive`. - The warning Badge text is dark instead of white. Alert description text now has the same colour as the Alert title. diff --git a/decisions/semantic-color-roles.md b/decisions/semantic-color-roles.md index ec94f66fb..32e09f2b1 100644 --- a/decisions/semantic-color-roles.md +++ b/decisions/semantic-color-roles.md @@ -20,13 +20,13 @@ Tracking issues (tailor-inc/platform-planning): Four hues, each with 12 steps, 12 alpha steps and a contrast colour, for light (`:root`) and dark (`.dark`): -`--palette-{blue|green|amber|red}-{1..12}`, `--palette-{hue}-a{1..12}`, `--palette-{hue}-contrast` +`--primitive-{blue|green|amber|red}-{1..12}`, `--primitive-{hue}-a{1..12}`, `--primitive-{hue}-contrast` -They are not bridged to Tailwind. Consumers use the semantic roles, not steps. The `--palette-` prefix avoids collisions with a consumer's own copy of Radix Colors. The word "palette" also names the theme files (default, cream, bloom) in the docs, so the prefix could be renamed later without touching the roles. +They are not bridged to Tailwind. Consumers use the semantic roles, not steps. The `--primitive-` prefix keeps the two layers readable as primitive then semantic, and avoids collisions with a consumer's own copy of Radix Colors. It is not `--palette-` because "palette" already names the theme files (default, cream, bloom). The role tokens have no prefix. The existing `--semantic-shadow-*` tokens are a different kind of token and are not part of this set. ### Semantic roles (public, bridged) -`--semantic-{info|success|warning|danger|neutral}-{role}`, bridged as `--color-semantic-*` (5 intents x 8 roles = 40 utilities such as `bg-semantic-info-solid`). +`--{info|success|warning|danger|neutral}-{role}`, bridged as `--color-{intent}-{role}` (5 intents x 8 roles = 40 utilities such as `bg-info-solid`). | role | hue source | neutral source | | ------------- | ------------------------------- | ----------------------------- | @@ -41,7 +41,7 @@ They are not bridged to Tailwind. Consumers use the semantic roles, not steps. T The full set is bridged, including roles no component uses yet (neutral solid roles, hue `border`), so the set is one coherent vocabulary and `--alert-*` can alias every role. -The roles and the aliases are declared for `:root, .dark`, so a nested `.dark` wrapper resolves them against its own scales. The only per-mode override is `--semantic-warning-text` (step 12 in light, step 11 in dark). +The roles and the aliases are declared for `:root, .dark`, so a nested `.dark` wrapper resolves them against its own scales. The only per-mode override is `--warning-text` (step 12 in light, step 11 in dark). ### Aliases @@ -82,9 +82,9 @@ The "alpha" steps are translucent only where the generator could express them. I ## Consequences - Names do not change: `bg-status-*`, `bg-alert-*` and `text-destructive` keep working. Values do. -- `--status-*` are now fill and indicator colours. In dark mode they are step 9 values (`#15803d`, `#dc2626`, `#2563eb`), which are below 4.5:1 as text on the dark card (success 3.57:1, danger 3.71:1, info 3.47:1). CsvImporter text and the custom-components example moved to `text-semantic-*-text`. Other consumers that use `text-status-*` in dark mode will see lower contrast. -- Badge `error` and `subtle-error`, and Alert `error`, no longer follow `--destructive`. They use the danger role in both modes. Button and `text-destructive` still follow `--destructive`, so in dark mode a Badge error (`#dc2626`) and a destructive Button (`#f87171`) differ. Consumers who override `--destructive` for Alert or Badge must override `--semantic-danger-*` or `--alert-error-*` instead. +- `--status-*` are now fill and indicator colours. In dark mode they are step 9 values (`#15803d`, `#dc2626`, `#2563eb`), which are below 4.5:1 as text on the dark card (success 3.57:1, danger 3.71:1, info 3.47:1). CsvImporter text and the custom-components example moved to `text-{intent}-text`. Other consumers that use `text-status-*` in dark mode will see lower contrast. +- Badge `error` and `subtle-error`, and Alert `error`, no longer follow `--destructive`. They use the danger role in both modes. Button and `text-destructive` still follow `--destructive`, so in dark mode a Badge error (`#dc2626`) and a destructive Button (`#f87171`) differ. Consumers who override `--destructive` for Alert or Badge must override `--danger-*` or `--alert-error-*` instead. - The warning Badge text is dark (`#4f3515`) instead of white. - Alert description text is the same colour as the Alert title in the default palette. - Consumers who override `--status-*` or `--alert-*` keep working; the aliases are only the default values. -- cream, bloom and `_template.css` values are not edited. The header comments now list `--semantic-*` among the inherited tokens. +- cream, bloom and `_template.css` values are not edited. The header comments now list `--{intent}-{role}` among the inherited tokens. diff --git a/docs-manifest.json b/docs-manifest.json index c69a03c45..45403bcc3 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -372,9 +372,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "9d8ffad15d9b3347", + "outline": "8668ad90672475c7", "snapshot": null, - "outputMd": "88466a0159cc0216", + "outputMd": "18b8fc747d52455a", "examples": null } }, @@ -1216,9 +1216,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "e3d84f4a19a8028f", + "outline": "7800c7acb9be603e", "snapshot": null, - "outputMd": "2715f58f113da4de", + "outputMd": "f4d6ead675a73699", "examples": "d3cca347b055a503" } }, @@ -1582,13 +1582,13 @@ }, "skill": { "packages/core/skills/app-shell-patterns/references/concepts/authentication.md": "ce27a191becaca3a", - "packages/core/skills/app-shell-patterns/references/concepts/custom-components.md": "88466a0159cc0216", + "packages/core/skills/app-shell-patterns/references/concepts/custom-components.md": "18b8fc747d52455a", "packages/core/skills/app-shell-patterns/references/concepts/file-based-routing.md": "caa1ea6264d3b543", "packages/core/skills/app-shell-patterns/references/concepts/graphql.md": "ba5107604e4f42e8", "packages/core/skills/app-shell-patterns/references/concepts/modules-and-resources.md": "bf15b5460f22d165", "packages/core/skills/app-shell-patterns/references/concepts/routing-navigation.md": "2f3ff3f095c2b1cc", "packages/core/skills/app-shell-patterns/references/concepts/sidebar-navigation.md": "96f1cf893c231861", - "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "2715f58f113da4de", + "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "f4d6ead675a73699", "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/custom-components.docs.outline.md b/docs-src/concepts/custom-components.docs.outline.md index d46ec6796..c01340a4a 100644 --- a/docs-src/concepts/custom-components.docs.outline.md +++ b/docs-src/concepts/custom-components.docs.outline.md @@ -44,7 +44,7 @@ export function StepIndicator({ steps, current }: Props) { flex items-center gap-2 rounded-md px-3 py-2 bg-muted text-sm text-muted-foreground data-[active]:bg-primary data-[active]:text-primary-foreground - data-[completed]:text-semantic-success-text + data-[completed]:text-success-text " > {i < current && } diff --git a/docs-src/concepts/styling-theming.docs.outline.md b/docs-src/concepts/styling-theming.docs.outline.md index b542f5cd8..a151a62ba 100644 --- a/docs-src/concepts/styling-theming.docs.outline.md +++ b/docs-src/concepts/styling-theming.docs.outline.md @@ -11,7 +11,7 @@ Styling is done with **Tailwind CSS v4** against `@tailor-platform/app-shell`'s **The tokens are the rails.** Consistency across customers, apps, and AI runs comes from the token system, not from rules written in prose. A hand-typed `#fff` or `padding: 13px` is not a "small deviation" — it is the mechanism by which consistency dies. -**Every token in this document is verified against the shipped CSS.** If a token is not listed here, assume it does not exist. The hue scales (`--palette-*`) are internal: they are not bridged to Tailwind and are not listed. Inventing a plausible-sounding token (`bg-surface-1`, `text-fg-muted`, `--space-4`) is the worst failure mode available: Tailwind emits **no CSS at all** for an unknown utility, so the class is silently dropped and the element renders unstyled — no error, no warning, nothing in the console. When unsure, read `node_modules/@tailor-platform/app-shell/dist/themes/default.css`; it is the ground truth. +**Every token in this document is verified against the shipped CSS.** If a token is not listed here, assume it does not exist. The hue scales (`--primitive-*`) are internal: they are not bridged to Tailwind and are not listed. Inventing a plausible-sounding token (`bg-surface-1`, `text-fg-muted`, `--space-4`) is the worst failure mode available: Tailwind emits **no CSS at all** for an unknown utility, so the class is silently dropped and the element renders unstyled — no error, no warning, nothing in the console. When unsure, read `node_modules/@tailor-platform/app-shell/dist/themes/default.css`; it is the ground truth. ## Setup @@ -170,34 +170,32 @@ Five status colors, used for badge fills and status dots. They are aliases of th Prefer `Badge` with a semantic variant (`success`, `warning`, `error`, `info`, `neutral`) over applying these directly — the variants already pair fill and foreground correctly. Reach for the raw token only on custom surfaces. -These are fill and indicator colors. Do not use them as text color: `--status-completed`, `--status-danger` and `--status-neutral` are below 4.5:1 on a dark card, and `--status-attention` is below 3:1 on a light one. For text, use `text-semantic-{intent}-text`. +These are fill and indicator colors. Do not use them as text color: `--status-completed`, `--status-danger` and `--status-neutral` are below 4.5:1 on a dark card, and `--status-attention` is below 3:1 on a light one. For text, use `text-{intent}-text`. #### Semantic color roles -Each intent has eight roles. The intents are `info`, `success`, `warning`, `danger` and `neutral`. The hue intents read internal color scales; `neutral` reads the system tokens. Every role is a token named `--semantic-{intent}-{role}` and a Tailwind color named `semantic-{intent}-{role}`: +Each intent has eight roles. The intents are `info`, `success`, `warning`, `danger` and `neutral`. The hue intents read internal color scales; `neutral` reads the system tokens. Every role is a token named `--{intent}-{role}` and a Tailwind color named `{intent}-{role}`: -| Role | Tailwind | Use for | -| --------------- | -------------------------------- | ----------------------------------- | -| `surface` | `bg-semantic-info-surface` | Soft background | -| `surface-hover` | `bg-semantic-info-surface-hover` | Hover state of a soft background | -| `border` | `border-semantic-info-border` | Border on a soft background | -| `solid` | `bg-semantic-info-solid` | Filled background | -| `solid-hover` | `bg-semantic-info-solid-hover` | Hover state of a filled background | -| `text` | `text-semantic-info-text` | Text and icons on a soft background | -| `on-solid` | `text-semantic-info-on-solid` | Text on a filled background | -| `indicator` | `bg-semantic-info-indicator` | Dots and small marks | +| Role | Tailwind | Use for | +| --------------- | ----------------------- | ----------------------------------- | +| `surface` | `bg-info-surface` | Soft background | +| `surface-hover` | `bg-info-surface-hover` | Hover state of a soft background | +| `border` | `border-info-border` | Border on a soft background | +| `solid` | `bg-info-solid` | Filled background | +| `solid-hover` | `bg-info-solid-hover` | Hover state of a filled background | +| `text` | `text-info-text` | Text and icons on a soft background | +| `on-solid` | `text-info-on-solid` | Text on a filled background | +| `indicator` | `bg-info-indicator` | Dots and small marks | ```tsx - - Paid - +Paid ``` In the default palette, `text` on `surface` and `on-solid` on `solid` and `solid-hover` are at least 4.5:1 for the four hue intents, in light and dark mode, on `--card` and `--background`. A unit test checks this from the shipped CSS. It does not cover hover surfaces, the `cream` and `bloom` palettes, or the `neutral` intent. Text on `surface-hover` is below 4.5:1 for some roles. Some light-mode values are opaque instead of translucent: the `warning` surface, surface-hover and border, the `info` border and the `danger` surface-hover. They do not blend with a tinted parent. All dark-mode values are translucent. -`Badge` and `Alert` read these roles. `Badge` `error` and `subtle-error`, and `Alert` `error`, follow `--semantic-danger-*`. They no longer follow `--destructive`, which still drives `Button` and `text-destructive`. +`Badge` and `Alert` read these roles. `Badge` `error` and `subtle-error`, and `Alert` `error`, follow `--danger-*`. They no longer follow `--destructive`, which still drives `Button` and `text-destructive`. #### Sidebar & charts diff --git a/docs/concepts/custom-components.md b/docs/concepts/custom-components.md index 230d50837..9ac012935 100644 --- a/docs/concepts/custom-components.md +++ b/docs/concepts/custom-components.md @@ -44,7 +44,7 @@ export function StepIndicator({ steps, current }: Props) { flex items-center gap-2 rounded-md px-3 py-2 bg-muted text-sm text-muted-foreground data-[active]:bg-primary data-[active]:text-primary-foreground - data-[completed]:text-semantic-success-text + data-[completed]:text-success-text " > {i < current && } diff --git a/docs/concepts/styling-theming.md b/docs/concepts/styling-theming.md index 531fd748c..93cad4e02 100644 --- a/docs/concepts/styling-theming.md +++ b/docs/concepts/styling-theming.md @@ -11,7 +11,7 @@ Styling is done with **Tailwind CSS v4** against `@tailor-platform/app-shell`'s **The tokens are the rails.** Consistency across customers, apps, and AI runs comes from the token system, not from rules written in prose. A hand-typed `#fff` or `padding: 13px` is not a "small deviation" — it is the mechanism by which consistency dies. -**Every token in this document is verified against the shipped CSS.** If a token is not listed here, assume it does not exist. The hue scales (`--palette-*`) are internal: they are not bridged to Tailwind and are not listed. Inventing a plausible-sounding token (`bg-surface-1`, `text-fg-muted`, `--space-4`) is the worst failure mode available: Tailwind emits **no CSS at all** for an unknown utility, so the class is silently dropped and the element renders unstyled — no error, no warning, nothing in the console. When unsure, read `node_modules/@tailor-platform/app-shell/dist/themes/default.css`; it is the ground truth. +**Every token in this document is verified against the shipped CSS.** If a token is not listed here, assume it does not exist. The hue scales (`--primitive-*`) are internal: they are not bridged to Tailwind and are not listed. Inventing a plausible-sounding token (`bg-surface-1`, `text-fg-muted`, `--space-4`) is the worst failure mode available: Tailwind emits **no CSS at all** for an unknown utility, so the class is silently dropped and the element renders unstyled — no error, no warning, nothing in the console. When unsure, read `node_modules/@tailor-platform/app-shell/dist/themes/default.css`; it is the ground truth. ## Setup @@ -187,34 +187,32 @@ Five status colors, used for badge fills and status dots. They are aliases of th Prefer `Badge` with a semantic variant (`success`, `warning`, `error`, `info`, `neutral`) over applying these directly — the variants already pair fill and foreground correctly. Reach for the raw token only on custom surfaces. -These are fill and indicator colors. Do not use them as text color: `--status-completed`, `--status-danger` and `--status-neutral` are below 4.5:1 on a dark card, and `--status-attention` is below 3:1 on a light one. For text, use `text-semantic-{intent}-text`. +These are fill and indicator colors. Do not use them as text color: `--status-completed`, `--status-danger` and `--status-neutral` are below 4.5:1 on a dark card, and `--status-attention` is below 3:1 on a light one. For text, use `text-{intent}-text`. #### Semantic color roles -Each intent has eight roles. The intents are `info`, `success`, `warning`, `danger` and `neutral`. The hue intents read internal color scales; `neutral` reads the system tokens. Every role is a token named `--semantic-{intent}-{role}` and a Tailwind color named `semantic-{intent}-{role}`: +Each intent has eight roles. The intents are `info`, `success`, `warning`, `danger` and `neutral`. The hue intents read internal color scales; `neutral` reads the system tokens. Every role is a token named `--{intent}-{role}` and a Tailwind color named `{intent}-{role}`: -| Role | Tailwind | Use for | -| --------------- | -------------------------------- | ----------------------------------- | -| `surface` | `bg-semantic-info-surface` | Soft background | -| `surface-hover` | `bg-semantic-info-surface-hover` | Hover state of a soft background | -| `border` | `border-semantic-info-border` | Border on a soft background | -| `solid` | `bg-semantic-info-solid` | Filled background | -| `solid-hover` | `bg-semantic-info-solid-hover` | Hover state of a filled background | -| `text` | `text-semantic-info-text` | Text and icons on a soft background | -| `on-solid` | `text-semantic-info-on-solid` | Text on a filled background | -| `indicator` | `bg-semantic-info-indicator` | Dots and small marks | +| Role | Tailwind | Use for | +| --------------- | ----------------------- | ----------------------------------- | +| `surface` | `bg-info-surface` | Soft background | +| `surface-hover` | `bg-info-surface-hover` | Hover state of a soft background | +| `border` | `border-info-border` | Border on a soft background | +| `solid` | `bg-info-solid` | Filled background | +| `solid-hover` | `bg-info-solid-hover` | Hover state of a filled background | +| `text` | `text-info-text` | Text and icons on a soft background | +| `on-solid` | `text-info-on-solid` | Text on a filled background | +| `indicator` | `bg-info-indicator` | Dots and small marks | ```tsx - - Paid - +Paid ``` In the default palette, `text` on `surface` and `on-solid` on `solid` and `solid-hover` are at least 4.5:1 for the four hue intents, in light and dark mode, on `--card` and `--background`. A unit test checks this from the shipped CSS. It does not cover hover surfaces, the `cream` and `bloom` palettes, or the `neutral` intent. Text on `surface-hover` is below 4.5:1 for some roles. Some light-mode values are opaque instead of translucent: the `warning` surface, surface-hover and border, the `info` border and the `danger` surface-hover. They do not blend with a tinted parent. All dark-mode values are translucent. -`Badge` and `Alert` read these roles. `Badge` `error` and `subtle-error`, and `Alert` `error`, follow `--semantic-danger-*`. They no longer follow `--destructive`, which still drives `Button` and `text-destructive`. +`Badge` and `Alert` read these roles. `Badge` `error` and `subtle-error`, and `Alert` `error`, follow `--danger-*`. They no longer follow `--destructive`, which still drives `Button` and `text-destructive`. #### Sidebar & charts diff --git a/packages/core/src/assets/semantic-contrast.test.ts b/packages/core/src/assets/semantic-contrast.test.ts index 986479a82..138ca4fcd 100644 --- a/packages/core/src/assets/semantic-contrast.test.ts +++ b/packages/core/src/assets/semantic-contrast.test.ts @@ -106,7 +106,7 @@ describe.each(modes)("semantic colour contrast (%s)", (mode) => { }); describe.each(intents)("%s", (intent) => { - const role = (name: string) => `--semantic-${intent}-${name}`; + const role = (name: string) => `--${intent}-${name}`; it("on-solid text meets 4.5:1 on solid", () => { expect(contrast(color(role("on-solid")), color(role("solid")))).toBeGreaterThanOrEqual( diff --git a/packages/core/src/assets/theme-bridge.test.ts b/packages/core/src/assets/theme-bridge.test.ts index 255154e15..6d49c9d4e 100644 --- a/packages/core/src/assets/theme-bridge.test.ts +++ b/packages/core/src/assets/theme-bridge.test.ts @@ -19,7 +19,7 @@ const defaultTheme = readFileSync(join(here, "themes/default.css"), "utf8"); * `--semantic-shadow-*` (bridged as `--shadow-*`, not `--color-*`) is not * mistaken for a colour role. */ -const SEMANTIC_PREFIX = "semantic-(?:info|success|warning|danger|neutral)"; +const SEMANTIC_PREFIX = "(?:info|success|warning|danger|neutral)"; /** Token names defined in the default palette for a given prefix, e.g. `alert`. */ function definedTokens(prefix: string): string[] { @@ -74,8 +74,7 @@ describe("theme bridge", () => { }); it("does not treat --semantic-shadow-* as a colour role", () => { - expect(definedTokens(SEMANTIC_PREFIX).some((name) => name.startsWith("semantic-shadow"))).toBe( - false, - ); + const roles = [...definedTokens(SEMANTIC_PREFIX), ...bridgedTokens(SEMANTIC_PREFIX).keys()]; + expect(roles.some((name) => name.includes("shadow"))).toBe(false); }); }); diff --git a/packages/core/src/assets/theme.bridge.css b/packages/core/src/assets/theme.bridge.css index 473fae7d6..97df4c706 100644 --- a/packages/core/src/assets/theme.bridge.css +++ b/packages/core/src/assets/theme.bridge.css @@ -100,46 +100,46 @@ --color-alert-info-foreground-muted: var(--alert-info-foreground-muted); --color-alert-info-border: var(--alert-info-border); - --color-semantic-neutral-surface: var(--semantic-neutral-surface); - --color-semantic-neutral-surface-hover: var(--semantic-neutral-surface-hover); - --color-semantic-neutral-border: var(--semantic-neutral-border); - --color-semantic-neutral-solid: var(--semantic-neutral-solid); - --color-semantic-neutral-solid-hover: var(--semantic-neutral-solid-hover); - --color-semantic-neutral-text: var(--semantic-neutral-text); - --color-semantic-neutral-on-solid: var(--semantic-neutral-on-solid); - --color-semantic-neutral-indicator: var(--semantic-neutral-indicator); - --color-semantic-info-surface: var(--semantic-info-surface); - --color-semantic-info-surface-hover: var(--semantic-info-surface-hover); - --color-semantic-info-border: var(--semantic-info-border); - --color-semantic-info-solid: var(--semantic-info-solid); - --color-semantic-info-solid-hover: var(--semantic-info-solid-hover); - --color-semantic-info-text: var(--semantic-info-text); - --color-semantic-info-on-solid: var(--semantic-info-on-solid); - --color-semantic-info-indicator: var(--semantic-info-indicator); - --color-semantic-success-surface: var(--semantic-success-surface); - --color-semantic-success-surface-hover: var(--semantic-success-surface-hover); - --color-semantic-success-border: var(--semantic-success-border); - --color-semantic-success-solid: var(--semantic-success-solid); - --color-semantic-success-solid-hover: var(--semantic-success-solid-hover); - --color-semantic-success-text: var(--semantic-success-text); - --color-semantic-success-on-solid: var(--semantic-success-on-solid); - --color-semantic-success-indicator: var(--semantic-success-indicator); - --color-semantic-warning-surface: var(--semantic-warning-surface); - --color-semantic-warning-surface-hover: var(--semantic-warning-surface-hover); - --color-semantic-warning-border: var(--semantic-warning-border); - --color-semantic-warning-solid: var(--semantic-warning-solid); - --color-semantic-warning-solid-hover: var(--semantic-warning-solid-hover); - --color-semantic-warning-text: var(--semantic-warning-text); - --color-semantic-warning-on-solid: var(--semantic-warning-on-solid); - --color-semantic-warning-indicator: var(--semantic-warning-indicator); - --color-semantic-danger-surface: var(--semantic-danger-surface); - --color-semantic-danger-surface-hover: var(--semantic-danger-surface-hover); - --color-semantic-danger-border: var(--semantic-danger-border); - --color-semantic-danger-solid: var(--semantic-danger-solid); - --color-semantic-danger-solid-hover: var(--semantic-danger-solid-hover); - --color-semantic-danger-text: var(--semantic-danger-text); - --color-semantic-danger-on-solid: var(--semantic-danger-on-solid); - --color-semantic-danger-indicator: var(--semantic-danger-indicator); + --color-neutral-surface: var(--neutral-surface); + --color-neutral-surface-hover: var(--neutral-surface-hover); + --color-neutral-border: var(--neutral-border); + --color-neutral-solid: var(--neutral-solid); + --color-neutral-solid-hover: var(--neutral-solid-hover); + --color-neutral-text: var(--neutral-text); + --color-neutral-on-solid: var(--neutral-on-solid); + --color-neutral-indicator: var(--neutral-indicator); + --color-info-surface: var(--info-surface); + --color-info-surface-hover: var(--info-surface-hover); + --color-info-border: var(--info-border); + --color-info-solid: var(--info-solid); + --color-info-solid-hover: var(--info-solid-hover); + --color-info-text: var(--info-text); + --color-info-on-solid: var(--info-on-solid); + --color-info-indicator: var(--info-indicator); + --color-success-surface: var(--success-surface); + --color-success-surface-hover: var(--success-surface-hover); + --color-success-border: var(--success-border); + --color-success-solid: var(--success-solid); + --color-success-solid-hover: var(--success-solid-hover); + --color-success-text: var(--success-text); + --color-success-on-solid: var(--success-on-solid); + --color-success-indicator: var(--success-indicator); + --color-warning-surface: var(--warning-surface); + --color-warning-surface-hover: var(--warning-surface-hover); + --color-warning-border: var(--warning-border); + --color-warning-solid: var(--warning-solid); + --color-warning-solid-hover: var(--warning-solid-hover); + --color-warning-text: var(--warning-text); + --color-warning-on-solid: var(--warning-on-solid); + --color-warning-indicator: var(--warning-indicator); + --color-danger-surface: var(--danger-surface); + --color-danger-surface-hover: var(--danger-surface-hover); + --color-danger-border: var(--danger-border); + --color-danger-solid: var(--danger-solid); + --color-danger-solid-hover: var(--danger-solid-hover); + --color-danger-text: var(--danger-text); + --color-danger-on-solid: var(--danger-on-solid); + --color-danger-indicator: var(--danger-indicator); --shadow-xs: var(--semantic-shadow-xs); --shadow-sm: var(--semantic-shadow-sm); diff --git a/packages/core/src/assets/themes/default.css b/packages/core/src/assets/themes/default.css index 195928940..67f806ae6 100644 --- a/packages/core/src/assets/themes/default.css +++ b/packages/core/src/assets/themes/default.css @@ -61,109 +61,109 @@ * The "alpha" steps are translucent only where the generator could express * them; some light steps (for example amber a2-a6) resolve to opaque hex. */ - --palette-blue-1: #f7f8f9; - --palette-blue-2: #f0f4fa; - --palette-blue-3: #e5ecf9; - --palette-blue-4: #d6e4fd; - --palette-blue-5: #c5d9ff; - --palette-blue-6: #b0cbff; - --palette-blue-7: #98b9f7; - --palette-blue-8: #769ff0; - --palette-blue-9: #2563eb; - --palette-blue-10: #2057d1; - --palette-blue-11: #1e55cf; - --palette-blue-12: #152e63; - --palette-blue-a1: #3b7bbb04; - --palette-blue-a2: #136ffa0b; - --palette-blue-a3: #0758ef16; - --palette-blue-a4: #bed6ff99; - --palette-blue-a5: #c5d9ff; - --palette-blue-a6: #b0cbff; - --palette-blue-a7: #0155f364; - --palette-blue-a8: #004ee787; - --palette-blue-a9: #014ae9da; - --palette-blue-a10: #0140ccdf; - --palette-blue-a11: #013fcae1; - --palette-blue-a12: #001b55ea; - --palette-blue-contrast: #ffffff; + --primitive-blue-1: #f7f8f9; + --primitive-blue-2: #f0f4fa; + --primitive-blue-3: #e5ecf9; + --primitive-blue-4: #d6e4fd; + --primitive-blue-5: #c5d9ff; + --primitive-blue-6: #b0cbff; + --primitive-blue-7: #98b9f7; + --primitive-blue-8: #769ff0; + --primitive-blue-9: #2563eb; + --primitive-blue-10: #2057d1; + --primitive-blue-11: #1e55cf; + --primitive-blue-12: #152e63; + --primitive-blue-a1: #3b7bbb04; + --primitive-blue-a2: #136ffa0b; + --primitive-blue-a3: #0758ef16; + --primitive-blue-a4: #bed6ff99; + --primitive-blue-a5: #c5d9ff; + --primitive-blue-a6: #b0cbff; + --primitive-blue-a7: #0155f364; + --primitive-blue-a8: #004ee787; + --primitive-blue-a9: #014ae9da; + --primitive-blue-a10: #0140ccdf; + --primitive-blue-a11: #013fcae1; + --primitive-blue-a12: #001b55ea; + --primitive-blue-contrast: #ffffff; - --palette-green-1: #f5f9f6; - --palette-green-2: #eff5f0; - --palette-green-3: #e1f1e3; - --palette-green-4: #d1ead5; - --palette-green-5: #bfe2c5; - --palette-green-6: #a7d7b0; - --palette-green-7: #88c895; - --palette-green-8: #57b36f; - --palette-green-9: #15803d; - --palette-green-10: #00722f; - --palette-green-11: #047936; - --palette-green-12: #1e3c25; - --palette-green-a1: #26d05006; - --palette-green-a2: #1190260c; - --palette-green-a3: #04a1181a; - --palette-green-a4: #02991a2a; - --palette-green-a5: #04961d3d; - --palette-green-a6: #01911c55; - --palette-green-a7: #028e1e75; - --palette-green-a8: #028e26a7; - --palette-green-a9: #00752cea; - --palette-green-a10: #00722f; - --palette-green-a11: #007733fb; - --palette-green-a12: #012309e1; - --palette-green-contrast: #ffffff; + --primitive-green-1: #f5f9f6; + --primitive-green-2: #eff5f0; + --primitive-green-3: #e1f1e3; + --primitive-green-4: #d1ead5; + --primitive-green-5: #bfe2c5; + --primitive-green-6: #a7d7b0; + --primitive-green-7: #88c895; + --primitive-green-8: #57b36f; + --primitive-green-9: #15803d; + --primitive-green-10: #00722f; + --primitive-green-11: #047936; + --primitive-green-12: #1e3c25; + --primitive-green-a1: #26d05006; + --primitive-green-a2: #1190260c; + --primitive-green-a3: #04a1181a; + --primitive-green-a4: #02991a2a; + --primitive-green-a5: #04961d3d; + --primitive-green-a6: #01911c55; + --primitive-green-a7: #028e1e75; + --primitive-green-a8: #028e26a7; + --primitive-green-a9: #00752cea; + --primitive-green-a10: #00722f; + --primitive-green-a11: #007733fb; + --primitive-green-a12: #012309e1; + --primitive-green-contrast: #ffffff; - --palette-amber-1: #f9f7f5; - --palette-amber-2: #fff2e1; - --palette-amber-3: #ffe7bd; - --palette-amber-4: #ffdca5; - --palette-amber-5: #ffd089; - --palette-amber-6: #ffbe5d; - --palette-amber-7: #f8ad4a; - --palette-amber-8: #e99300; - --palette-amber-9: #f59e0b; - --palette-amber-10: #e99300; - --palette-amber-11: #a35f00; - --palette-amber-12: #4f3515; - --palette-amber-a1: #d07b2606; - --palette-amber-a2: #fff2e1; - --palette-amber-a3: #ffe7bd; - --palette-amber-a4: #ffdca5; - --palette-amber-a5: #ffd089; - --palette-amber-a6: #ffbe5d; - --palette-amber-a7: #f78c00b4; - --palette-amber-a8: #e99300; - --palette-amber-a9: #f59a00f4; - --palette-amber-a10: #e99300; - --palette-amber-a11: #a35f00; - --palette-amber-a12: #3f2300ea; - --palette-amber-contrast: #4f3515; + --primitive-amber-1: #f9f7f5; + --primitive-amber-2: #fff2e1; + --primitive-amber-3: #ffe7bd; + --primitive-amber-4: #ffdca5; + --primitive-amber-5: #ffd089; + --primitive-amber-6: #ffbe5d; + --primitive-amber-7: #f8ad4a; + --primitive-amber-8: #e99300; + --primitive-amber-9: #f59e0b; + --primitive-amber-10: #e99300; + --primitive-amber-11: #a35f00; + --primitive-amber-12: #4f3515; + --primitive-amber-a1: #d07b2606; + --primitive-amber-a2: #fff2e1; + --primitive-amber-a3: #ffe7bd; + --primitive-amber-a4: #ffdca5; + --primitive-amber-a5: #ffd089; + --primitive-amber-a6: #ffbe5d; + --primitive-amber-a7: #f78c00b4; + --primitive-amber-a8: #e99300; + --primitive-amber-a9: #f59a00f4; + --primitive-amber-a10: #e99300; + --primitive-amber-a11: #a35f00; + --primitive-amber-a12: #3f2300ea; + --primitive-amber-contrast: #4f3515; - --palette-red-1: #faf7f6; - --palette-red-2: #f9f2f1; - --palette-red-3: #f9e5e2; - --palette-red-4: #ffd4ce; - --palette-red-5: #ffc5bd; - --palette-red-6: #f9b4ab; - --palette-red-7: #f19f95; - --palette-red-8: #e88479; - --palette-red-9: #dc2626; - --palette-red-10: #cc0615; - --palette-red-11: #ce0c17; - --palette-red-12: #641c18; - --palette-red-a1: #fb622f05; - --palette-red-a2: #e12e150a; - --palette-red-a3: #f0240619; - --palette-red-a4: #ffd4ce; - --palette-red-a5: #ffc5bd; - --palette-red-a6: #f71d0151; - --palette-red-a7: #e41b0368; - --palette-red-a8: #d7160084; - --palette-red-a9: #d70101d9; - --palette-red-a10: #cb000ff9; - --palette-red-a11: #cc000cf3; - --palette-red-a12: #540400e7; - --palette-red-contrast: #ffffff; + --primitive-red-1: #faf7f6; + --primitive-red-2: #f9f2f1; + --primitive-red-3: #f9e5e2; + --primitive-red-4: #ffd4ce; + --primitive-red-5: #ffc5bd; + --primitive-red-6: #f9b4ab; + --primitive-red-7: #f19f95; + --primitive-red-8: #e88479; + --primitive-red-9: #dc2626; + --primitive-red-10: #cc0615; + --primitive-red-11: #ce0c17; + --primitive-red-12: #641c18; + --primitive-red-a1: #fb622f05; + --primitive-red-a2: #e12e150a; + --primitive-red-a3: #f0240619; + --primitive-red-a4: #ffd4ce; + --primitive-red-a5: #ffc5bd; + --primitive-red-a6: #f71d0151; + --primitive-red-a7: #e41b0368; + --primitive-red-a8: #d7160084; + --primitive-red-a9: #d70101d9; + --primitive-red-a10: #cb000ff9; + --primitive-red-a11: #cc000cf3; + --primitive-red-a12: #540400e7; + --primitive-red-contrast: #ffffff; } /* @@ -173,79 +173,79 @@ * the roles against its own hue scales. The values are identical in both * selectors; only the hue scales differ per mode. * - * Roles: `--semantic-{info|success|warning|danger|neutral}-{surface|surface-hover| + * Roles: `--{info|success|warning|danger|neutral}-{surface|surface-hover| * border|solid|solid-hover|text|on-solid|indicator}`. Hue intents read the hue * scales; `neutral` reads the system tokens. `--status-*` and `--alert-*` below * are aliases of these roles, so both families agree by construction. */ :root, .dark { - --semantic-neutral-surface: var(--muted); - --semantic-neutral-surface-hover: var(--accent); - --semantic-neutral-border: var(--border); - --semantic-neutral-solid: var(--secondary); - --semantic-neutral-solid-hover: var(--accent); - --semantic-neutral-text: var(--foreground); - --semantic-neutral-on-solid: var(--secondary-foreground); - --semantic-neutral-indicator: var(--muted-foreground); - --semantic-info-surface: var(--palette-blue-a3); - --semantic-info-surface-hover: var(--palette-blue-a4); - --semantic-info-border: var(--palette-blue-a6); - --semantic-info-solid: var(--palette-blue-9); - --semantic-info-solid-hover: var(--palette-blue-10); - --semantic-info-text: var(--palette-blue-11); - --semantic-info-on-solid: var(--palette-blue-contrast); - --semantic-info-indicator: var(--palette-blue-9); - --semantic-success-surface: var(--palette-green-a3); - --semantic-success-surface-hover: var(--palette-green-a4); - --semantic-success-border: var(--palette-green-a6); - --semantic-success-solid: var(--palette-green-9); - --semantic-success-solid-hover: var(--palette-green-10); - --semantic-success-text: var(--palette-green-11); - --semantic-success-on-solid: var(--palette-green-contrast); - --semantic-success-indicator: var(--palette-green-9); - --semantic-warning-surface: var(--palette-amber-a3); - --semantic-warning-surface-hover: var(--palette-amber-a4); - --semantic-warning-border: var(--palette-amber-a6); - --semantic-warning-solid: var(--palette-amber-9); - --semantic-warning-solid-hover: var(--palette-amber-10); - --semantic-warning-text: var(--palette-amber-12); - --semantic-warning-on-solid: var(--palette-amber-contrast); - --semantic-warning-indicator: var(--palette-amber-9); - --semantic-danger-surface: var(--palette-red-a3); - --semantic-danger-surface-hover: var(--palette-red-a4); - --semantic-danger-border: var(--palette-red-a6); - --semantic-danger-solid: var(--palette-red-9); - --semantic-danger-solid-hover: var(--palette-red-10); - --semantic-danger-text: var(--palette-red-11); - --semantic-danger-on-solid: var(--palette-red-contrast); - --semantic-danger-indicator: var(--palette-red-9); + --neutral-surface: var(--muted); + --neutral-surface-hover: var(--accent); + --neutral-border: var(--border); + --neutral-solid: var(--secondary); + --neutral-solid-hover: var(--accent); + --neutral-text: var(--foreground); + --neutral-on-solid: var(--secondary-foreground); + --neutral-indicator: var(--muted-foreground); + --info-surface: var(--primitive-blue-a3); + --info-surface-hover: var(--primitive-blue-a4); + --info-border: var(--primitive-blue-a6); + --info-solid: var(--primitive-blue-9); + --info-solid-hover: var(--primitive-blue-10); + --info-text: var(--primitive-blue-11); + --info-on-solid: var(--primitive-blue-contrast); + --info-indicator: var(--primitive-blue-9); + --success-surface: var(--primitive-green-a3); + --success-surface-hover: var(--primitive-green-a4); + --success-border: var(--primitive-green-a6); + --success-solid: var(--primitive-green-9); + --success-solid-hover: var(--primitive-green-10); + --success-text: var(--primitive-green-11); + --success-on-solid: var(--primitive-green-contrast); + --success-indicator: var(--primitive-green-9); + --warning-surface: var(--primitive-amber-a3); + --warning-surface-hover: var(--primitive-amber-a4); + --warning-border: var(--primitive-amber-a6); + --warning-solid: var(--primitive-amber-9); + --warning-solid-hover: var(--primitive-amber-10); + --warning-text: var(--primitive-amber-12); + --warning-on-solid: var(--primitive-amber-contrast); + --warning-indicator: var(--primitive-amber-9); + --danger-surface: var(--primitive-red-a3); + --danger-surface-hover: var(--primitive-red-a4); + --danger-border: var(--primitive-red-a6); + --danger-solid: var(--primitive-red-9); + --danger-solid-hover: var(--primitive-red-10); + --danger-text: var(--primitive-red-11); + --danger-on-solid: var(--primitive-red-contrast); + --danger-indicator: var(--primitive-red-9); - --status-default: var(--semantic-neutral-indicator); - --status-neutral: var(--semantic-info-solid); - --status-completed: var(--semantic-success-solid); - --status-attention: var(--semantic-warning-solid); - --status-danger: var(--semantic-danger-solid); - --alert-neutral-background: var(--semantic-neutral-surface); - --alert-neutral-foreground: var(--semantic-neutral-text); + --status-default: var(--neutral-indicator); + --status-neutral: var(--info-solid); + --status-completed: var(--success-solid); + --status-attention: var(--warning-solid); + --status-danger: var(--danger-solid); + --alert-neutral-background: var(--neutral-surface); + --alert-neutral-foreground: var(--neutral-text); --alert-neutral-foreground-muted: var(--muted-foreground); - --alert-neutral-border: var(--semantic-neutral-border); - --alert-success-background: var(--semantic-success-surface); - --alert-success-foreground: var(--semantic-success-text); + --alert-neutral-border: var(--neutral-border); + --alert-success-background: var(--success-surface); + --alert-success-foreground: var(--success-text); --alert-success-foreground-muted: var(--alert-success-foreground); - --alert-success-border: var(--semantic-success-border); - --alert-warning-background: var(--semantic-warning-surface); - --alert-warning-foreground: var(--semantic-warning-text); + --alert-success-border: var(--success-border); + --alert-warning-background: var(--warning-surface); + --alert-warning-foreground: var(--warning-text); --alert-warning-foreground-muted: var(--alert-warning-foreground); - --alert-warning-border: var(--semantic-warning-border); - --alert-error-background: var(--semantic-danger-surface); - --alert-error-foreground: var(--semantic-danger-text); + --alert-warning-border: var(--warning-border); + --alert-error-background: var(--danger-surface); + --alert-error-foreground: var(--danger-text); --alert-error-foreground-muted: var(--alert-error-foreground); - --alert-error-border: var(--semantic-danger-border); - --alert-info-background: var(--semantic-info-surface); - --alert-info-foreground: var(--semantic-info-text); + --alert-error-border: var(--danger-border); + --alert-info-background: var(--info-surface); + --alert-info-foreground: var(--info-text); --alert-info-foreground-muted: var(--alert-info-foreground); - --alert-info-border: var(--semantic-info-border); + --alert-info-border: var(--info-border); } /* @@ -294,110 +294,110 @@ --semantic-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.42), 0 4px 6px -4px rgb(0 0 0 / 0.35); /* ── 6. HUE SCALES (dark) ─────────────────────────────────────────────── */ - --palette-blue-1: #050a16; - --palette-blue-2: #0e1626; - --palette-blue-3: #11244c; - --palette-blue-4: #132e66; - --palette-blue-5: #1a397a; - --palette-blue-6: #22448b; - --palette-blue-7: #2b509f; - --palette-blue-8: #325ebb; - --palette-blue-9: #2563eb; - --palette-blue-10: #315dba; - --palette-blue-11: #88b5ff; - --palette-blue-12: #d1e2ff; - --palette-blue-a1: #000af60d; - --palette-blue-a2: #2c70f81e; - --palette-blue-a3: #246bfe45; - --palette-blue-a4: #226aff60; - --palette-blue-a5: #2d71ff75; - --palette-blue-a6: #3777fe87; - --palette-blue-a7: #407dfe9c; - --palette-blue-a8: #417efeb9; - --palette-blue-a9: #276afeeb; - --palette-blue-a10: #407dfeb8; - --palette-blue-a11: #88b5ff; - --palette-blue-a12: #d1e2ff; - --palette-blue-contrast: #ffffff; + --primitive-blue-1: #050a16; + --primitive-blue-2: #0e1626; + --primitive-blue-3: #11244c; + --primitive-blue-4: #132e66; + --primitive-blue-5: #1a397a; + --primitive-blue-6: #22448b; + --primitive-blue-7: #2b509f; + --primitive-blue-8: #325ebb; + --primitive-blue-9: #2563eb; + --primitive-blue-10: #315dba; + --primitive-blue-11: #88b5ff; + --primitive-blue-12: #d1e2ff; + --primitive-blue-a1: #000af60d; + --primitive-blue-a2: #2c70f81e; + --primitive-blue-a3: #246bfe45; + --primitive-blue-a4: #226aff60; + --primitive-blue-a5: #2d71ff75; + --primitive-blue-a6: #3777fe87; + --primitive-blue-a7: #407dfe9c; + --primitive-blue-a8: #417efeb9; + --primitive-blue-a9: #276afeeb; + --primitive-blue-a10: #407dfeb8; + --primitive-blue-a11: #88b5ff; + --primitive-blue-a12: #d1e2ff; + --primitive-blue-contrast: #ffffff; - --palette-green-1: #050c06; - --palette-green-2: #101a12; - --palette-green-3: #152b1a; - --palette-green-4: #0f3c1d; - --palette-green-5: #124b24; - --palette-green-6: #155b2c; - --palette-green-7: #166c34; - --palette-green-8: #137f3c; - --palette-green-9: #15803d; - --palette-green-10: #1b6f37; - --palette-green-11: #71d088; - --palette-green-12: #acf7bb; - --palette-green-a1: #00b40003; - --palette-green-a2: #64fa8211; - --palette-green-a3: #5bfb7f23; - --palette-green-a4: #23fb6635; - --palette-green-a5: #28fe6c44; - --palette-green-a6: #2bfd7055; - --palette-green-a7: #28ff7366; - --palette-green-a8: #1dff737a; - --palette-green-a9: #21ff747b; - --palette-green-a10: #33fd776a; - --palette-green-a11: #89fea5cf; - --palette-green-a12: #b2ffc1f7; - --palette-green-contrast: #ffffff; + --primitive-green-1: #050c06; + --primitive-green-2: #101a12; + --primitive-green-3: #152b1a; + --primitive-green-4: #0f3c1d; + --primitive-green-5: #124b24; + --primitive-green-6: #155b2c; + --primitive-green-7: #166c34; + --primitive-green-8: #137f3c; + --primitive-green-9: #15803d; + --primitive-green-10: #1b6f37; + --primitive-green-11: #71d088; + --primitive-green-12: #acf7bb; + --primitive-green-a1: #00b40003; + --primitive-green-a2: #64fa8211; + --primitive-green-a3: #5bfb7f23; + --primitive-green-a4: #23fb6635; + --primitive-green-a5: #28fe6c44; + --primitive-green-a6: #2bfd7055; + --primitive-green-a7: #28ff7366; + --primitive-green-a8: #1dff737a; + --primitive-green-a9: #21ff747b; + --primitive-green-a10: #33fd776a; + --primitive-green-a11: #89fea5cf; + --primitive-green-a12: #b2ffc1f7; + --primitive-green-contrast: #ffffff; - --palette-amber-1: #0d0906; - --palette-amber-2: #1c150e; - --palette-amber-3: #2f1f0c; - --palette-amber-4: #412500; - --palette-amber-5: #4e2e00; - --palette-amber-6: #5d3c0f; - --palette-amber-7: #724d1e; - --palette-amber-8: #926426; - --palette-amber-9: #f59e0b; - --palette-amber-10: #e99300; - --palette-amber-11: #fbb050; - --palette-amber-12: #fde2c4; - --palette-amber-a1: #ca000004; - --palette-amber-a2: #fc9e4013; - --palette-amber-a3: #fc941827; - --palette-amber-a4: #fc81003a; - --palette-amber-a5: #ff8c0047; - --palette-amber-a6: #fd9c1857; - --palette-amber-a7: #fda7396d; - --palette-amber-a8: #ffac3d8e; - --palette-amber-a9: #ffa40bf5; - --palette-amber-a10: #fea000e9; - --palette-amber-a11: #ffb351fb; - --palette-amber-a12: #ffe4c6fd; - --palette-amber-contrast: #4f3515; + --primitive-amber-1: #0d0906; + --primitive-amber-2: #1c150e; + --primitive-amber-3: #2f1f0c; + --primitive-amber-4: #412500; + --primitive-amber-5: #4e2e00; + --primitive-amber-6: #5d3c0f; + --primitive-amber-7: #724d1e; + --primitive-amber-8: #926426; + --primitive-amber-9: #f59e0b; + --primitive-amber-10: #e99300; + --primitive-amber-11: #fbb050; + --primitive-amber-12: #fde2c4; + --primitive-amber-a1: #ca000004; + --primitive-amber-a2: #fc9e4013; + --primitive-amber-a3: #fc941827; + --primitive-amber-a4: #fc81003a; + --primitive-amber-a5: #ff8c0047; + --primitive-amber-a6: #fd9c1857; + --primitive-amber-a7: #fda7396d; + --primitive-amber-a8: #ffac3d8e; + --primitive-amber-a9: #ffa40bf5; + --primitive-amber-a10: #fea000e9; + --primitive-amber-a11: #ffb351fb; + --primitive-amber-a12: #ffe4c6fd; + --primitive-amber-contrast: #4f3515; - --palette-red-1: #100807; - --palette-red-2: #1f1210; - --palette-red-3: #3c110e; - --palette-red-4: #530b0a; - --palette-red-5: #641210; - --palette-red-6: #76201c; - --palette-red-7: #8f312a; - --palette-red-8: #b94038; - --palette-red-9: #dc2626; - --palette-red-10: #b63d36; - --palette-red-11: #ff9083; - --palette-red-12: #ffd1ca; - --palette-red-a1: #e5000007; - --palette-red-a2: #fe675016; - --palette-red-a3: #fb2c1e35; - --palette-red-a4: #ff0e0a4c; - --palette-red-a5: #ff201b5e; - --palette-red-a6: #fd3b3271; - --palette-red-a7: #fe51448b; - --palette-red-a8: #fe554ab7; - --palette-red-a9: #ff2b2bdb; - --palette-red-a10: #fe5248b4; - --palette-red-a11: #ff9083; - --palette-red-a12: #ffd1ca; - --palette-red-contrast: #ffffff; + --primitive-red-1: #100807; + --primitive-red-2: #1f1210; + --primitive-red-3: #3c110e; + --primitive-red-4: #530b0a; + --primitive-red-5: #641210; + --primitive-red-6: #76201c; + --primitive-red-7: #8f312a; + --primitive-red-8: #b94038; + --primitive-red-9: #dc2626; + --primitive-red-10: #b63d36; + --primitive-red-11: #ff9083; + --primitive-red-12: #ffd1ca; + --primitive-red-a1: #e5000007; + --primitive-red-a2: #fe675016; + --primitive-red-a3: #fb2c1e35; + --primitive-red-a4: #ff0e0a4c; + --primitive-red-a5: #ff201b5e; + --primitive-red-a6: #fd3b3271; + --primitive-red-a7: #fe51448b; + --primitive-red-a8: #fe554ab7; + --primitive-red-a9: #ff2b2bdb; + --primitive-red-a10: #fe5248b4; + --primitive-red-a11: #ff9083; + --primitive-red-a12: #ffd1ca; + --primitive-red-contrast: #ffffff; /* Warning text: step 12 on the light surface, step 11 on the dark one. */ - --semantic-warning-text: var(--palette-amber-11); + --warning-text: var(--primitive-amber-11); } diff --git a/packages/core/src/components/badge/badge.test.tsx b/packages/core/src/components/badge/badge.test.tsx index 0a0578016..73de97159 100644 --- a/packages/core/src/components/badge/badge.test.tsx +++ b/packages/core/src/components/badge/badge.test.tsx @@ -5,7 +5,7 @@ import { describe, expect, it } from "vitest"; import { badgeVariants } from "./badge"; /** - * Badge is precompiled with `astw:` utilities. A `bg-semantic-*` class whose + * Badge is precompiled with `astw:` utilities. A `bg-{intent}-{role}` class whose * token is missing from the Tailwind bridge emits no CSS and fails silently, * so check every semantic utility a variant uses against `theme.bridge.css`. */ @@ -29,7 +29,12 @@ describe("badgeVariants semantic colour classes", () => { it.each(variants)("%s uses only bridged semantic tokens", (variant) => { const classes = badgeVariants({ variant }).split(/\s+/); const tokens = classes - .map((c) => /^astw:(?:hover:)?(?:bg|text)-(semantic-[a-z-]+)$/.exec(c)?.[1]) + .map( + (c) => + /^astw:(?:hover:)?(?:bg|text)-((?:info|success|warning|danger|neutral)-[a-z-]+)$/.exec( + c, + )?.[1], + ) .filter((t): t is string => t !== undefined); expect(tokens.length).toBeGreaterThanOrEqual(3); diff --git a/packages/core/src/components/badge/badge.tsx b/packages/core/src/components/badge/badge.tsx index 52ec48640..90d3763a5 100644 --- a/packages/core/src/components/badge/badge.tsx +++ b/packages/core/src/components/badge/badge.tsx @@ -11,22 +11,22 @@ const badgeVariants = cva( default: "astw:border-transparent astw:bg-primary astw:text-primary-foreground astw:hover:bg-primary/80", success: - "astw:border-transparent astw:bg-semantic-success-solid astw:text-semantic-success-on-solid astw:hover:bg-semantic-success-solid-hover", + "astw:border-transparent astw:bg-success-solid astw:text-success-on-solid astw:hover:bg-success-solid-hover", warning: - "astw:border-transparent astw:bg-semantic-warning-solid astw:text-semantic-warning-on-solid astw:hover:bg-semantic-warning-solid-hover", + "astw:border-transparent astw:bg-warning-solid astw:text-warning-on-solid astw:hover:bg-warning-solid-hover", error: - "astw:border-transparent astw:bg-semantic-danger-solid astw:text-semantic-danger-on-solid astw:hover:bg-semantic-danger-solid-hover", + "astw:border-transparent astw:bg-danger-solid astw:text-danger-on-solid astw:hover:bg-danger-solid-hover", neutral: "astw:border-transparent astw:bg-secondary astw:text-secondary-foreground astw:hover:bg-secondary/80", - info: "astw:border-transparent astw:bg-semantic-info-solid astw:text-semantic-info-on-solid astw:hover:bg-semantic-info-solid-hover", + info: "astw:border-transparent astw:bg-info-solid astw:text-info-on-solid astw:hover:bg-info-solid-hover", "subtle-success": - "astw:border-transparent astw:bg-semantic-success-surface astw:text-semantic-success-text astw:hover:bg-semantic-success-surface-hover", + "astw:border-transparent astw:bg-success-surface astw:text-success-text astw:hover:bg-success-surface-hover", "subtle-warning": - "astw:border-transparent astw:bg-semantic-warning-surface astw:text-semantic-warning-text astw:hover:bg-semantic-warning-surface-hover", + "astw:border-transparent astw:bg-warning-surface astw:text-warning-text astw:hover:bg-warning-surface-hover", "subtle-error": - "astw:border-transparent astw:bg-semantic-danger-surface astw:text-semantic-danger-text astw:hover:bg-semantic-danger-surface-hover", + "astw:border-transparent astw:bg-danger-surface astw:text-danger-text astw:hover:bg-danger-surface-hover", "subtle-info": - "astw:border-transparent astw:bg-semantic-info-surface astw:text-semantic-info-text astw:hover:bg-semantic-info-surface-hover", + "astw:border-transparent astw:bg-info-surface astw:text-info-text astw:hover:bg-info-surface-hover", // Outline variants with status dots - matches Figma design "outline-success": "astw:gap-0.5 astw:pl-1.5 astw:pr-2 astw:border-border astw:bg-card astw:text-foreground", @@ -48,11 +48,11 @@ const badgeVariants = cva( // Status dot colors for outline variants const statusDotColors: Record = { - "outline-success": "astw:bg-semantic-success-indicator", - "outline-warning": "astw:bg-semantic-warning-indicator", - "outline-error": "astw:bg-semantic-danger-indicator", - "outline-info": "astw:bg-semantic-info-indicator", - "outline-neutral": "astw:bg-semantic-neutral-indicator", + "outline-success": "astw:bg-success-indicator", + "outline-warning": "astw:bg-warning-indicator", + "outline-error": "astw:bg-danger-indicator", + "outline-info": "astw:bg-info-indicator", + "outline-neutral": "astw:bg-neutral-indicator", }; function StatusDot({ variant }: { variant: string }) { diff --git a/packages/core/src/components/csv-importer/CsvImporter.tsx b/packages/core/src/components/csv-importer/CsvImporter.tsx index dc786593a..346c4a2c6 100644 --- a/packages/core/src/components/csv-importer/CsvImporter.tsx +++ b/packages/core/src/components/csv-importer/CsvImporter.tsx @@ -364,10 +364,10 @@ function ReviewStep({ Total: {rawRows.length} rows {errorCount > 0 && Errors: {errorCount}} {warningCount > 0 && ( - Warnings: {warningCount} + Warnings: {warningCount} )} {validated && errorCount === 0 && !validating && ( - + {t("reviewNoErrors")} @@ -438,7 +438,7 @@ function ReviewStep({ "astw:text-xs astw:px-2", issue.level === "error" ? "astw:text-destructive" - : "astw:text-semantic-warning-text", + : "astw:text-warning-text", )} > {issue.message} From 75f6f1ab7a96bfbbe6d15dda8a58912134999e26 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Wed, 30 Sep 2026 09:32:37 +0900 Subject: [PATCH 11/29] fix(examples): use the success text role in the spinner showcase text-status-completed is a fill colour and is 3.57:1 on the dark card. Co-Authored-By: Claude Opus 5.5 --- examples/vite-app/src/pages/showcase/spinner/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/vite-app/src/pages/showcase/spinner/page.tsx b/examples/vite-app/src/pages/showcase/spinner/page.tsx index 34d366bac..cd5b0d831 100644 --- a/examples/vite-app/src/pages/showcase/spinner/page.tsx +++ b/examples/vite-app/src/pages/showcase/spinner/page.tsx @@ -51,7 +51,7 @@ const SpinnerPage = () => {
-
+
From 1494c822d1798be4ef64ee9d1457d9ab41133f69 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Wed, 30 Sep 2026 09:32:38 +0900 Subject: [PATCH 12/29] test(core): hold subtle hover text at a documented contrast floor Text on surface-hover is below 4.5:1 for danger and success in light mode. The test now fails if it drops below 4.2:1, so it cannot get worse unnoticed. Steps are not changed. Co-Authored-By: Claude Opus 5.5 --- .../core/src/assets/semantic-contrast.test.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/core/src/assets/semantic-contrast.test.ts b/packages/core/src/assets/semantic-contrast.test.ts index 138ca4fcd..f19ac3bd5 100644 --- a/packages/core/src/assets/semantic-contrast.test.ts +++ b/packages/core/src/assets/semantic-contrast.test.ts @@ -10,8 +10,10 @@ import { describe, expect, it } from "vitest"; * * Scope of the guarantee: the default state of each pair, on the default * palette's `--card` and `--background`. Not covered on purpose: - * - hover states (`surface-hover`): subtle-badge hover text is below 4.5:1 for - * some roles, see decisions/semantic-color-roles.md; + * - hover states (`surface-hover`) against 4.5:1: subtle-badge hover text is + * below 4.5:1 for some roles (danger and success in light mode). They are held + * at a documented floor (MIN_HOVER_TEXT) so they cannot get worse unnoticed, + * see decisions/semantic-color-roles.md; * - branded palettes (cream, bloom) and their tinted shell backgrounds; * - the neutral intent, whose values are the pre-existing system tokens; * - the warning `solid` fill against the card (a fill, not a text pair). @@ -86,6 +88,8 @@ function contrast(a: Rgba, b: Rgba): number { } const MIN_TEXT = 4.5; +/** Known and accepted: subtle hover text is below AA for some roles. A floor, not a pass. */ +const MIN_HOVER_TEXT = 4.2; const modes: Mode[] = ["light", "dark"]; const intents = ["info", "success", "warning", "danger"] as const; const alertVariantFor = { info: "info", success: "success", warning: "warning", danger: "error" }; @@ -124,6 +128,15 @@ describe.each(modes)("semantic colour contrast (%s)", (mode) => { expect(pair(role("text"), role("surface"), page)).toBeGreaterThanOrEqual(MIN_TEXT); }); + it.each(pages)( + "text stays above the documented hover floor on surface-hover over %s", + (page) => { + expect(pair(role("text"), role("surface-hover"), page)).toBeGreaterThanOrEqual( + MIN_HOVER_TEXT, + ); + }, + ); + const variant = alertVariantFor[intent]; it.each(pages)("alert foreground meets 4.5:1 on alert background over %s", (page) => { From c564b02a4099292d3014c3edb7572725a9764305 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Wed, 30 Sep 2026 09:32:41 +0900 Subject: [PATCH 13/29] docs: state that Badge reads the roles and not --status-* The decision record claimed that overriding --status-* keeps working. That holds for bg-status-* and Alert, not for Badge. Also add the text-status-* migration to the changeset. Co-Authored-By: Claude Opus 5.5 --- .changeset/calm-hues-agree.md | 3 ++- decisions/semantic-color-roles.md | 4 ++-- docs-manifest.json | 6 +++--- docs-src/concepts/styling-theming.docs.outline.md | 2 +- docs/concepts/styling-theming.md | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.changeset/calm-hues-agree.md b/.changeset/calm-hues-agree.md index 0059f343a..a1c6a0f23 100644 --- a/.changeset/calm-hues-agree.md +++ b/.changeset/calm-hues-agree.md @@ -10,6 +10,7 @@ Add semantic colour roles (`--{info|success|warning|danger|neutral}-{surface|sur Visible changes in the default palette: -- `bg-status-*` and `bg-alert-*` keep their names but change values. `--status-*` are fill colours; do not use them as text (below 4.5:1 in dark mode). Use `text-{intent}-text`. +- `bg-status-*` and `bg-alert-*` keep their names but change values. `--status-*` are fill colours; do not use them as text (below 4.5:1 in dark mode). Use `text-{intent}-text` instead (for example `text-status-completed` becomes `text-success-text`). +- Badge reads the semantic roles instead of `--status-*`. To recolour Badge, override `--{intent}-*`; overriding `--status-*` no longer changes Badge. - Badge `error` and `subtle-error`, and Alert `error`, follow `--danger-*` instead of `--destructive`. Button and `text-destructive` still follow `--destructive`. - The warning Badge text is dark instead of white. Alert description text now has the same colour as the Alert title. diff --git a/decisions/semantic-color-roles.md b/decisions/semantic-color-roles.md index 32e09f2b1..eef88bdd2 100644 --- a/decisions/semantic-color-roles.md +++ b/decisions/semantic-color-roles.md @@ -60,7 +60,7 @@ A unit test (`semantic-contrast.test.ts`) computes contrast from `default.css` f Not guaranteed: -- Hover surfaces. Light-mode text on `surface-hover` is 4.22:1 (danger, over both pages), 4.34:1 (success over `--background`) and 4.51:1 (success over `--card`). Subtle Badge hover can fall below 4.5:1 for danger and success. +- Hover surfaces. Light-mode text on `surface-hover` is 4.22:1 (danger, over both pages), 4.34:1 (success over `--background`) and 4.51:1 (success over `--card`). Subtle Badge hover can fall below 4.5:1 for danger and success. The test holds `text` on `surface-hover` at a floor of 4.2:1 so it cannot get worse unnoticed. This is a floor, not a pass. Changing the step is a design decision for a later change. - The `cream` and `bloom` palettes and their tinted shell backgrounds (for example `text` on `surface` over the bloom light shell `#efe8ff` is 4.16:1 for success and 4.15:1 for danger). - The neutral intent, which keeps the pre-existing system tokens. - Warning `solid` against the card as a fill: 2.15:1 in light mode. `--status-attention` is unchanged (`#f59e0b`), so `text-status-attention` is still low contrast on light surfaces. CsvImporter now uses the warning text role instead. @@ -86,5 +86,5 @@ The "alpha" steps are translucent only where the generator could express them. I - Badge `error` and `subtle-error`, and Alert `error`, no longer follow `--destructive`. They use the danger role in both modes. Button and `text-destructive` still follow `--destructive`, so in dark mode a Badge error (`#dc2626`) and a destructive Button (`#f87171`) differ. Consumers who override `--destructive` for Alert or Badge must override `--danger-*` or `--alert-error-*` instead. - The warning Badge text is dark (`#4f3515`) instead of white. - Alert description text is the same colour as the Alert title in the default palette. -- Consumers who override `--status-*` or `--alert-*` keep working; the aliases are only the default values. +- Consumers who override `--status-*` or `--alert-*` keep working for `bg-status-*`, `bg-alert-*` and Alert, which read those tokens. Badge reads the roles, not `--status-*`. To recolour Badge, override the roles (`--{intent}-*`); overriding `--status-*` no longer changes Badge. - cream, bloom and `_template.css` values are not edited. The header comments now list `--{intent}-{role}` among the inherited tokens. diff --git a/docs-manifest.json b/docs-manifest.json index 45403bcc3..583b7ad39 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -1216,9 +1216,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "7800c7acb9be603e", + "outline": "c519bc15f0c7230f", "snapshot": null, - "outputMd": "f4d6ead675a73699", + "outputMd": "596e027370ee1894", "examples": "d3cca347b055a503" } }, @@ -1588,7 +1588,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": "2f3ff3f095c2b1cc", "packages/core/skills/app-shell-patterns/references/concepts/sidebar-navigation.md": "96f1cf893c231861", - "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "f4d6ead675a73699", + "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "596e027370ee1894", "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 a151a62ba..5c4cd8b26 100644 --- a/docs-src/concepts/styling-theming.docs.outline.md +++ b/docs-src/concepts/styling-theming.docs.outline.md @@ -158,7 +158,7 @@ There are no `-hover` or `-active` brand tokens. Express interaction states with #### Status -Five status colors, used for badge fills and status dots. They are aliases of the [semantic color roles](#semantic-color-roles) below, so status colors and Alert colors agree by default: +Five status colors for custom surfaces. They are aliases of the [semantic color roles](#semantic-color-roles) below, so status colors and Alert colors agree by default: | Token | Use | Tailwind | | -------------------- | ------------------------ | --------------------- | diff --git a/docs/concepts/styling-theming.md b/docs/concepts/styling-theming.md index 93cad4e02..fd6bc5dda 100644 --- a/docs/concepts/styling-theming.md +++ b/docs/concepts/styling-theming.md @@ -175,7 +175,7 @@ There are no `-hover` or `-active` brand tokens. Express interaction states with #### Status -Five status colors, used for badge fills and status dots. They are aliases of the [semantic color roles](#semantic-color-roles) below, so status colors and Alert colors agree by default: +Five status colors for custom surfaces. They are aliases of the [semantic color roles](#semantic-color-roles) below, so status colors and Alert colors agree by default: | Token | Use | Tailwind | | -------------------- | ------------------------ | --------------------- | From 2b00bd8a5c7c05946497670dd297c0f98e1022c3 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Wed, 30 Sep 2026 10:07:14 +0900 Subject: [PATCH 14/29] refactor(core): rename the on-solid role to contrast Match the primitive scales' own -contrast step and the Radix and Chakra name for text on a filled background. Also fix the palette header comments and drop a shadow test that no longer guards anything after the --semantic- prefix was removed. Co-Authored-By: Claude Opus 5.5 --- .changeset/calm-hues-agree.md | 2 +- decisions/semantic-color-roles.md | 4 ++-- docs-manifest.json | 6 +++--- docs-src/concepts/styling-theming.docs.outline.md | 4 ++-- docs/concepts/styling-theming.md | 4 ++-- packages/core/src/assets/semantic-contrast.test.ts | 8 ++++---- packages/core/src/assets/theme-bridge.test.ts | 11 +---------- packages/core/src/assets/theme.bridge.css | 10 +++++----- packages/core/src/assets/theme.css | 2 +- packages/core/src/assets/themes/_template.css | 2 +- packages/core/src/assets/themes/default.css | 12 ++++++------ packages/core/src/components/badge/badge.tsx | 8 ++++---- 12 files changed, 32 insertions(+), 41 deletions(-) diff --git a/.changeset/calm-hues-agree.md b/.changeset/calm-hues-agree.md index a1c6a0f23..21a310165 100644 --- a/.changeset/calm-hues-agree.md +++ b/.changeset/calm-hues-agree.md @@ -2,7 +2,7 @@ "@tailor-platform/app-shell": minor --- -Add semantic colour roles (`--{info|success|warning|danger|neutral}-{surface|surface-hover|border|solid|solid-hover|text|on-solid|indicator}`), available as Tailwind colours. `--status-*` and `--alert-*` now alias these roles, and Badge reads them, so `text` on `surface` and `on-solid` on `solid` are at least 4.5:1 in the default palette, in light and dark mode. +Add semantic colour roles (`--{info|success|warning|danger|neutral}-{surface|surface-hover|border|solid|solid-hover|text|contrast|indicator}`), available as Tailwind colours. `--status-*` and `--alert-*` now alias these roles, and Badge reads them, so `text` on `surface` and `contrast` on `solid` are at least 4.5:1 in the default palette, in light and dark mode. ```tsx Paid diff --git a/decisions/semantic-color-roles.md b/decisions/semantic-color-roles.md index eef88bdd2..dd3f41852 100644 --- a/decisions/semantic-color-roles.md +++ b/decisions/semantic-color-roles.md @@ -36,7 +36,7 @@ They are not bridged to Tailwind. Consumers use the semantic roles, not steps. T | solid | 9 | `var(--secondary)` | | solid-hover | 10 | `var(--accent)` | | text | 11 (warning: 12 light, 11 dark) | `var(--foreground)` | -| on-solid | contrast | `var(--secondary-foreground)` | +| contrast | contrast | `var(--secondary-foreground)` | | indicator | 9 | `var(--muted-foreground)` | The full set is bridged, including roles no component uses yet (neutral solid roles, hue `border`), so the set is one coherent vocabulary and `--alert-*` can alias every role. @@ -56,7 +56,7 @@ Filled, subtle and outline-dot variants read the semantic roles with `astw:` uti ### Contrast contract -A unit test (`semantic-contrast.test.ts`) computes contrast from `default.css` for light and dark and asserts at least 4.5:1 for: `on-solid` on `solid` and on `solid-hover`; `text` on `surface`; the alert foreground and description on the alert background. The last two are checked over `--card` and `--background`. +A unit test (`semantic-contrast.test.ts`) computes contrast from `default.css` for light and dark and asserts at least 4.5:1 for: `contrast` on `solid` and on `solid-hover`; `text` on `surface`; the alert foreground and description on the alert background. The last two are checked over `--card` and `--background`. Not guaranteed: diff --git a/docs-manifest.json b/docs-manifest.json index 583b7ad39..17944d97b 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -1216,9 +1216,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "c519bc15f0c7230f", + "outline": "8fab8ad00c3dcdf8", "snapshot": null, - "outputMd": "596e027370ee1894", + "outputMd": "7d59c6ae26c6d6f9", "examples": "d3cca347b055a503" } }, @@ -1588,7 +1588,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": "2f3ff3f095c2b1cc", "packages/core/skills/app-shell-patterns/references/concepts/sidebar-navigation.md": "96f1cf893c231861", - "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "596e027370ee1894", + "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "7d59c6ae26c6d6f9", "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 5c4cd8b26..7e2302fa1 100644 --- a/docs-src/concepts/styling-theming.docs.outline.md +++ b/docs-src/concepts/styling-theming.docs.outline.md @@ -184,14 +184,14 @@ Each intent has eight roles. The intents are `info`, `success`, `warning`, `dang | `solid` | `bg-info-solid` | Filled background | | `solid-hover` | `bg-info-solid-hover` | Hover state of a filled background | | `text` | `text-info-text` | Text and icons on a soft background | -| `on-solid` | `text-info-on-solid` | Text on a filled background | +| `contrast` | `text-info-contrast` | Text on a filled background | | `indicator` | `bg-info-indicator` | Dots and small marks | ```tsx Paid ``` -In the default palette, `text` on `surface` and `on-solid` on `solid` and `solid-hover` are at least 4.5:1 for the four hue intents, in light and dark mode, on `--card` and `--background`. A unit test checks this from the shipped CSS. It does not cover hover surfaces, the `cream` and `bloom` palettes, or the `neutral` intent. Text on `surface-hover` is below 4.5:1 for some roles. +In the default palette, `text` on `surface` and `contrast` on `solid` and `solid-hover` are at least 4.5:1 for the four hue intents, in light and dark mode, on `--card` and `--background`. A unit test checks this from the shipped CSS. It does not cover hover surfaces, the `cream` and `bloom` palettes, or the `neutral` intent. Text on `surface-hover` is below 4.5:1 for some roles. Some light-mode values are opaque instead of translucent: the `warning` surface, surface-hover and border, the `info` border and the `danger` surface-hover. They do not blend with a tinted parent. All dark-mode values are translucent. diff --git a/docs/concepts/styling-theming.md b/docs/concepts/styling-theming.md index fd6bc5dda..7f5b9b864 100644 --- a/docs/concepts/styling-theming.md +++ b/docs/concepts/styling-theming.md @@ -201,14 +201,14 @@ Each intent has eight roles. The intents are `info`, `success`, `warning`, `dang | `solid` | `bg-info-solid` | Filled background | | `solid-hover` | `bg-info-solid-hover` | Hover state of a filled background | | `text` | `text-info-text` | Text and icons on a soft background | -| `on-solid` | `text-info-on-solid` | Text on a filled background | +| `contrast` | `text-info-contrast` | Text on a filled background | | `indicator` | `bg-info-indicator` | Dots and small marks | ```tsx Paid ``` -In the default palette, `text` on `surface` and `on-solid` on `solid` and `solid-hover` are at least 4.5:1 for the four hue intents, in light and dark mode, on `--card` and `--background`. A unit test checks this from the shipped CSS. It does not cover hover surfaces, the `cream` and `bloom` palettes, or the `neutral` intent. Text on `surface-hover` is below 4.5:1 for some roles. +In the default palette, `text` on `surface` and `contrast` on `solid` and `solid-hover` are at least 4.5:1 for the four hue intents, in light and dark mode, on `--card` and `--background`. A unit test checks this from the shipped CSS. It does not cover hover surfaces, the `cream` and `bloom` palettes, or the `neutral` intent. Text on `surface-hover` is below 4.5:1 for some roles. Some light-mode values are opaque instead of translucent: the `warning` surface, surface-hover and border, the `info` border and the `danger` surface-hover. They do not blend with a tinted parent. All dark-mode values are translucent. diff --git a/packages/core/src/assets/semantic-contrast.test.ts b/packages/core/src/assets/semantic-contrast.test.ts index f19ac3bd5..61974dd16 100644 --- a/packages/core/src/assets/semantic-contrast.test.ts +++ b/packages/core/src/assets/semantic-contrast.test.ts @@ -112,14 +112,14 @@ describe.each(modes)("semantic colour contrast (%s)", (mode) => { describe.each(intents)("%s", (intent) => { const role = (name: string) => `--${intent}-${name}`; - it("on-solid text meets 4.5:1 on solid", () => { - expect(contrast(color(role("on-solid")), color(role("solid")))).toBeGreaterThanOrEqual( + it("the contrast role meets 4.5:1 on solid", () => { + expect(contrast(color(role("contrast")), color(role("solid")))).toBeGreaterThanOrEqual( MIN_TEXT, ); }); - it("on-solid text meets 4.5:1 on solid-hover", () => { - expect(contrast(color(role("on-solid")), color(role("solid-hover")))).toBeGreaterThanOrEqual( + it("the contrast role meets 4.5:1 on solid-hover", () => { + expect(contrast(color(role("contrast")), color(role("solid-hover")))).toBeGreaterThanOrEqual( MIN_TEXT, ); }); diff --git a/packages/core/src/assets/theme-bridge.test.ts b/packages/core/src/assets/theme-bridge.test.ts index 6d49c9d4e..084b4e356 100644 --- a/packages/core/src/assets/theme-bridge.test.ts +++ b/packages/core/src/assets/theme-bridge.test.ts @@ -14,11 +14,7 @@ const here = dirname(fileURLToPath(import.meta.url)); const bridge = readFileSync(join(here, "theme.bridge.css"), "utf8"); const defaultTheme = readFileSync(join(here, "themes/default.css"), "utf8"); -/** - * Semantic colour roles: 5 intents x 8 roles. The intent list is explicit so - * `--semantic-shadow-*` (bridged as `--shadow-*`, not `--color-*`) is not - * mistaken for a colour role. - */ +/** Semantic colour roles: 5 intents x 8 roles, named `--{intent}-{role}`. */ const SEMANTIC_PREFIX = "(?:info|success|warning|danger|neutral)"; /** Token names defined in the default palette for a given prefix, e.g. `alert`. */ @@ -72,9 +68,4 @@ describe("theme bridge", () => { // 5 intents x 8 roles, pinned for the same reason as the alert count. expect(bridgedTokens(SEMANTIC_PREFIX).size).toBe(40); }); - - it("does not treat --semantic-shadow-* as a colour role", () => { - const roles = [...definedTokens(SEMANTIC_PREFIX), ...bridgedTokens(SEMANTIC_PREFIX).keys()]; - expect(roles.some((name) => name.includes("shadow"))).toBe(false); - }); }); diff --git a/packages/core/src/assets/theme.bridge.css b/packages/core/src/assets/theme.bridge.css index 97df4c706..5500ba076 100644 --- a/packages/core/src/assets/theme.bridge.css +++ b/packages/core/src/assets/theme.bridge.css @@ -106,7 +106,7 @@ --color-neutral-solid: var(--neutral-solid); --color-neutral-solid-hover: var(--neutral-solid-hover); --color-neutral-text: var(--neutral-text); - --color-neutral-on-solid: var(--neutral-on-solid); + --color-neutral-contrast: var(--neutral-contrast); --color-neutral-indicator: var(--neutral-indicator); --color-info-surface: var(--info-surface); --color-info-surface-hover: var(--info-surface-hover); @@ -114,7 +114,7 @@ --color-info-solid: var(--info-solid); --color-info-solid-hover: var(--info-solid-hover); --color-info-text: var(--info-text); - --color-info-on-solid: var(--info-on-solid); + --color-info-contrast: var(--info-contrast); --color-info-indicator: var(--info-indicator); --color-success-surface: var(--success-surface); --color-success-surface-hover: var(--success-surface-hover); @@ -122,7 +122,7 @@ --color-success-solid: var(--success-solid); --color-success-solid-hover: var(--success-solid-hover); --color-success-text: var(--success-text); - --color-success-on-solid: var(--success-on-solid); + --color-success-contrast: var(--success-contrast); --color-success-indicator: var(--success-indicator); --color-warning-surface: var(--warning-surface); --color-warning-surface-hover: var(--warning-surface-hover); @@ -130,7 +130,7 @@ --color-warning-solid: var(--warning-solid); --color-warning-solid-hover: var(--warning-solid-hover); --color-warning-text: var(--warning-text); - --color-warning-on-solid: var(--warning-on-solid); + --color-warning-contrast: var(--warning-contrast); --color-warning-indicator: var(--warning-indicator); --color-danger-surface: var(--danger-surface); --color-danger-surface-hover: var(--danger-surface-hover); @@ -138,7 +138,7 @@ --color-danger-solid: var(--danger-solid); --color-danger-solid-hover: var(--danger-solid-hover); --color-danger-text: var(--danger-text); - --color-danger-on-solid: var(--danger-on-solid); + --color-danger-contrast: var(--danger-contrast); --color-danger-indicator: var(--danger-indicator); --shadow-xs: var(--semantic-shadow-xs); diff --git a/packages/core/src/assets/theme.css b/packages/core/src/assets/theme.css index b496c5787..f0238e8fd 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-*, --semantic-* (colour roles), --status-*, --alert-*. + * --ring, --sidebar-*, --{info|success|warning|danger|neutral}-* (colour roles), --status-*, --alert-*. * * Light/dark mode is controlled separately via `.dark` on . * Palette selection is by CSS import; there is no runtime palette prop. diff --git a/packages/core/src/assets/themes/_template.css b/packages/core/src/assets/themes/_template.css index 0b0f02810..fd7e0e9a8 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-*, --semantic-* (colour roles), --status-*, --alert-* + * --ring, --sidebar-*, --{info|success|warning|danger|neutral}-* (colour roles), --status-*, --alert-* * * PREVIEW: `/showcase/colors` in the Vite example app. * ═══════════════════════════════════════════════════════════════════════════ diff --git a/packages/core/src/assets/themes/default.css b/packages/core/src/assets/themes/default.css index 67f806ae6..7c205ba1d 100644 --- a/packages/core/src/assets/themes/default.css +++ b/packages/core/src/assets/themes/default.css @@ -174,7 +174,7 @@ * selectors; only the hue scales differ per mode. * * Roles: `--{info|success|warning|danger|neutral}-{surface|surface-hover| - * border|solid|solid-hover|text|on-solid|indicator}`. Hue intents read the hue + * border|solid|solid-hover|text|contrast|indicator}`. Hue intents read the hue * scales; `neutral` reads the system tokens. `--status-*` and `--alert-*` below * are aliases of these roles, so both families agree by construction. */ @@ -186,7 +186,7 @@ --neutral-solid: var(--secondary); --neutral-solid-hover: var(--accent); --neutral-text: var(--foreground); - --neutral-on-solid: var(--secondary-foreground); + --neutral-contrast: var(--secondary-foreground); --neutral-indicator: var(--muted-foreground); --info-surface: var(--primitive-blue-a3); --info-surface-hover: var(--primitive-blue-a4); @@ -194,7 +194,7 @@ --info-solid: var(--primitive-blue-9); --info-solid-hover: var(--primitive-blue-10); --info-text: var(--primitive-blue-11); - --info-on-solid: var(--primitive-blue-contrast); + --info-contrast: var(--primitive-blue-contrast); --info-indicator: var(--primitive-blue-9); --success-surface: var(--primitive-green-a3); --success-surface-hover: var(--primitive-green-a4); @@ -202,7 +202,7 @@ --success-solid: var(--primitive-green-9); --success-solid-hover: var(--primitive-green-10); --success-text: var(--primitive-green-11); - --success-on-solid: var(--primitive-green-contrast); + --success-contrast: var(--primitive-green-contrast); --success-indicator: var(--primitive-green-9); --warning-surface: var(--primitive-amber-a3); --warning-surface-hover: var(--primitive-amber-a4); @@ -210,7 +210,7 @@ --warning-solid: var(--primitive-amber-9); --warning-solid-hover: var(--primitive-amber-10); --warning-text: var(--primitive-amber-12); - --warning-on-solid: var(--primitive-amber-contrast); + --warning-contrast: var(--primitive-amber-contrast); --warning-indicator: var(--primitive-amber-9); --danger-surface: var(--primitive-red-a3); --danger-surface-hover: var(--primitive-red-a4); @@ -218,7 +218,7 @@ --danger-solid: var(--primitive-red-9); --danger-solid-hover: var(--primitive-red-10); --danger-text: var(--primitive-red-11); - --danger-on-solid: var(--primitive-red-contrast); + --danger-contrast: var(--primitive-red-contrast); --danger-indicator: var(--primitive-red-9); --status-default: var(--neutral-indicator); diff --git a/packages/core/src/components/badge/badge.tsx b/packages/core/src/components/badge/badge.tsx index 90d3763a5..5aefe2b94 100644 --- a/packages/core/src/components/badge/badge.tsx +++ b/packages/core/src/components/badge/badge.tsx @@ -11,14 +11,14 @@ const badgeVariants = cva( default: "astw:border-transparent astw:bg-primary astw:text-primary-foreground astw:hover:bg-primary/80", success: - "astw:border-transparent astw:bg-success-solid astw:text-success-on-solid astw:hover:bg-success-solid-hover", + "astw:border-transparent astw:bg-success-solid astw:text-success-contrast astw:hover:bg-success-solid-hover", warning: - "astw:border-transparent astw:bg-warning-solid astw:text-warning-on-solid astw:hover:bg-warning-solid-hover", + "astw:border-transparent astw:bg-warning-solid astw:text-warning-contrast astw:hover:bg-warning-solid-hover", error: - "astw:border-transparent astw:bg-danger-solid astw:text-danger-on-solid astw:hover:bg-danger-solid-hover", + "astw:border-transparent astw:bg-danger-solid astw:text-danger-contrast astw:hover:bg-danger-solid-hover", neutral: "astw:border-transparent astw:bg-secondary astw:text-secondary-foreground astw:hover:bg-secondary/80", - info: "astw:border-transparent astw:bg-info-solid astw:text-info-on-solid astw:hover:bg-info-solid-hover", + info: "astw:border-transparent astw:bg-info-solid astw:text-info-contrast astw:hover:bg-info-solid-hover", "subtle-success": "astw:border-transparent astw:bg-success-surface astw:text-success-text astw:hover:bg-success-surface-hover", "subtle-warning": From 2a2411b2aadb26dc4815f8a40ddeb6de1f9b4673 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Thu, 1 Oct 2026 09:32:34 +0900 Subject: [PATCH 15/29] refactor(core): read the semantic colour roles in Alert and CsvImporter Components now read --{intent}-{role} directly. --status-* and --alert-* stay as aliases for consumer code and are no longer used by shipped components. Co-Authored-By: Claude Fable 5.1 --- .changeset/calm-hues-agree.md | 2 +- decisions/semantic-color-roles.md | 4 ++-- docs-manifest.json | 6 +++--- docs-src/concepts/styling-theming.docs.outline.md | 6 +++--- docs/concepts/styling-theming.md | 6 +++--- .../src__components__alert__alert.test.tsx.snap | 12 ++++++------ packages/core/src/components/alert/alert.tsx | 15 +++++++-------- .../src/components/csv-importer/CsvImporter.tsx | 4 ++-- 8 files changed, 27 insertions(+), 28 deletions(-) diff --git a/.changeset/calm-hues-agree.md b/.changeset/calm-hues-agree.md index 21a310165..328630c0b 100644 --- a/.changeset/calm-hues-agree.md +++ b/.changeset/calm-hues-agree.md @@ -11,6 +11,6 @@ Add semantic colour roles (`--{info|success|warning|danger|neutral}-{surface|sur Visible changes in the default palette: - `bg-status-*` and `bg-alert-*` keep their names but change values. `--status-*` are fill colours; do not use them as text (below 4.5:1 in dark mode). Use `text-{intent}-text` instead (for example `text-status-completed` becomes `text-success-text`). -- Badge reads the semantic roles instead of `--status-*`. To recolour Badge, override `--{intent}-*`; overriding `--status-*` no longer changes Badge. +- Badge, Alert and CsvImporter read the semantic roles instead of `--status-*` and `--alert-*`. To recolour them, override `--{intent}-*`; overriding `--status-*` or `--alert-*` no longer changes these components. The old names remain for your own `bg-status-*` / `bg-alert-*` utilities. - Badge `error` and `subtle-error`, and Alert `error`, follow `--danger-*` instead of `--destructive`. Button and `text-destructive` still follow `--destructive`. - The warning Badge text is dark instead of white. Alert description text now has the same colour as the Alert title. diff --git a/decisions/semantic-color-roles.md b/decisions/semantic-color-roles.md index dd3f41852..93d1db629 100644 --- a/decisions/semantic-color-roles.md +++ b/decisions/semantic-color-roles.md @@ -1,6 +1,6 @@ # Decision: hue scales and semantic colour roles under `--status-*` and `--alert-*` -> Status: **Decided for the first step. Hue scales and semantic roles are added; `--status-*` and hue `--alert-*` alias the roles; Badge reads the roles.** +> Status: **Decided for the first step. Hue scales and semantic roles are added; `--status-*` and hue `--alert-*` alias the roles; Badge, Alert and CsvImporter read the roles.** > Scope: `packages/core/src/assets/themes/default.css`, the Tailwind bridge, Badge. `--destructive`, the gray scale and theme accent scales are not part of this step. ## Context @@ -86,5 +86,5 @@ The "alpha" steps are translucent only where the generator could express them. I - Badge `error` and `subtle-error`, and Alert `error`, no longer follow `--destructive`. They use the danger role in both modes. Button and `text-destructive` still follow `--destructive`, so in dark mode a Badge error (`#dc2626`) and a destructive Button (`#f87171`) differ. Consumers who override `--destructive` for Alert or Badge must override `--danger-*` or `--alert-error-*` instead. - The warning Badge text is dark (`#4f3515`) instead of white. - Alert description text is the same colour as the Alert title in the default palette. -- Consumers who override `--status-*` or `--alert-*` keep working for `bg-status-*`, `bg-alert-*` and Alert, which read those tokens. Badge reads the roles, not `--status-*`. To recolour Badge, override the roles (`--{intent}-*`); overriding `--status-*` no longer changes Badge. +- 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) 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. diff --git a/docs-manifest.json b/docs-manifest.json index 17944d97b..1cf7c197b 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -1216,9 +1216,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "8fab8ad00c3dcdf8", + "outline": "a2abf4b3f0acb282", "snapshot": null, - "outputMd": "7d59c6ae26c6d6f9", + "outputMd": "942a4f3aac1b8b72", "examples": "d3cca347b055a503" } }, @@ -1588,7 +1588,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": "2f3ff3f095c2b1cc", "packages/core/skills/app-shell-patterns/references/concepts/sidebar-navigation.md": "96f1cf893c231861", - "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "7d59c6ae26c6d6f9", + "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "942a4f3aac1b8b72", "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 7e2302fa1..732f0a591 100644 --- a/docs-src/concepts/styling-theming.docs.outline.md +++ b/docs-src/concepts/styling-theming.docs.outline.md @@ -195,7 +195,7 @@ In the default palette, `text` on `surface` and `contrast` on `solid` and `solid Some light-mode values are opaque instead of translucent: the `warning` surface, surface-hover and border, the `info` border and the `danger` surface-hover. They do not blend with a tinted parent. All dark-mode values are translucent. -`Badge` and `Alert` read these roles. `Badge` `error` and `subtle-error`, and `Alert` `error`, follow `--danger-*`. They no longer follow `--destructive`, which still drives `Button` and `text-destructive`. +`Badge`, `Alert` and `CsvImporter` read these roles directly, not `--status-*` or `--alert-*`. To recolour them, override the roles. `Badge` `error` and `subtle-error`, and `Alert` `error`, follow `--danger-*`. They no longer follow `--destructive`, which still drives `Button` and `text-destructive`. #### Sidebar & charts @@ -205,7 +205,7 @@ Some light-mode values are opaque instead of translucent: the `warning` surface, #### Alerts -`--alert-{neutral,success,warning,error,info}-{background,foreground,foreground-muted,border}` — five variants × four slots, each bridged into Tailwind. In the default palette they are aliases of the semantic color roles (`error` reads the `danger` role); `foreground-muted` follows `foreground`, so overriding `foreground` changes both: +`--alert-{neutral,success,warning,error,info}-{background,foreground,foreground-muted,border}` — five variants × four slots, each bridged into Tailwind. In the default palette they are aliases of the semantic color roles (`error` reads the `danger` role); `foreground-muted` follows `foreground`, so overriding `foreground` changes both. They are kept for custom surfaces in consumer code; the `Alert` component reads the roles, so overriding `--alert-*` changes these utilities but not `Alert`: | Slot | Tailwind | Use for | | ------------------ | ---------------------------------- | ---------------------- | @@ -214,7 +214,7 @@ Some light-mode values are opaque instead of translucent: the `warning` surface, | `foreground-muted` | `text-alert-info-foreground-muted` | Description text | | `border` | `border-alert-info-border` | Callout border | -Prefer the `Alert` component — it already pairs the four slots per variant and supplies the icon. Reach for the utilities directly only on custom surfaces `Alert` doesn't cover, such as a status-highlighted table row or an inline note: +Prefer the `Alert` component — it already pairs surface, border and text per variant and supplies the icon. Reach for the utilities directly only on custom surfaces `Alert` doesn't cover, such as a status-highlighted table row or an inline note: ```tsx
diff --git a/docs/concepts/styling-theming.md b/docs/concepts/styling-theming.md index 7f5b9b864..2a4af87a8 100644 --- a/docs/concepts/styling-theming.md +++ b/docs/concepts/styling-theming.md @@ -212,7 +212,7 @@ In the default palette, `text` on `surface` and `contrast` on `solid` and `solid Some light-mode values are opaque instead of translucent: the `warning` surface, surface-hover and border, the `info` border and the `danger` surface-hover. They do not blend with a tinted parent. All dark-mode values are translucent. -`Badge` and `Alert` read these roles. `Badge` `error` and `subtle-error`, and `Alert` `error`, follow `--danger-*`. They no longer follow `--destructive`, which still drives `Button` and `text-destructive`. +`Badge`, `Alert` and `CsvImporter` read these roles directly, not `--status-*` or `--alert-*`. To recolour them, override the roles. `Badge` `error` and `subtle-error`, and `Alert` `error`, follow `--danger-*`. They no longer follow `--destructive`, which still drives `Button` and `text-destructive`. #### Sidebar & charts @@ -222,7 +222,7 @@ Some light-mode values are opaque instead of translucent: the `warning` surface, #### Alerts -`--alert-{neutral,success,warning,error,info}-{background,foreground,foreground-muted,border}` — five variants × four slots, each bridged into Tailwind. In the default palette they are aliases of the semantic color roles (`error` reads the `danger` role); `foreground-muted` follows `foreground`, so overriding `foreground` changes both: +`--alert-{neutral,success,warning,error,info}-{background,foreground,foreground-muted,border}` — five variants × four slots, each bridged into Tailwind. In the default palette they are aliases of the semantic color roles (`error` reads the `danger` role); `foreground-muted` follows `foreground`, so overriding `foreground` changes both. They are kept for custom surfaces in consumer code; the `Alert` component reads the roles, so overriding `--alert-*` changes these utilities but not `Alert`: | Slot | Tailwind | Use for | | ------------------ | ---------------------------------- | ---------------------- | @@ -231,7 +231,7 @@ Some light-mode values are opaque instead of translucent: the `warning` surface, | `foreground-muted` | `text-alert-info-foreground-muted` | Description text | | `border` | `border-alert-info-border` | Callout border | -Prefer the `Alert` component — it already pairs the four slots per variant and supplies the icon. Reach for the utilities directly only on custom surfaces `Alert` doesn't cover, such as a status-highlighted table row or an inline note: +Prefer the `Alert` component — it already pairs surface, border and text per variant and supplies the icon. Reach for the utilities directly only on custom surfaces `Alert` doesn't cover, such as a status-highlighted table row or an inline note: ```tsx
diff --git a/packages/core/__snapshots__/src__components__alert__alert.test.tsx.snap b/packages/core/__snapshots__/src__components__alert__alert.test.tsx.snap index aeb870c8c..65b6bb8cc 100644 --- a/packages/core/__snapshots__/src__components__alert__alert.test.tsx.snap +++ b/packages/core/__snapshots__/src__components__alert__alert.test.tsx.snap @@ -1,13 +1,13 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Alert > snapshots > error variant 1`] = `""`; +exports[`Alert > snapshots > error variant 1`] = `""`; -exports[`Alert > snapshots > info variant 1`] = `""`; +exports[`Alert > snapshots > info variant 1`] = `""`; -exports[`Alert > snapshots > neutral variant (default) 1`] = `""`; +exports[`Alert > snapshots > neutral variant (default) 1`] = `""`; -exports[`Alert > snapshots > success variant 1`] = `""`; +exports[`Alert > snapshots > success variant 1`] = `""`; -exports[`Alert > snapshots > warning variant 1`] = `""`; +exports[`Alert > snapshots > warning variant 1`] = `""`; -exports[`Alert > snapshots > with dismissible 1`] = `""`; +exports[`Alert > snapshots > with dismissible 1`] = `""`; diff --git a/packages/core/src/components/alert/alert.tsx b/packages/core/src/components/alert/alert.tsx index 8afb601d4..f39860656 100644 --- a/packages/core/src/components/alert/alert.tsx +++ b/packages/core/src/components/alert/alert.tsx @@ -16,15 +16,14 @@ const alertVariants = cva( variants: { /** Visual style and automatic icon */ variant: { + // Each variant reads the semantic colour roles of its intent. The + // description inherits the title colour; only neutral mutes it. neutral: - "astw:bg-alert-neutral-background astw:text-alert-neutral-foreground astw:border-alert-neutral-border astw:*:data-[slot=alert-description]:text-alert-neutral-foreground-muted", - success: - "astw:bg-alert-success-background astw:text-alert-success-foreground astw:border-alert-success-border astw:*:data-[slot=alert-description]:text-alert-success-foreground-muted", - warning: - "astw:bg-alert-warning-background astw:text-alert-warning-foreground astw:border-alert-warning-border astw:*:data-[slot=alert-description]:text-alert-warning-foreground-muted", - error: - "astw:bg-alert-error-background astw:text-alert-error-foreground astw:border-alert-error-border astw:*:data-[slot=alert-description]:text-alert-error-foreground-muted", - info: "astw:bg-alert-info-background astw:text-alert-info-foreground astw:border-alert-info-border astw:*:data-[slot=alert-description]:text-alert-info-foreground-muted", + "astw:bg-neutral-surface astw:text-neutral-text astw:border-neutral-border astw:*:data-[slot=alert-description]:text-muted-foreground", + success: "astw:bg-success-surface astw:text-success-text astw:border-success-border", + warning: "astw:bg-warning-surface astw:text-warning-text astw:border-warning-border", + error: "astw:bg-danger-surface astw:text-danger-text astw:border-danger-border", + info: "astw:bg-info-surface astw:text-info-text astw:border-info-border", }, }, defaultVariants: { diff --git a/packages/core/src/components/csv-importer/CsvImporter.tsx b/packages/core/src/components/csv-importer/CsvImporter.tsx index 346c4a2c6..3cc9b9253 100644 --- a/packages/core/src/components/csv-importer/CsvImporter.tsx +++ b/packages/core/src/components/csv-importer/CsvImporter.tsx @@ -417,7 +417,7 @@ function ReviewStep({ className={cn( "astw:px-1 astw:py-1", issue?.level === "error" && "astw:bg-destructive/10", - issue?.level === "warning" && "astw:bg-status-attention/10", + issue?.level === "warning" && "astw:bg-warning-surface", )} >
@@ -426,7 +426,7 @@ function ReviewStep({ className={cn( "astw:w-full astw:rounded astw:border astw:px-2 astw:py-1 astw:text-sm astw:bg-transparent", issue?.level === "error" && "astw:border-destructive", - issue?.level === "warning" && "astw:border-status-attention", + issue?.level === "warning" && "astw:border-warning-solid", !issue && "astw:border-transparent", )} value={displayValue} From 7c296667d66afbffdd125c2363b3dcc0927afabb Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Thu, 1 Oct 2026 09:44:44 +0900 Subject: [PATCH 16/29] feat(core): add the gray primitive scale and read it from the base text and line tokens Tailwind neutral, declared once in :root. --foreground, --muted-foreground, --border and --input in the default palette now read gray steps; their resolved values do not change. Also describe the target two-axis shape in the decision record. Co-Authored-By: Claude Fable 5.1 --- .changeset/calm-hues-agree.md | 2 + decisions/semantic-color-roles.md | 20 +++++++++- docs-manifest.json | 2 +- packages/core/src/assets/themes/default.css | 42 ++++++++++++++------- 4 files changed, 50 insertions(+), 16 deletions(-) diff --git a/.changeset/calm-hues-agree.md b/.changeset/calm-hues-agree.md index 328630c0b..726406534 100644 --- a/.changeset/calm-hues-agree.md +++ b/.changeset/calm-hues-agree.md @@ -8,6 +8,8 @@ Add semantic colour roles (`--{info|success|warning|danger|neutral}-{surface|sur Paid ``` +A gray scale (`--primitive-gray-{50..950}`, Tailwind neutral) is also added; the default palette's text and line tokens read it, with no value change. + Visible changes in the default palette: - `bg-status-*` and `bg-alert-*` keep their names but change values. `--status-*` are fill colours; do not use them as text (below 4.5:1 in dark mode). Use `text-{intent}-text` instead (for example `text-status-completed` becomes `text-success-text`). diff --git a/decisions/semantic-color-roles.md b/decisions/semantic-color-roles.md index 93d1db629..c6e654521 100644 --- a/decisions/semantic-color-roles.md +++ b/decisions/semantic-color-roles.md @@ -18,10 +18,12 @@ Tracking issues (tailor-inc/platform-planning): ### Hue scales (internal) -Four hues, each with 12 steps, 12 alpha steps and a contrast colour, for light (`:root`) and dark (`.dark`): +One gray scale and four hues. Each hue has 12 steps, 12 alpha steps and a contrast colour, for light (`:root`) and dark (`.dark`): `--primitive-{blue|green|amber|red}-{1..12}`, `--primitive-{hue}-a{1..12}`, `--primitive-{hue}-contrast` +Gray is the Tailwind `neutral` family, `--primitive-gray-{50..950}`, declared once because it is the same in both modes. It is not generated with Radix: the default palette already used these exact values (`#0a0a0a`, `#737373`, `#e5e5e5`, `#fafafa`, `#a3a3a3`), and the Radix generator cannot produce them. In the default palette `--foreground`, `--muted-foreground`, `--border` and `--input` read gray steps (light 950 / 500 / 200 / 200, dark 50 / 400 for the two text tokens). Dark `--border` and `--input` stay translucent white, and all surfaces stay literal: they are theme inputs, not scale steps. No default value changes. cream and bloom keep their own values; a theme can later pick another family (slate for their navy ink). Gray has no alpha steps. + They are not bridged to Tailwind. Consumers use the semantic roles, not steps. The `--primitive-` prefix keeps the two layers readable as primitive then semantic, and avoids collisions with a consumer's own copy of Radix Colors. It is not `--palette-` because "palette" already names the theme files (default, cream, bloom). The role tokens have no prefix. The existing `--semantic-shadow-*` tokens are a different kind of token and are not part of this set. ### Semantic roles (public, bridged) @@ -71,10 +73,24 @@ Seeds: blue `#2563eb` (Tailwind blue-600), green `#15803d` (green-700), amber `# The "alpha" steps are translucent only where the generator could express them. In light mode, warning a3, a4 and a6, info a6 and danger a4 are opaque 6-digit hex. So the warning surface, surface-hover and border, the info border and the danger surface-hover do not blend with a tinted parent. Dark mode has no opaque values among a3, a4 and a6. Fixing the generator is a later step. +## Target shape + +This step is the status part of a two-axis palette. The rest is planned as follows, so the roles added here do not need to change later: + +| Axis | Scale | Roles | Step | +| ------------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------ | +| Status (this step) | `--primitive-{blue,green,amber,red}-*` | `--{info,success,warning,danger}-{role}` | done | +| Ink and lines | `--primitive-gray-*` | base tokens read it (`--foreground`, `--muted-foreground`, `--border`, `--input`) | done for default; themes later | +| Danger action | red | `--destructive` becomes an alias of `--danger-solid` once its text and border uses move to `--danger-text` / `--danger-border` | next | +| Brand | an accent scale per theme (bloom and cream read blue, theme-tailor reads cyan) | `--primary`, `--ring` and brand tints read accent steps | with the Theme Generator | + +`--status-*` and `--alert-*` stay as aliases for consumer code. Shipped components read the roles. + ## Out of scope - `--destructive` and `--destructive-foreground` aliasing. Themes override `--destructive`, and it is used as solid, text and border at once, so it needs its own change. Button is untouched. -- Gray scale and base-palette remap, theme accent scales, Theme Generator. +- Remapping surfaces or other themes onto the gray scale; gray alpha steps. +- Theme accent scales (the brand axis) and the Theme Generator. - Bridging hue scales to Tailwind. - New Badge or Alert variants, icons in Badge. - Semantic-role swatches on the `/showcase/colors` page. diff --git a/docs-manifest.json b/docs-manifest.json index 1cf7c197b..438746611 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -82,7 +82,7 @@ "hashes": { "typeSurface": "f28bf34ae1466912", "outline": "6138eac23571e177", - "snapshot": "e83317f5eef937e5", + "snapshot": "202f4fa7806204a0", "outputMd": "db2c4388ca783a75", "examples": "65d5fc73550e395a" } diff --git a/packages/core/src/assets/themes/default.css b/packages/core/src/assets/themes/default.css index 7c205ba1d..e0a82cea9 100644 --- a/packages/core/src/assets/themes/default.css +++ b/packages/core/src/assets/themes/default.css @@ -27,15 +27,15 @@ /* ── 4. SYSTEM ────────────────────────────────────────────────────────── */ --background: rgba(250, 250, 250, 1); - --foreground: rgba(10, 10, 10, 1); + --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); - --muted-foreground: rgba(115, 115, 115, 1); - --border: rgba(229, 229, 229, 1); - --input: rgba(229, 229, 229, 1); + --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); @@ -54,13 +54,29 @@ --semantic-shadow-md: 0 4px 6px -1px rgb(15 23 42 / 0.09), 0 2px 4px -2px rgb(15 23 42 / 0.06); --semantic-shadow-lg: 0 10px 15px -3px rgb(15 23 42 / 0.12), 0 4px 6px -4px rgb(15 23 42 / 0.09); - /* ── 6. HUE SCALES (light) — internal; consumers use the semantic roles below ── - * Radix-style 12-step + alpha scales for four hues. Names are prefixed to - * avoid colliding with a consumer's own copy of Radix Colors. Not bridged - * to Tailwind. See decisions/semantic-color-roles.md for how they were made. - * The "alpha" steps are translucent only where the generator could express - * them; some light steps (for example amber a2-a6) resolve to opaque hex. + /* ── 6. PRIMITIVE SCALES (light) — internal; consumers use the semantic roles below ── + * One gray scale and four Radix-style 12-step + alpha hue scales. Names are + * prefixed to avoid colliding with a consumer's own copy of Radix Colors. + * Not bridged to Tailwind. See decisions/semantic-color-roles.md for how + * they were made. The hue "alpha" steps are translucent only where the + * generator could express them; some light steps (for example amber a2-a6) + * resolve to opaque hex. + * + * Gray is the Tailwind `neutral` family (the values this palette already + * used) and is the same in both modes; the SYSTEM text and line tokens + * above read it. Surfaces stay literal: they are theme inputs. */ + --primitive-gray-50: #fafafa; + --primitive-gray-100: #f5f5f5; + --primitive-gray-200: #e5e5e5; + --primitive-gray-300: #d4d4d4; + --primitive-gray-400: #a3a3a3; + --primitive-gray-500: #737373; + --primitive-gray-600: #525252; + --primitive-gray-700: #404040; + --primitive-gray-800: #262626; + --primitive-gray-900: #171717; + --primitive-gray-950: #0a0a0a; --primitive-blue-1: #f7f8f9; --primitive-blue-2: #f0f4fa; --primitive-blue-3: #e5ecf9; @@ -267,13 +283,13 @@ /* ── 4. SYSTEM (dark) ─────────────────────────────────────────────────── */ --background: rgba(10, 10, 10, 1); - --foreground: rgba(250, 250, 250, 1); + --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); - --muted-foreground: rgba(163, 163, 163, 1); + --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); @@ -293,7 +309,7 @@ --semantic-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.35), 0 2px 4px -2px rgb(0 0 0 / 0.28); --semantic-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.42), 0 4px 6px -4px rgb(0 0 0 / 0.35); - /* ── 6. HUE SCALES (dark) ─────────────────────────────────────────────── */ + /* ── 6. PRIMITIVE SCALES (dark) — gray is declared once above ────────── */ --primitive-blue-1: #050a16; --primitive-blue-2: #0e1626; --primitive-blue-3: #11244c; From 3dd50aba61758387fde1eac9367bc5d3e89c7a62 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Thu, 1 Oct 2026 20:58:06 +0900 Subject: [PATCH 17/29] fix(core): read the semantic text roles for MetricCard trends The trend colours were literal green-600 / red-600 with hand-placed dark values. They now read --success-text and --danger-text like Badge and Alert. Co-Authored-By: Claude Fable 5.1 --- .changeset/calm-hues-agree.md | 2 +- decisions/semantic-color-roles.md | 4 ++-- docs-manifest.json | 6 +++--- docs-src/concepts/styling-theming.docs.outline.md | 2 +- docs/concepts/styling-theming.md | 2 +- packages/core/src/components/metric-card/MetricCard.tsx | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.changeset/calm-hues-agree.md b/.changeset/calm-hues-agree.md index 726406534..1277eda12 100644 --- a/.changeset/calm-hues-agree.md +++ b/.changeset/calm-hues-agree.md @@ -13,6 +13,6 @@ A gray scale (`--primitive-gray-{50..950}`, Tailwind neutral) is also added; the Visible changes in the default palette: - `bg-status-*` and `bg-alert-*` keep their names but change values. `--status-*` are fill colours; do not use them as text (below 4.5:1 in dark mode). Use `text-{intent}-text` instead (for example `text-status-completed` becomes `text-success-text`). -- Badge, Alert and CsvImporter read the semantic roles instead of `--status-*` and `--alert-*`. To recolour them, override `--{intent}-*`; overriding `--status-*` or `--alert-*` no longer changes these components. The old names remain for your own `bg-status-*` / `bg-alert-*` utilities. +- Badge, Alert, CsvImporter and MetricCard read the semantic roles instead of `--status-*` and `--alert-*`. To recolour them, override `--{intent}-*`; overriding `--status-*` or `--alert-*` no longer changes these components. The old names remain for your own `bg-status-*` / `bg-alert-*` utilities. - Badge `error` and `subtle-error`, and Alert `error`, follow `--danger-*` instead of `--destructive`. Button and `text-destructive` still follow `--destructive`. - The warning Badge text is dark instead of white. Alert description text now has the same colour as the Alert title. diff --git a/decisions/semantic-color-roles.md b/decisions/semantic-color-roles.md index c6e654521..7a1f0d22a 100644 --- a/decisions/semantic-color-roles.md +++ b/decisions/semantic-color-roles.md @@ -1,6 +1,6 @@ # Decision: hue scales and semantic colour roles under `--status-*` and `--alert-*` -> Status: **Decided for the first step. Hue scales and semantic roles are added; `--status-*` and hue `--alert-*` alias the roles; Badge, Alert and CsvImporter read the roles.** +> Status: **Decided for the first step. Hue scales and semantic roles are added; `--status-*` and hue `--alert-*` alias the roles; Badge, Alert, CsvImporter and MetricCard read the roles.** > Scope: `packages/core/src/assets/themes/default.css`, the Tailwind bridge, Badge. `--destructive`, the gray scale and theme accent scales are not part of this step. ## Context @@ -102,5 +102,5 @@ This step is the status part of a two-axis palette. The rest is planned as follo - Badge `error` and `subtle-error`, and Alert `error`, no longer follow `--destructive`. They use the danger role in both modes. Button and `text-destructive` still follow `--destructive`, so in dark mode a Badge error (`#dc2626`) and a destructive Button (`#f87171`) differ. Consumers who override `--destructive` for Alert or Badge must override `--danger-*` or `--alert-error-*` instead. - The warning Badge text is dark (`#4f3515`) instead of white. - 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) 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. +- 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. diff --git a/docs-manifest.json b/docs-manifest.json index 438746611..c09f410a3 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -1216,9 +1216,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "a2abf4b3f0acb282", + "outline": "2c8be0feb2fa8fe9", "snapshot": null, - "outputMd": "942a4f3aac1b8b72", + "outputMd": "9c4ec55d4b3afed1", "examples": "d3cca347b055a503" } }, @@ -1588,7 +1588,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": "2f3ff3f095c2b1cc", "packages/core/skills/app-shell-patterns/references/concepts/sidebar-navigation.md": "96f1cf893c231861", - "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "942a4f3aac1b8b72", + "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "9c4ec55d4b3afed1", "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 732f0a591..fb48ea557 100644 --- a/docs-src/concepts/styling-theming.docs.outline.md +++ b/docs-src/concepts/styling-theming.docs.outline.md @@ -195,7 +195,7 @@ In the default palette, `text` on `surface` and `contrast` on `solid` and `solid Some light-mode values are opaque instead of translucent: the `warning` surface, surface-hover and border, the `info` border and the `danger` surface-hover. They do not blend with a tinted parent. All dark-mode values are translucent. -`Badge`, `Alert` and `CsvImporter` read these roles directly, not `--status-*` or `--alert-*`. To recolour them, override the roles. `Badge` `error` and `subtle-error`, and `Alert` `error`, follow `--danger-*`. They no longer follow `--destructive`, which still drives `Button` and `text-destructive`. +`Badge`, `Alert`, `CsvImporter` and `MetricCard` read these roles directly, not `--status-*` or `--alert-*`. To recolour them, override the roles. `Badge` `error` and `subtle-error`, and `Alert` `error`, follow `--danger-*`. They no longer follow `--destructive`, which still drives `Button` and `text-destructive`. #### Sidebar & charts diff --git a/docs/concepts/styling-theming.md b/docs/concepts/styling-theming.md index 2a4af87a8..bdbcb7580 100644 --- a/docs/concepts/styling-theming.md +++ b/docs/concepts/styling-theming.md @@ -212,7 +212,7 @@ In the default palette, `text` on `surface` and `contrast` on `solid` and `solid Some light-mode values are opaque instead of translucent: the `warning` surface, surface-hover and border, the `info` border and the `danger` surface-hover. They do not blend with a tinted parent. All dark-mode values are translucent. -`Badge`, `Alert` and `CsvImporter` read these roles directly, not `--status-*` or `--alert-*`. To recolour them, override the roles. `Badge` `error` and `subtle-error`, and `Alert` `error`, follow `--danger-*`. They no longer follow `--destructive`, which still drives `Button` and `text-destructive`. +`Badge`, `Alert`, `CsvImporter` and `MetricCard` read these roles directly, not `--status-*` or `--alert-*`. To recolour them, override the roles. `Badge` `error` and `subtle-error`, and `Alert` `error`, follow `--danger-*`. They no longer follow `--destructive`, which still drives `Button` and `text-destructive`. #### Sidebar & charts diff --git a/packages/core/src/components/metric-card/MetricCard.tsx b/packages/core/src/components/metric-card/MetricCard.tsx index ea46dd389..8040671fb 100644 --- a/packages/core/src/components/metric-card/MetricCard.tsx +++ b/packages/core/src/components/metric-card/MetricCard.tsx @@ -10,8 +10,8 @@ const iconSlotClasses = "astw:flex astw:size-4 astw:items-center astw:justify-center astw:shrink-0 astw:text-muted-foreground"; const trendDirectionClasses: Record = { - up: "astw:text-green-600 astw:dark:text-green-400", - down: "astw:text-red-600 astw:dark:text-red-400", + up: "astw:text-success-text", + down: "astw:text-danger-text", neutral: "astw:text-muted-foreground", }; From 996a681561fd7f383b18889c5d2cf29009d7c7f0 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Thu, 1 Oct 2026 21:08:25 +0900 Subject: [PATCH 18/29] docs(core): align palette guidance on colour roles and note the --status-default change Palette headers now say roles inherit and may be overridden only to recolour the status components, which matches the styling docs. The ADR scope line lists what this step actually touches. The changeset, docs and ADR state that --status-default follows --muted-foreground. Co-Authored-By: Claude Fable 5.1 --- .changeset/calm-hues-agree.md | 2 +- decisions/semantic-color-roles.md | 4 +-- docs-manifest.json | 6 ++-- .../concepts/styling-theming.docs.outline.md | 32 +++++++++---------- docs/concepts/styling-theming.md | 32 +++++++++---------- packages/core/src/assets/theme.css | 4 ++- packages/core/src/assets/themes/_template.css | 5 ++- 7 files changed, 45 insertions(+), 40 deletions(-) diff --git a/.changeset/calm-hues-agree.md b/.changeset/calm-hues-agree.md index 1277eda12..426e646ee 100644 --- a/.changeset/calm-hues-agree.md +++ b/.changeset/calm-hues-agree.md @@ -12,7 +12,7 @@ A gray scale (`--primitive-gray-{50..950}`, Tailwind neutral) is also added; the Visible changes in the default palette: -- `bg-status-*` and `bg-alert-*` keep their names but change values. `--status-*` are fill colours; do not use them as text (below 4.5:1 in dark mode). Use `text-{intent}-text` instead (for example `text-status-completed` becomes `text-success-text`). +- `bg-status-*` and `bg-alert-*` keep their names but change values. `--status-default` now follows `--muted-foreground` (it was a fixed `#737373`), so it is theme-dependent and translucent in the `cream` and `bloom` palettes. `--status-*` are fill colours; do not use them as text (below 4.5:1 in dark mode). Use `text-{intent}-text` instead (for example `text-status-completed` becomes `text-success-text`). - Badge, Alert, CsvImporter and MetricCard read the semantic roles instead of `--status-*` and `--alert-*`. To recolour them, override `--{intent}-*`; overriding `--status-*` or `--alert-*` no longer changes these components. The old names remain for your own `bg-status-*` / `bg-alert-*` utilities. - Badge `error` and `subtle-error`, and Alert `error`, follow `--danger-*` instead of `--destructive`. Button and `text-destructive` still follow `--destructive`. - The warning Badge text is dark instead of white. Alert description text now has the same colour as the Alert title. diff --git a/decisions/semantic-color-roles.md b/decisions/semantic-color-roles.md index 7a1f0d22a..f16ac64ec 100644 --- a/decisions/semantic-color-roles.md +++ b/decisions/semantic-color-roles.md @@ -1,7 +1,7 @@ # Decision: hue scales and semantic colour roles under `--status-*` and `--alert-*` > Status: **Decided for the first step. Hue scales and semantic roles are added; `--status-*` and hue `--alert-*` alias the roles; Badge, Alert, CsvImporter and MetricCard read the roles.** -> Scope: `packages/core/src/assets/themes/default.css`, the Tailwind bridge, Badge. `--destructive`, the gray scale and theme accent scales are not part of this step. +> Scope: `packages/core/src/assets/themes/default.css` (hue scales, gray scale, roles, aliases), the Tailwind bridge, and the components that read the roles: Badge, Alert, CsvImporter, MetricCard. `--destructive` and the theme accent scales are not part of this step. ## Context @@ -47,7 +47,7 @@ The roles and the aliases are declared for `:root, .dark`, so a nested `.dark` w ### Aliases -- `--status-default | neutral | completed | attention | danger` alias the neutral indicator and the info, success, warning and danger `solid` roles. +- `--status-default | neutral | completed | attention | danger` alias the neutral indicator and the info, success, warning and danger `solid` roles. `--status-default` therefore follows `--muted-foreground` instead of the fixed `#737373` it had: `#a3a3a3` in dark default, and translucent ink in cream and bloom. - `--alert-{success,warning,error,info}-{background,border,foreground}` alias `surface`, `border` and `text` (`error` reads the `danger` intent). - `--alert-{h}-foreground-muted` aliases `--alert-{h}-foreground` at full strength. The first plan used 85% of the text colour; over the surface that is 4.26:1 (info), 3.78:1 (success) and 4.23:1 (danger) in light mode, and no opacity below about 97% passes. Keeping the alias on `--alert-{h}-foreground` also keeps the override chain: a consumer who overrides the foreground gets a matching description. - `--alert-neutral-*` keeps its values, expressed through the neutral roles. diff --git a/docs-manifest.json b/docs-manifest.json index c09f410a3..4f3ab7af1 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -1216,9 +1216,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "2c8be0feb2fa8fe9", + "outline": "095898ba50580a60", "snapshot": null, - "outputMd": "9c4ec55d4b3afed1", + "outputMd": "a1a8b39e9fd0e146", "examples": "d3cca347b055a503" } }, @@ -1588,7 +1588,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": "2f3ff3f095c2b1cc", "packages/core/skills/app-shell-patterns/references/concepts/sidebar-navigation.md": "96f1cf893c231861", - "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "9c4ec55d4b3afed1", + "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "a1a8b39e9fd0e146", "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 fb48ea557..0d328af4c 100644 --- a/docs-src/concepts/styling-theming.docs.outline.md +++ b/docs-src/concepts/styling-theming.docs.outline.md @@ -86,14 +86,14 @@ Only import one palette at a time. Theme tokens live in `packages/core/src/assets/themes/`. Copy `_template.css` to start a new palette — it lists exactly which sections to fill in for light and dark mode. -| Section | Required? | What to set | -| --------------------- | --------------------- | ------------------------------------------------------------------------------ | -| **1. Brand** | Yes | `primary`, `secondary`, `accent` (+ foregrounds) — both modes | -| **2. Shell gradient** | Branded palettes only | `--shell-gradient-base`, `--shell-gradient-tint` | -| **3. System** | Tune or copy default | Surfaces: background, card, popover, muted, borders | -| **4. Palette** | Optional | Radius, chart colors, shadows | -| **5. Semantic** | Do not duplicate | Semantic roles, status and alert tokens inherit from `default.css` | -| **6. Structural** | Branded palettes | Copy the structural override block from `bloom.css` or `cream.css` when needed | +| Section | Required? | What to set | +| --------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **1. Brand** | Yes | `primary`, `secondary`, `accent` (+ foregrounds) — both modes | +| **2. Shell gradient** | Branded palettes only | `--shell-gradient-base`, `--shell-gradient-tint` | +| **3. System** | Tune or copy default | Surfaces: background, card, popover, muted, borders | +| **4. Palette** | Optional | Radius, chart colors, shadows | +| **5. Semantic** | Inherit | Colour scales, status and alert tokens inherit from `default.css`. Override a colour role (`--{intent}-{role}`) only to recolour the status components | +| **6. Structural** | Branded palettes | Copy the structural override block from `bloom.css` or `cream.css` when needed | A palette is selected by CSS import, not by an AppShell prop. Import exactly one theme file after `@tailor-platform/app-shell/styles`; if you import none, the default palette from `styles` is used. @@ -160,17 +160,17 @@ There are no `-hover` or `-active` brand tokens. Express interaction states with Five status colors for custom surfaces. They are aliases of the [semantic color roles](#semantic-color-roles) below, so status colors and Alert colors agree by default: -| Token | Use | Tailwind | -| -------------------- | ------------------------ | --------------------- | -| `--status-default` | none / not applicable | `bg-status-default` | -| `--status-neutral` | informational | `bg-status-neutral` | -| `--status-completed` | success, completed | `bg-status-completed` | -| `--status-attention` | warning, needs attention | `bg-status-attention` | -| `--status-danger` | error, blocked | `bg-status-danger` | +| Token | Use | Tailwind | +| -------------------- | ---------------------------------------------------- | --------------------- | +| `--status-default` | none / not applicable (follows `--muted-foreground`) | `bg-status-default` | +| `--status-neutral` | informational | `bg-status-neutral` | +| `--status-completed` | success, completed | `bg-status-completed` | +| `--status-attention` | warning, needs attention | `bg-status-attention` | +| `--status-danger` | error, blocked | `bg-status-danger` | Prefer `Badge` with a semantic variant (`success`, `warning`, `error`, `info`, `neutral`) over applying these directly — the variants already pair fill and foreground correctly. Reach for the raw token only on custom surfaces. -These are fill and indicator colors. Do not use them as text color: `--status-completed`, `--status-danger` and `--status-neutral` are below 4.5:1 on a dark card, and `--status-attention` is below 3:1 on a light one. For text, use `text-{intent}-text`. +These are fill and indicator colors. `--status-default` follows `--muted-foreground`, so it is theme-dependent and translucent in `cream` and `bloom`; the four hue tokens are opaque in every palette. Do not use them as text color: `--status-completed`, `--status-danger` and `--status-neutral` are below 4.5:1 on a dark card, and `--status-attention` is below 3:1 on a light one. For text, use `text-{intent}-text`. #### Semantic color roles diff --git a/docs/concepts/styling-theming.md b/docs/concepts/styling-theming.md index bdbcb7580..8c5ca0850 100644 --- a/docs/concepts/styling-theming.md +++ b/docs/concepts/styling-theming.md @@ -86,14 +86,14 @@ Only import one palette at a time. Theme tokens live in `packages/core/src/assets/themes/`. Copy `_template.css` to start a new palette — it lists exactly which sections to fill in for light and dark mode. -| Section | Required? | What to set | -| --------------------- | --------------------- | ------------------------------------------------------------------------------ | -| **1. Brand** | Yes | `primary`, `secondary`, `accent` (+ foregrounds) — both modes | -| **2. Shell gradient** | Branded palettes only | `--shell-gradient-base`, `--shell-gradient-tint` | -| **3. System** | Tune or copy default | Surfaces: background, card, popover, muted, borders | -| **4. Palette** | Optional | Radius, chart colors, shadows | -| **5. Semantic** | Do not duplicate | Semantic roles, status and alert tokens inherit from `default.css` | -| **6. Structural** | Branded palettes | Copy the structural override block from `bloom.css` or `cream.css` when needed | +| Section | Required? | What to set | +| --------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **1. Brand** | Yes | `primary`, `secondary`, `accent` (+ foregrounds) — both modes | +| **2. Shell gradient** | Branded palettes only | `--shell-gradient-base`, `--shell-gradient-tint` | +| **3. System** | Tune or copy default | Surfaces: background, card, popover, muted, borders | +| **4. Palette** | Optional | Radius, chart colors, shadows | +| **5. Semantic** | Inherit | Colour scales, status and alert tokens inherit from `default.css`. Override a colour role (`--{intent}-{role}`) only to recolour the status components | +| **6. Structural** | Branded palettes | Copy the structural override block from `bloom.css` or `cream.css` when needed | A palette is selected by CSS import, not by an AppShell prop. Import exactly one theme file after `@tailor-platform/app-shell/styles`; if you import none, the default palette from `styles` is used. @@ -177,17 +177,17 @@ There are no `-hover` or `-active` brand tokens. Express interaction states with Five status colors for custom surfaces. They are aliases of the [semantic color roles](#semantic-color-roles) below, so status colors and Alert colors agree by default: -| Token | Use | Tailwind | -| -------------------- | ------------------------ | --------------------- | -| `--status-default` | none / not applicable | `bg-status-default` | -| `--status-neutral` | informational | `bg-status-neutral` | -| `--status-completed` | success, completed | `bg-status-completed` | -| `--status-attention` | warning, needs attention | `bg-status-attention` | -| `--status-danger` | error, blocked | `bg-status-danger` | +| Token | Use | Tailwind | +| -------------------- | ---------------------------------------------------- | --------------------- | +| `--status-default` | none / not applicable (follows `--muted-foreground`) | `bg-status-default` | +| `--status-neutral` | informational | `bg-status-neutral` | +| `--status-completed` | success, completed | `bg-status-completed` | +| `--status-attention` | warning, needs attention | `bg-status-attention` | +| `--status-danger` | error, blocked | `bg-status-danger` | Prefer `Badge` with a semantic variant (`success`, `warning`, `error`, `info`, `neutral`) over applying these directly — the variants already pair fill and foreground correctly. Reach for the raw token only on custom surfaces. -These are fill and indicator colors. Do not use them as text color: `--status-completed`, `--status-danger` and `--status-neutral` are below 4.5:1 on a dark card, and `--status-attention` is below 3:1 on a light one. For text, use `text-{intent}-text`. +These are fill and indicator colors. `--status-default` follows `--muted-foreground`, so it is theme-dependent and translucent in `cream` and `bloom`; the four hue tokens are opaque in every palette. Do not use them as text color: `--status-completed`, `--status-danger` and `--status-neutral` are below 4.5:1 on a dark card, and `--status-attention` is below 3:1 on a light one. For text, use `text-{intent}-text`. #### Semantic color roles diff --git a/packages/core/src/assets/theme.css b/packages/core/src/assets/theme.css index f0238e8fd..97899dee8 100644 --- a/packages/core/src/assets/theme.css +++ b/packages/core/src/assets/theme.css @@ -16,7 +16,9 @@ * 4. PALETTE — optional: radius, charts, shadows, destructive. * * Inherited from default.css — do not duplicate in new palettes: - * --ring, --sidebar-*, --{info|success|warning|danger|neutral}-* (colour roles), --status-*, --alert-*. + * --ring, --sidebar-*, --primitive-* (colour scales), --status-*, --alert-*. + * The colour roles --{intent}-{role} inherit too; a palette overrides them + * only to recolour the status components. * * Light/dark mode is controlled separately via `.dark` on . * Palette selection is by CSS import; there is no runtime palette prop. diff --git a/packages/core/src/assets/themes/_template.css b/packages/core/src/assets/themes/_template.css index fd7e0e9a8..6783295fa 100644 --- a/packages/core/src/assets/themes/_template.css +++ b/packages/core/src/assets/themes/_template.css @@ -17,7 +17,10 @@ * `@tailor-platform/app-shell/styles` in the consuming app. * * DO NOT SET (inherited from `:root` in default.css): - * --ring, --sidebar-*, --{info|success|warning|danger|neutral}-* (colour roles), --status-*, --alert-* + * --ring, --sidebar-*, --primitive-* (colour scales), --status-*, --alert-* + * The colour roles --{info|success|warning|danger|neutral}-{role} also inherit. + * Override a role only to recolour the status components (Badge, Alert, ...) + * for this palette; never set --status-* or --alert-*, they alias the roles. * * PREVIEW: `/showcase/colors` in the Vite example app. * ═══════════════════════════════════════════════════════════════════════════ From 057c0796950c5e15bae1ea5dc97545b01352836d Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Fri, 2 Oct 2026 09:11:07 +0900 Subject: [PATCH 19/29] refactor(core): group the primitive scales and renumber the palette sections Light and dark scales now sit next to each other in their own rules after the palette tiers, numbered 6; the semantic roles are 7. Co-Authored-By: Claude Fable 5.1 --- packages/core/src/assets/themes/default.css | 289 ++++++++++---------- 1 file changed, 150 insertions(+), 139 deletions(-) diff --git a/packages/core/src/assets/themes/default.css b/packages/core/src/assets/themes/default.css index e0a82cea9..849f012b5 100644 --- a/packages/core/src/assets/themes/default.css +++ b/packages/core/src/assets/themes/default.css @@ -53,19 +53,69 @@ --semantic-shadow-sm: 0 1px 3px 0 rgb(15 23 42 / 0.08), 0 1px 2px -1px rgb(15 23 42 / 0.08); --semantic-shadow-md: 0 4px 6px -1px rgb(15 23 42 / 0.09), 0 2px 4px -2px rgb(15 23 42 / 0.06); --semantic-shadow-lg: 0 10px 15px -3px rgb(15 23 42 / 0.12), 0 4px 6px -4px rgb(15 23 42 / 0.09); +} + +/* + * Dark variant. Overrides BRAND + SYSTEM + PALETTE. The primitive scales (6) + * and the semantic roles (7) follow below. Shared aliases/ring re-resolve + * when BRAND changes; `--radius` inherits from the light block. + */ +.dark { + 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); + --accent: rgb(48, 48, 48); + --accent-foreground: rgba(250, 250, 250, 1); + + /* ── 4. SYSTEM (dark) ─────────────────────────────────────────────────── */ + --background: rgba(10, 10, 10, 1); + --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); + --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); - /* ── 6. PRIMITIVE SCALES (light) — internal; consumers use the semantic roles below ── - * One gray scale and four Radix-style 12-step + alpha hue scales. Names are - * prefixed to avoid colliding with a consumer's own copy of Radix Colors. - * Not bridged to Tailwind. See decisions/semantic-color-roles.md for how - * they were made. The hue "alpha" steps are translucent only where the - * generator could express them; some light steps (for example amber a2-a6) - * resolve to opaque hex. - * - * Gray is the Tailwind `neutral` family (the values this palette already - * used) and is the same in both modes; the SYSTEM text and line tokens - * above read it. Surfaces stay literal: they are theme inputs. - */ + /* ── 5. PALETTE (dark) ────────────────────────────────────────────────── */ + --destructive: rgba(248, 113, 113, 1); + --destructive-foreground: rgba(254, 242, 242, 1); + --chart-1: rgba(29, 78, 216, 1); + --chart-2: rgba(16, 185, 129, 1); + --chart-3: rgba(245, 158, 11, 1); + --chart-4: rgba(168, 85, 247, 1); + --chart-5: rgba(244, 63, 94, 1); + --semantic-shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.35); + --semantic-shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.38), 0 1px 2px -1px rgb(0 0 0 / 0.32); + --semantic-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.35), 0 2px 4px -2px rgb(0 0 0 / 0.28); + --semantic-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.42), 0 4px 6px -4px rgb(0 0 0 / 0.35); +} + +/* + * 6. PRIMITIVE SCALES — internal; consumers use the semantic roles (7) below. + * + * One gray scale and four Radix-style 12-step + alpha hue scales, light then + * dark. Names are prefixed to avoid colliding with a consumer's own copy of + * Radix Colors. Not bridged to Tailwind. See decisions/semantic-color-roles.md + * for how they were made. The hue "alpha" steps are translucent only where the + * generator could express them; some light steps (for example amber a2-a6) + * 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. + */ +:root { + /* gray — both modes */ --primitive-gray-50: #fafafa; --primitive-gray-100: #f5f5f5; --primitive-gray-200: #e5e5e5; @@ -77,6 +127,8 @@ --primitive-gray-800: #262626; --primitive-gray-900: #171717; --primitive-gray-950: #0a0a0a; + + /* hues — light */ --primitive-blue-1: #f7f8f9; --primitive-blue-2: #f0f4fa; --primitive-blue-3: #e5ecf9; @@ -182,134 +234,8 @@ --primitive-red-contrast: #ffffff; } -/* - * 6. SEMANTIC — product-wide; inherited by all palettes, do not override. - * - * Declared for both `:root` and `.dark` so a nested `.dark` wrapper re-resolves - * the roles against its own hue scales. The values are identical in both - * selectors; only the hue scales differ per mode. - * - * Roles: `--{info|success|warning|danger|neutral}-{surface|surface-hover| - * border|solid|solid-hover|text|contrast|indicator}`. Hue intents read the hue - * scales; `neutral` reads the system tokens. `--status-*` and `--alert-*` below - * are aliases of these roles, so both families agree by construction. - */ -:root, .dark { - --neutral-surface: var(--muted); - --neutral-surface-hover: var(--accent); - --neutral-border: var(--border); - --neutral-solid: var(--secondary); - --neutral-solid-hover: var(--accent); - --neutral-text: var(--foreground); - --neutral-contrast: var(--secondary-foreground); - --neutral-indicator: var(--muted-foreground); - --info-surface: var(--primitive-blue-a3); - --info-surface-hover: var(--primitive-blue-a4); - --info-border: var(--primitive-blue-a6); - --info-solid: var(--primitive-blue-9); - --info-solid-hover: var(--primitive-blue-10); - --info-text: var(--primitive-blue-11); - --info-contrast: var(--primitive-blue-contrast); - --info-indicator: var(--primitive-blue-9); - --success-surface: var(--primitive-green-a3); - --success-surface-hover: var(--primitive-green-a4); - --success-border: var(--primitive-green-a6); - --success-solid: var(--primitive-green-9); - --success-solid-hover: var(--primitive-green-10); - --success-text: var(--primitive-green-11); - --success-contrast: var(--primitive-green-contrast); - --success-indicator: var(--primitive-green-9); - --warning-surface: var(--primitive-amber-a3); - --warning-surface-hover: var(--primitive-amber-a4); - --warning-border: var(--primitive-amber-a6); - --warning-solid: var(--primitive-amber-9); - --warning-solid-hover: var(--primitive-amber-10); - --warning-text: var(--primitive-amber-12); - --warning-contrast: var(--primitive-amber-contrast); - --warning-indicator: var(--primitive-amber-9); - --danger-surface: var(--primitive-red-a3); - --danger-surface-hover: var(--primitive-red-a4); - --danger-border: var(--primitive-red-a6); - --danger-solid: var(--primitive-red-9); - --danger-solid-hover: var(--primitive-red-10); - --danger-text: var(--primitive-red-11); - --danger-contrast: var(--primitive-red-contrast); - --danger-indicator: var(--primitive-red-9); - - --status-default: var(--neutral-indicator); - --status-neutral: var(--info-solid); - --status-completed: var(--success-solid); - --status-attention: var(--warning-solid); - --status-danger: var(--danger-solid); - --alert-neutral-background: var(--neutral-surface); - --alert-neutral-foreground: var(--neutral-text); - --alert-neutral-foreground-muted: var(--muted-foreground); - --alert-neutral-border: var(--neutral-border); - --alert-success-background: var(--success-surface); - --alert-success-foreground: var(--success-text); - --alert-success-foreground-muted: var(--alert-success-foreground); - --alert-success-border: var(--success-border); - --alert-warning-background: var(--warning-surface); - --alert-warning-foreground: var(--warning-text); - --alert-warning-foreground-muted: var(--alert-warning-foreground); - --alert-warning-border: var(--warning-border); - --alert-error-background: var(--danger-surface); - --alert-error-foreground: var(--danger-text); - --alert-error-foreground-muted: var(--alert-error-foreground); - --alert-error-border: var(--danger-border); - --alert-info-background: var(--info-surface); - --alert-info-foreground: var(--info-text); - --alert-info-foreground-muted: var(--alert-info-foreground); - --alert-info-border: var(--info-border); -} - -/* - * Dark variant. Overrides BRAND + SYSTEM + the hue scales. The semantic roles - * and aliases live in the shared block above and re-resolve against these - * scales. Shared aliases/ring re-resolve when BRAND changes; `--radius` - * inherits from the light block. - */ -.dark { - 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); - --accent: rgb(48, 48, 48); - --accent-foreground: rgba(250, 250, 250, 1); - - /* ── 4. SYSTEM (dark) ─────────────────────────────────────────────────── */ - --background: rgba(10, 10, 10, 1); - --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); - --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); - - /* ── 5. PALETTE (dark) ────────────────────────────────────────────────── */ - --destructive: rgba(248, 113, 113, 1); - --destructive-foreground: rgba(254, 242, 242, 1); - --chart-1: rgba(29, 78, 216, 1); - --chart-2: rgba(16, 185, 129, 1); - --chart-3: rgba(245, 158, 11, 1); - --chart-4: rgba(168, 85, 247, 1); - --chart-5: rgba(244, 63, 94, 1); - --semantic-shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.35); - --semantic-shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.38), 0 1px 2px -1px rgb(0 0 0 / 0.32); - --semantic-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.35), 0 2px 4px -2px rgb(0 0 0 / 0.28); - --semantic-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.42), 0 4px 6px -4px rgb(0 0 0 / 0.35); - - /* ── 6. PRIMITIVE SCALES (dark) — gray is declared once above ────────── */ + /* hues — dark */ --primitive-blue-1: #050a16; --primitive-blue-2: #0e1626; --primitive-blue-3: #11244c; @@ -413,7 +339,92 @@ --primitive-red-a11: #ff9083; --primitive-red-a12: #ffd1ca; --primitive-red-contrast: #ffffff; +} + +/* + * 7. SEMANTIC — product-wide; inherited by all palettes, do not override. + * + * Declared for both `:root` and `.dark` so a nested `.dark` wrapper re-resolves + * the roles against its own hue scales (6). The values are identical in both + * selectors; only the hue scales differ per mode. The one per-mode exception, + * `--warning-text`, is declared in the `.dark` rule after this block. + * + * Roles: `--{info|success|warning|danger|neutral}-{surface|surface-hover| + * border|solid|solid-hover|text|contrast|indicator}`. Hue intents read the hue + * scales; `neutral` reads the system tokens. `--status-*` and `--alert-*` below + * are aliases of these roles, so both families agree by construction. + */ +:root, +.dark { + --neutral-surface: var(--muted); + --neutral-surface-hover: var(--accent); + --neutral-border: var(--border); + --neutral-solid: var(--secondary); + --neutral-solid-hover: var(--accent); + --neutral-text: var(--foreground); + --neutral-contrast: var(--secondary-foreground); + --neutral-indicator: var(--muted-foreground); + --info-surface: var(--primitive-blue-a3); + --info-surface-hover: var(--primitive-blue-a4); + --info-border: var(--primitive-blue-a6); + --info-solid: var(--primitive-blue-9); + --info-solid-hover: var(--primitive-blue-10); + --info-text: var(--primitive-blue-11); + --info-contrast: var(--primitive-blue-contrast); + --info-indicator: var(--primitive-blue-9); + --success-surface: var(--primitive-green-a3); + --success-surface-hover: var(--primitive-green-a4); + --success-border: var(--primitive-green-a6); + --success-solid: var(--primitive-green-9); + --success-solid-hover: var(--primitive-green-10); + --success-text: var(--primitive-green-11); + --success-contrast: var(--primitive-green-contrast); + --success-indicator: var(--primitive-green-9); + --warning-surface: var(--primitive-amber-a3); + --warning-surface-hover: var(--primitive-amber-a4); + --warning-border: var(--primitive-amber-a6); + --warning-solid: var(--primitive-amber-9); + --warning-solid-hover: var(--primitive-amber-10); + --warning-text: var(--primitive-amber-12); + --warning-contrast: var(--primitive-amber-contrast); + --warning-indicator: var(--primitive-amber-9); + --danger-surface: var(--primitive-red-a3); + --danger-surface-hover: var(--primitive-red-a4); + --danger-border: var(--primitive-red-a6); + --danger-solid: var(--primitive-red-9); + --danger-solid-hover: var(--primitive-red-10); + --danger-text: var(--primitive-red-11); + --danger-contrast: var(--primitive-red-contrast); + --danger-indicator: var(--primitive-red-9); + --status-default: var(--neutral-indicator); + --status-neutral: var(--info-solid); + --status-completed: var(--success-solid); + --status-attention: var(--warning-solid); + --status-danger: var(--danger-solid); + --alert-neutral-background: var(--neutral-surface); + --alert-neutral-foreground: var(--neutral-text); + --alert-neutral-foreground-muted: var(--muted-foreground); + --alert-neutral-border: var(--neutral-border); + --alert-success-background: var(--success-surface); + --alert-success-foreground: var(--success-text); + --alert-success-foreground-muted: var(--alert-success-foreground); + --alert-success-border: var(--success-border); + --alert-warning-background: var(--warning-surface); + --alert-warning-foreground: var(--warning-text); + --alert-warning-foreground-muted: var(--alert-warning-foreground); + --alert-warning-border: var(--warning-border); + --alert-error-background: var(--danger-surface); + --alert-error-foreground: var(--danger-text); + --alert-error-foreground-muted: var(--alert-error-foreground); + --alert-error-border: var(--danger-border); + --alert-info-background: var(--info-surface); + --alert-info-foreground: var(--info-text); + --alert-info-foreground-muted: var(--alert-info-foreground); + --alert-info-border: var(--info-border); +} + +.dark { /* Warning text: step 12 on the light surface, step 11 on the dark one. */ --warning-text: var(--primitive-amber-11); } From 47ee44630c780f394dd3756cc0debd66d82f6b10 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Fri, 2 Oct 2026 09:18:04 +0900 Subject: [PATCH 20/29] docs(core): point custom surfaces at the colour roles and note how a role is added Co-Authored-By: Claude Fable 5.1 --- decisions/semantic-color-roles.md | 2 ++ docs-manifest.json | 6 +++--- .../concepts/styling-theming.docs.outline.md | 17 ++++------------- docs/concepts/styling-theming.md | 17 ++++------------- 4 files changed, 13 insertions(+), 29 deletions(-) diff --git a/decisions/semantic-color-roles.md b/decisions/semantic-color-roles.md index f16ac64ec..22d890a57 100644 --- a/decisions/semantic-color-roles.md +++ b/decisions/semantic-color-roles.md @@ -43,6 +43,8 @@ They are not bridged to Tailwind. Consumers use the semantic roles, not steps. T The full set is bridged, including roles no component uses yet (neutral solid roles, hue `border`), so the set is one coherent vocabulary and `--alert-*` can alias every role. +Adding a role or an intent touches four places in one commit: `default.css` (both the `:root, .dark` block and, if the role has a per-mode exception, the `.dark` rule), `theme.bridge.css`, the pinned counts in `theme-bridge.test.ts`, and the roles table in `docs-src/concepts/styling-theming.docs.outline.md`. The docs table is hand-written; only the bridge count is checked by a test. + The roles and the aliases are declared for `:root, .dark`, so a nested `.dark` wrapper resolves them against its own scales. The only per-mode override is `--warning-text` (step 12 in light, step 11 in dark). ### Aliases diff --git a/docs-manifest.json b/docs-manifest.json index 46a217d71..ccc99dd0c 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -1284,9 +1284,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "095898ba50580a60", + "outline": "72eb8fb70b13f348", "snapshot": null, - "outputMd": "a1a8b39e9fd0e146", + "outputMd": "3d469b794cb3a439", "examples": "d3cca347b055a503" } }, @@ -1656,7 +1656,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": "2f3ff3f095c2b1cc", "packages/core/skills/app-shell-patterns/references/concepts/sidebar-navigation.md": "dc6b3afc049b4148", - "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "a1a8b39e9fd0e146", + "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "3d469b794cb3a439", "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 0d328af4c..099f171e5 100644 --- a/docs-src/concepts/styling-theming.docs.outline.md +++ b/docs-src/concepts/styling-theming.docs.outline.md @@ -193,7 +193,7 @@ Each intent has eight roles. The intents are `info`, `success`, `warning`, `dang In the default palette, `text` on `surface` and `contrast` on `solid` and `solid-hover` are at least 4.5:1 for the four hue intents, in light and dark mode, on `--card` and `--background`. A unit test checks this from the shipped CSS. It does not cover hover surfaces, the `cream` and `bloom` palettes, or the `neutral` intent. Text on `surface-hover` is below 4.5:1 for some roles. -Some light-mode values are opaque instead of translucent: the `warning` surface, surface-hover and border, the `info` border and the `danger` surface-hover. They do not blend with a tinted parent. All dark-mode values are translucent. +Most `surface` and `border` values are translucent tints, so an opacity modifier compounds rather than replaces — `bg-info-surface/50` halves the tint's alpha instead of setting it to 50%. Some light-mode values are opaque instead of translucent: the `warning` surface, surface-hover and border, the `info` border and the `danger` surface-hover. They do not blend with a tinted parent. All dark-mode values are translucent. `Badge`, `Alert`, `CsvImporter` and `MetricCard` read these roles directly, not `--status-*` or `--alert-*`. To recolour them, override the roles. `Badge` `error` and `subtle-error`, and `Alert` `error`, follow `--danger-*`. They no longer follow `--destructive`, which still drives `Button` and `text-destructive`. @@ -205,25 +205,16 @@ Some light-mode values are opaque instead of translucent: the `warning` surface, #### Alerts -`--alert-{neutral,success,warning,error,info}-{background,foreground,foreground-muted,border}` — five variants × four slots, each bridged into Tailwind. In the default palette they are aliases of the semantic color roles (`error` reads the `danger` role); `foreground-muted` follows `foreground`, so overriding `foreground` changes both. They are kept for custom surfaces in consumer code; the `Alert` component reads the roles, so overriding `--alert-*` changes these utilities but not `Alert`: +`--alert-{neutral,success,warning,error,info}-{background,foreground,foreground-muted,border}` and the matching utilities (`bg-alert-info-background`, `text-alert-info-foreground`, ...) remain for existing code. In the default palette they are aliases of the semantic color roles: `background` is `surface`, `border` is `border`, `foreground` is `text`, and `foreground-muted` follows `foreground`; `error` reads the `danger` intent. The `Alert` component reads the roles, so overriding `--alert-*` changes these utilities but not `Alert`. -| Slot | Tailwind | Use for | -| ------------------ | ---------------------------------- | ---------------------- | -| `background` | `bg-alert-info-background` | Callout fill | -| `foreground` | `text-alert-info-foreground` | Primary text and icons | -| `foreground-muted` | `text-alert-info-foreground-muted` | Description text | -| `border` | `border-alert-info-border` | Callout border | - -Prefer the `Alert` component — it already pairs surface, border and text per variant and supplies the icon. Reach for the utilities directly only on custom surfaces `Alert` doesn't cover, such as a status-highlighted table row or an inline note: +For new custom surfaces, use the roles instead of the alert slots: ```tsx -
+
…
``` -Most `background` and `border` values are translucent tints, so an opacity modifier compounds rather than replaces — `bg-alert-info-background/50` halves the tint's alpha instead of setting it to 50%. In light mode the `warning` background and border and the `info` border are opaque, so an opacity modifier does not blend them with a tinted parent. - ```tsx // Good — semantic token pairs, and a variant where one exists
…
diff --git a/docs/concepts/styling-theming.md b/docs/concepts/styling-theming.md index 8c5ca0850..d0bdc74b2 100644 --- a/docs/concepts/styling-theming.md +++ b/docs/concepts/styling-theming.md @@ -210,7 +210,7 @@ Each intent has eight roles. The intents are `info`, `success`, `warning`, `dang In the default palette, `text` on `surface` and `contrast` on `solid` and `solid-hover` are at least 4.5:1 for the four hue intents, in light and dark mode, on `--card` and `--background`. A unit test checks this from the shipped CSS. It does not cover hover surfaces, the `cream` and `bloom` palettes, or the `neutral` intent. Text on `surface-hover` is below 4.5:1 for some roles. -Some light-mode values are opaque instead of translucent: the `warning` surface, surface-hover and border, the `info` border and the `danger` surface-hover. They do not blend with a tinted parent. All dark-mode values are translucent. +Most `surface` and `border` values are translucent tints, so an opacity modifier compounds rather than replaces — `bg-info-surface/50` halves the tint's alpha instead of setting it to 50%. Some light-mode values are opaque instead of translucent: the `warning` surface, surface-hover and border, the `info` border and the `danger` surface-hover. They do not blend with a tinted parent. All dark-mode values are translucent. `Badge`, `Alert`, `CsvImporter` and `MetricCard` read these roles directly, not `--status-*` or `--alert-*`. To recolour them, override the roles. `Badge` `error` and `subtle-error`, and `Alert` `error`, follow `--danger-*`. They no longer follow `--destructive`, which still drives `Button` and `text-destructive`. @@ -222,25 +222,16 @@ Some light-mode values are opaque instead of translucent: the `warning` surface, #### Alerts -`--alert-{neutral,success,warning,error,info}-{background,foreground,foreground-muted,border}` — five variants × four slots, each bridged into Tailwind. In the default palette they are aliases of the semantic color roles (`error` reads the `danger` role); `foreground-muted` follows `foreground`, so overriding `foreground` changes both. They are kept for custom surfaces in consumer code; the `Alert` component reads the roles, so overriding `--alert-*` changes these utilities but not `Alert`: +`--alert-{neutral,success,warning,error,info}-{background,foreground,foreground-muted,border}` and the matching utilities (`bg-alert-info-background`, `text-alert-info-foreground`, ...) remain for existing code. In the default palette they are aliases of the semantic color roles: `background` is `surface`, `border` is `border`, `foreground` is `text`, and `foreground-muted` follows `foreground`; `error` reads the `danger` intent. The `Alert` component reads the roles, so overriding `--alert-*` changes these utilities but not `Alert`. -| Slot | Tailwind | Use for | -| ------------------ | ---------------------------------- | ---------------------- | -| `background` | `bg-alert-info-background` | Callout fill | -| `foreground` | `text-alert-info-foreground` | Primary text and icons | -| `foreground-muted` | `text-alert-info-foreground-muted` | Description text | -| `border` | `border-alert-info-border` | Callout border | - -Prefer the `Alert` component — it already pairs surface, border and text per variant and supplies the icon. Reach for the utilities directly only on custom surfaces `Alert` doesn't cover, such as a status-highlighted table row or an inline note: +For new custom surfaces, use the roles instead of the alert slots: ```tsx -
+
…
``` -Most `background` and `border` values are translucent tints, so an opacity modifier compounds rather than replaces — `bg-alert-info-background/50` halves the tint's alpha instead of setting it to 50%. In light mode the `warning` background and border and the `info` border are opaque, so an opacity modifier does not blend them with a tinted parent. - ```tsx // Good — semantic token pairs, and a variant where one exists
…
From 3fdca4b8a0e0ef5a9f3749b306b4515ba4796c6c Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Fri, 2 Oct 2026 09:23:48 +0900 Subject: [PATCH 21/29] docs(core): state the hover floor and introduce the closing colour example Co-Authored-By: Claude Fable 5.1 --- docs-manifest.json | 6 +++--- docs-src/concepts/styling-theming.docs.outline.md | 4 +++- docs/concepts/styling-theming.md | 4 +++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/docs-manifest.json b/docs-manifest.json index ccc99dd0c..6ae4136fa 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -1284,9 +1284,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "72eb8fb70b13f348", + "outline": "2cb90564eed46574", "snapshot": null, - "outputMd": "3d469b794cb3a439", + "outputMd": "e88076f27520b951", "examples": "d3cca347b055a503" } }, @@ -1656,7 +1656,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": "2f3ff3f095c2b1cc", "packages/core/skills/app-shell-patterns/references/concepts/sidebar-navigation.md": "dc6b3afc049b4148", - "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "3d469b794cb3a439", + "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "e88076f27520b951", "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 099f171e5..eb3177a04 100644 --- a/docs-src/concepts/styling-theming.docs.outline.md +++ b/docs-src/concepts/styling-theming.docs.outline.md @@ -191,7 +191,7 @@ Each intent has eight roles. The intents are `info`, `success`, `warning`, `dang Paid ``` -In the default palette, `text` on `surface` and `contrast` on `solid` and `solid-hover` are at least 4.5:1 for the four hue intents, in light and dark mode, on `--card` and `--background`. A unit test checks this from the shipped CSS. It does not cover hover surfaces, the `cream` and `bloom` palettes, or the `neutral` intent. Text on `surface-hover` is below 4.5:1 for some roles. +In the default palette, `text` on `surface` and `contrast` on `solid` and `solid-hover` are at least 4.5:1 for the four hue intents, in light and dark mode, on `--card` and `--background`. A unit test checks this from the shipped CSS. `text` on `surface-hover` is held at 4.2:1 by the same test; it is below 4.5:1 for `danger` and `success` in light mode. The test does not cover the `cream` and `bloom` palettes or the `neutral` intent. Most `surface` and `border` values are translucent tints, so an opacity modifier compounds rather than replaces — `bg-info-surface/50` halves the tint's alpha instead of setting it to 50%. Some light-mode values are opaque instead of translucent: the `warning` surface, surface-hover and border, the `info` border and the `danger` surface-hover. They do not blend with a tinted parent. All dark-mode values are translucent. @@ -215,6 +215,8 @@ For new custom surfaces, use the roles instead of the alert slots:
``` +Across all colour tokens: + ```tsx // Good — semantic token pairs, and a variant where one exists
…
diff --git a/docs/concepts/styling-theming.md b/docs/concepts/styling-theming.md index d0bdc74b2..978d89dac 100644 --- a/docs/concepts/styling-theming.md +++ b/docs/concepts/styling-theming.md @@ -208,7 +208,7 @@ Each intent has eight roles. The intents are `info`, `success`, `warning`, `dang Paid ``` -In the default palette, `text` on `surface` and `contrast` on `solid` and `solid-hover` are at least 4.5:1 for the four hue intents, in light and dark mode, on `--card` and `--background`. A unit test checks this from the shipped CSS. It does not cover hover surfaces, the `cream` and `bloom` palettes, or the `neutral` intent. Text on `surface-hover` is below 4.5:1 for some roles. +In the default palette, `text` on `surface` and `contrast` on `solid` and `solid-hover` are at least 4.5:1 for the four hue intents, in light and dark mode, on `--card` and `--background`. A unit test checks this from the shipped CSS. `text` on `surface-hover` is held at 4.2:1 by the same test; it is below 4.5:1 for `danger` and `success` in light mode. The test does not cover the `cream` and `bloom` palettes or the `neutral` intent. Most `surface` and `border` values are translucent tints, so an opacity modifier compounds rather than replaces — `bg-info-surface/50` halves the tint's alpha instead of setting it to 50%. Some light-mode values are opaque instead of translucent: the `warning` surface, surface-hover and border, the `info` border and the `danger` surface-hover. They do not blend with a tinted parent. All dark-mode values are translucent. @@ -232,6 +232,8 @@ For new custom surfaces, use the roles instead of the alert slots:
``` +Across all colour tokens: + ```tsx // Good — semantic token pairs, and a variant where one exists
…
From 0e035dd516fb069be15e10a072c2a7cbd39e8c42 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Fri, 2 Oct 2026 09:27:11 +0900 Subject: [PATCH 22/29] docs(decisions): note that variant names stay until a major Co-Authored-By: Claude Fable 5.1 --- decisions/semantic-color-roles.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/decisions/semantic-color-roles.md b/decisions/semantic-color-roles.md index 22d890a57..a74b6d703 100644 --- a/decisions/semantic-color-roles.md +++ b/decisions/semantic-color-roles.md @@ -88,6 +88,8 @@ This step is the status part of a two-axis palette. The rest is planned as follo `--status-*` and `--alert-*` stay as aliases for consumer code. Shipped components read the roles. +The component variant names (`Badge` and `Alert` `error`, Badge `neutral`) are public API and are not renamed here. Aligning them with the intent names (`error` → `danger`) is a breaking change; it is a candidate for the next major, together with deprecating `--status-*` and `--alert-*`. + ## Out of scope - `--destructive` and `--destructive-foreground` aliasing. Themes override `--destructive`, and it is used as solid, text and border at once, so it needs its own change. Button is untouched. From dc2bbcf126d7c0854b7932790131ebc926982070 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Fri, 2 Oct 2026 09:37:28 +0900 Subject: [PATCH 23/29] docs(core): deprecate --status-* and --alert-* in favour of the colour roles Co-Authored-By: Claude Fable 5.1 --- .changeset/calm-hues-agree.md | 2 +- decisions/semantic-color-roles.md | 4 ++-- docs-manifest.json | 6 +++--- .../concepts/styling-theming.docs.outline.md | 20 +++++++++---------- docs/concepts/styling-theming.md | 20 +++++++++---------- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/.changeset/calm-hues-agree.md b/.changeset/calm-hues-agree.md index 426e646ee..3e5cf9470 100644 --- a/.changeset/calm-hues-agree.md +++ b/.changeset/calm-hues-agree.md @@ -12,7 +12,7 @@ A gray scale (`--primitive-gray-{50..950}`, Tailwind neutral) is also added; the Visible changes in the default palette: -- `bg-status-*` and `bg-alert-*` keep their names but change values. `--status-default` now follows `--muted-foreground` (it was a fixed `#737373`), so it is theme-dependent and translucent in the `cream` and `bloom` palettes. `--status-*` are fill colours; do not use them as text (below 4.5:1 in dark mode). Use `text-{intent}-text` instead (for example `text-status-completed` becomes `text-success-text`). +- `--status-*` and `--alert-*` are deprecated. `bg-status-*` and `bg-alert-*` keep working and change values; they will be removed in the next major. New code uses the roles. `--status-default` now follows `--muted-foreground` (it was a fixed `#737373`), so it is theme-dependent and translucent in the `cream` and `bloom` palettes. `--status-*` are fill colours; do not use them as text (below 4.5:1 in dark mode). Use `text-{intent}-text` instead (for example `text-status-completed` becomes `text-success-text`). - Badge, Alert, CsvImporter and MetricCard read the semantic roles instead of `--status-*` and `--alert-*`. To recolour them, override `--{intent}-*`; overriding `--status-*` or `--alert-*` no longer changes these components. The old names remain for your own `bg-status-*` / `bg-alert-*` utilities. - Badge `error` and `subtle-error`, and Alert `error`, follow `--danger-*` instead of `--destructive`. Button and `text-destructive` still follow `--destructive`. - The warning Badge text is dark instead of white. Alert description text now has the same colour as the Alert title. diff --git a/decisions/semantic-color-roles.md b/decisions/semantic-color-roles.md index a74b6d703..23927ead4 100644 --- a/decisions/semantic-color-roles.md +++ b/decisions/semantic-color-roles.md @@ -86,9 +86,9 @@ This step is the status part of a two-axis palette. The rest is planned as follo | Danger action | red | `--destructive` becomes an alias of `--danger-solid` once its text and border uses move to `--danger-text` / `--danger-border` | next | | Brand | an accent scale per theme (bloom and cream read blue, theme-tailor reads cyan) | `--primary`, `--ring` and brand tints read accent steps | with the Theme Generator | -`--status-*` and `--alert-*` stay as aliases for consumer code. Shipped components read the roles. +`--status-*` and `--alert-*` stay as aliases for consumer code and are deprecated: the docs and changeset say so, and they are removed in the next major. Shipped components read the roles. An ESLint rule in `@tailor-platform/eslint-plugin-app-shell` that flags the old utilities and offers the role as a fix is a separate change. -The component variant names (`Badge` and `Alert` `error`, Badge `neutral`) are public API and are not renamed here. Aligning them with the intent names (`error` → `danger`) is a breaking change; it is a candidate for the next major, together with deprecating `--status-*` and `--alert-*`. +The component variant names (`Badge` and `Alert` `error`, Badge `neutral`) are public API and are not renamed here. Aligning them with the intent names (`error` → `danger`) is a breaking change; it is a candidate for the next major, together with removing `--status-*` and `--alert-*`. ## Out of scope diff --git a/docs-manifest.json b/docs-manifest.json index 6ae4136fa..a44f3c5df 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -1284,9 +1284,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "2cb90564eed46574", + "outline": "a311d564be01189e", "snapshot": null, - "outputMd": "e88076f27520b951", + "outputMd": "5245dcdd9a823975", "examples": "d3cca347b055a503" } }, @@ -1656,7 +1656,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": "2f3ff3f095c2b1cc", "packages/core/skills/app-shell-patterns/references/concepts/sidebar-navigation.md": "dc6b3afc049b4148", - "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "e88076f27520b951", + "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "5245dcdd9a823975", "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 eb3177a04..8b878a20e 100644 --- a/docs-src/concepts/styling-theming.docs.outline.md +++ b/docs-src/concepts/styling-theming.docs.outline.md @@ -158,17 +158,17 @@ There are no `-hover` or `-active` brand tokens. Express interaction states with #### Status -Five status colors for custom surfaces. They are aliases of the [semantic color roles](#semantic-color-roles) below, so status colors and Alert colors agree by default: +**Deprecated.** These five tokens and their utilities remain for existing code and will be removed in the next major. New code uses the [semantic color roles](#semantic-color-roles) below. In the default palette they are aliases of those roles, so status colors and Alert colors agree: -| Token | Use | Tailwind | -| -------------------- | ---------------------------------------------------- | --------------------- | -| `--status-default` | none / not applicable (follows `--muted-foreground`) | `bg-status-default` | -| `--status-neutral` | informational | `bg-status-neutral` | -| `--status-completed` | success, completed | `bg-status-completed` | -| `--status-attention` | warning, needs attention | `bg-status-attention` | -| `--status-danger` | error, blocked | `bg-status-danger` | +| Token | Use | Tailwind | Use instead | +| -------------------- | ---------------------------------------------------- | --------------------- | ---------------------- | +| `--status-default` | none / not applicable (follows `--muted-foreground`) | `bg-status-default` | `bg-neutral-indicator` | +| `--status-neutral` | informational | `bg-status-neutral` | `bg-info-solid` | +| `--status-completed` | success, completed | `bg-status-completed` | `bg-success-solid` | +| `--status-attention` | warning, needs attention | `bg-status-attention` | `bg-warning-solid` | +| `--status-danger` | error, blocked | `bg-status-danger` | `bg-danger-solid` | -Prefer `Badge` with a semantic variant (`success`, `warning`, `error`, `info`, `neutral`) over applying these directly — the variants already pair fill and foreground correctly. Reach for the raw token only on custom surfaces. +Prefer `Badge` with a semantic variant (`success`, `warning`, `error`, `info`, `neutral`) over applying a fill directly — the variants already pair fill and foreground correctly. These are fill and indicator colors. `--status-default` follows `--muted-foreground`, so it is theme-dependent and translucent in `cream` and `bloom`; the four hue tokens are opaque in every palette. Do not use them as text color: `--status-completed`, `--status-danger` and `--status-neutral` are below 4.5:1 on a dark card, and `--status-attention` is below 3:1 on a light one. For text, use `text-{intent}-text`. @@ -205,7 +205,7 @@ Most `surface` and `border` values are translucent tints, so an opacity modifier #### Alerts -`--alert-{neutral,success,warning,error,info}-{background,foreground,foreground-muted,border}` and the matching utilities (`bg-alert-info-background`, `text-alert-info-foreground`, ...) remain for existing code. In the default palette they are aliases of the semantic color roles: `background` is `surface`, `border` is `border`, `foreground` is `text`, and `foreground-muted` follows `foreground`; `error` reads the `danger` intent. The `Alert` component reads the roles, so overriding `--alert-*` changes these utilities but not `Alert`. +**Deprecated.** `--alert-{neutral,success,warning,error,info}-{background,foreground,foreground-muted,border}` and the matching utilities (`bg-alert-info-background`, `text-alert-info-foreground`, ...) remain for existing code and will be removed in the next major. In the default palette they are aliases of the semantic color roles: `background` is `surface`, `border` is `border`, `foreground` is `text`, and `foreground-muted` follows `foreground`; `error` reads the `danger` intent. The `Alert` component reads the roles, so overriding `--alert-*` changes these utilities but not `Alert`. For new custom surfaces, use the roles instead of the alert slots: diff --git a/docs/concepts/styling-theming.md b/docs/concepts/styling-theming.md index 978d89dac..93eeb8174 100644 --- a/docs/concepts/styling-theming.md +++ b/docs/concepts/styling-theming.md @@ -175,17 +175,17 @@ There are no `-hover` or `-active` brand tokens. Express interaction states with #### Status -Five status colors for custom surfaces. They are aliases of the [semantic color roles](#semantic-color-roles) below, so status colors and Alert colors agree by default: +**Deprecated.** These five tokens and their utilities remain for existing code and will be removed in the next major. New code uses the [semantic color roles](#semantic-color-roles) below. In the default palette they are aliases of those roles, so status colors and Alert colors agree: -| Token | Use | Tailwind | -| -------------------- | ---------------------------------------------------- | --------------------- | -| `--status-default` | none / not applicable (follows `--muted-foreground`) | `bg-status-default` | -| `--status-neutral` | informational | `bg-status-neutral` | -| `--status-completed` | success, completed | `bg-status-completed` | -| `--status-attention` | warning, needs attention | `bg-status-attention` | -| `--status-danger` | error, blocked | `bg-status-danger` | +| Token | Use | Tailwind | Use instead | +| -------------------- | ---------------------------------------------------- | --------------------- | ---------------------- | +| `--status-default` | none / not applicable (follows `--muted-foreground`) | `bg-status-default` | `bg-neutral-indicator` | +| `--status-neutral` | informational | `bg-status-neutral` | `bg-info-solid` | +| `--status-completed` | success, completed | `bg-status-completed` | `bg-success-solid` | +| `--status-attention` | warning, needs attention | `bg-status-attention` | `bg-warning-solid` | +| `--status-danger` | error, blocked | `bg-status-danger` | `bg-danger-solid` | -Prefer `Badge` with a semantic variant (`success`, `warning`, `error`, `info`, `neutral`) over applying these directly — the variants already pair fill and foreground correctly. Reach for the raw token only on custom surfaces. +Prefer `Badge` with a semantic variant (`success`, `warning`, `error`, `info`, `neutral`) over applying a fill directly — the variants already pair fill and foreground correctly. These are fill and indicator colors. `--status-default` follows `--muted-foreground`, so it is theme-dependent and translucent in `cream` and `bloom`; the four hue tokens are opaque in every palette. Do not use them as text color: `--status-completed`, `--status-danger` and `--status-neutral` are below 4.5:1 on a dark card, and `--status-attention` is below 3:1 on a light one. For text, use `text-{intent}-text`. @@ -222,7 +222,7 @@ Most `surface` and `border` values are translucent tints, so an opacity modifier #### Alerts -`--alert-{neutral,success,warning,error,info}-{background,foreground,foreground-muted,border}` and the matching utilities (`bg-alert-info-background`, `text-alert-info-foreground`, ...) remain for existing code. In the default palette they are aliases of the semantic color roles: `background` is `surface`, `border` is `border`, `foreground` is `text`, and `foreground-muted` follows `foreground`; `error` reads the `danger` intent. The `Alert` component reads the roles, so overriding `--alert-*` changes these utilities but not `Alert`. +**Deprecated.** `--alert-{neutral,success,warning,error,info}-{background,foreground,foreground-muted,border}` and the matching utilities (`bg-alert-info-background`, `text-alert-info-foreground`, ...) remain for existing code and will be removed in the next major. In the default palette they are aliases of the semantic color roles: `background` is `surface`, `border` is `border`, `foreground` is `text`, and `foreground-muted` follows `foreground`; `error` reads the `danger` intent. The `Alert` component reads the roles, so overriding `--alert-*` changes these utilities but not `Alert`. For new custom surfaces, use the roles instead of the alert slots: From 21f5ef50a38169bb0e187168cb00332189af41b5 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Fri, 2 Oct 2026 09:38:10 +0900 Subject: [PATCH 24/29] docs(core): recommend the colour roles in the state and decision examples Co-Authored-By: Claude Fable 5.1 --- docs-manifest.json | 6 +++--- docs-src/concepts/styling-theming.docs.outline.md | 8 ++++---- docs/concepts/styling-theming.md | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs-manifest.json b/docs-manifest.json index a44f3c5df..5b7f144c6 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -1284,9 +1284,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "a311d564be01189e", + "outline": "877fed470e8a6e66", "snapshot": null, - "outputMd": "5245dcdd9a823975", + "outputMd": "eed1d4b3100db421", "examples": "d3cca347b055a503" } }, @@ -1656,7 +1656,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": "2f3ff3f095c2b1cc", "packages/core/skills/app-shell-patterns/references/concepts/sidebar-navigation.md": "dc6b3afc049b4148", - "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "5245dcdd9a823975", + "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "eed1d4b3100db421", "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 8b878a20e..4f6782b74 100644 --- a/docs-src/concepts/styling-theming.docs.outline.md +++ b/docs-src/concepts/styling-theming.docs.outline.md @@ -506,7 +506,7 @@ AppShell's UI components support data-attribute-based styling, following the [Ba ```css /* Style a component based on its state */ .SwitchThumb[data-checked] { - background-color: var(--status-completed); + background-color: var(--success-solid); } .MenuItem[data-highlighted] { @@ -577,9 +577,9 @@ These are visual-composition rules every screen must follow, regardless of patte | Intent | Pick | | ---------------------------------- | ------------------------------------------------------------------------------------------------------- | | Destructive action (delete, void) | `Button variant="destructive"`; `bg-destructive` on custom surfaces; confirm in a dialog at `shadow-lg` | -| Non-blocking caution | `Badge variant="warning"`, or `bg-status-attention` | -| Confirmation / completed state | `Badge variant="success"`, or `bg-status-completed` | -| Neutral callout | `Badge variant="info"`, or the `Alert` component | +| Non-blocking caution | `Badge variant="warning"`, or `bg-warning-surface text-warning-text` | +| Confirmation / completed state | `Badge variant="success"`, or `bg-success-surface text-success-text` | +| Neutral callout | `Badge variant="info"`, or `Alert variant="info"` | | Persistent panel (sidebar, header) | `shadow-sm` | | Hovered / sticky surface | `shadow-md` | | Popover / menu / tooltip | `bg-popover`, `shadow-md`, `duration-150` | diff --git a/docs/concepts/styling-theming.md b/docs/concepts/styling-theming.md index 93eeb8174..c15bc32d7 100644 --- a/docs/concepts/styling-theming.md +++ b/docs/concepts/styling-theming.md @@ -523,7 +523,7 @@ AppShell's UI components support data-attribute-based styling, following the [Ba ```css /* Style a component based on its state */ .SwitchThumb[data-checked] { - background-color: var(--status-completed); + background-color: var(--success-solid); } .MenuItem[data-highlighted] { @@ -594,9 +594,9 @@ These are visual-composition rules every screen must follow, regardless of patte | Intent | Pick | | ---------------------------------- | ------------------------------------------------------------------------------------------------------- | | Destructive action (delete, void) | `Button variant="destructive"`; `bg-destructive` on custom surfaces; confirm in a dialog at `shadow-lg` | -| Non-blocking caution | `Badge variant="warning"`, or `bg-status-attention` | -| Confirmation / completed state | `Badge variant="success"`, or `bg-status-completed` | -| Neutral callout | `Badge variant="info"`, or the `Alert` component | +| Non-blocking caution | `Badge variant="warning"`, or `bg-warning-surface text-warning-text` | +| Confirmation / completed state | `Badge variant="success"`, or `bg-success-surface text-success-text` | +| Neutral callout | `Badge variant="info"`, or `Alert variant="info"` | | Persistent panel (sidebar, header) | `shadow-sm` | | Hovered / sticky surface | `shadow-md` | | Popover / menu / tooltip | `bg-popover`, `shadow-md`, `duration-150` | From 1d6733d225aeade7488c98951550146c9c1d51c8 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Fri, 2 Oct 2026 09:39:30 +0900 Subject: [PATCH 25/29] docs(core): separate the neutral and informational callouts; split the changeset bullets Co-Authored-By: Claude Fable 5.1 --- .changeset/calm-hues-agree.md | 6 ++++-- docs-manifest.json | 6 +++--- docs-src/concepts/styling-theming.docs.outline.md | 5 +++-- docs/concepts/styling-theming.md | 5 +++-- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.changeset/calm-hues-agree.md b/.changeset/calm-hues-agree.md index 3e5cf9470..8a90361a1 100644 --- a/.changeset/calm-hues-agree.md +++ b/.changeset/calm-hues-agree.md @@ -12,7 +12,9 @@ A gray scale (`--primitive-gray-{50..950}`, Tailwind neutral) is also added; the Visible changes in the default palette: -- `--status-*` and `--alert-*` are deprecated. `bg-status-*` and `bg-alert-*` keep working and change values; they will be removed in the next major. New code uses the roles. `--status-default` now follows `--muted-foreground` (it was a fixed `#737373`), so it is theme-dependent and translucent in the `cream` and `bloom` palettes. `--status-*` are fill colours; do not use them as text (below 4.5:1 in dark mode). Use `text-{intent}-text` instead (for example `text-status-completed` becomes `text-success-text`). -- Badge, Alert, CsvImporter and MetricCard read the semantic roles instead of `--status-*` and `--alert-*`. To recolour them, override `--{intent}-*`; overriding `--status-*` or `--alert-*` no longer changes these components. The old names remain for your own `bg-status-*` / `bg-alert-*` utilities. +- `--status-*` and `--alert-*` are deprecated and will be removed in the next major. `bg-status-*` and `bg-alert-*` keep working; new code uses the roles. +- `bg-status-*` and `bg-alert-*` change values. `--status-default` now follows `--muted-foreground` (it was a fixed `#737373`), so it is theme-dependent and translucent in the `cream` and `bloom` palettes. +- `--status-*` are fill colours; do not use them as text (below 4.5:1 in dark mode). Use `text-{intent}-text` instead (for example `text-status-completed` becomes `text-success-text`). +- Badge, Alert, CsvImporter and MetricCard read the semantic roles. To recolour them, override `--{intent}-*`; overriding `--status-*` or `--alert-*` no longer changes these components. - Badge `error` and `subtle-error`, and Alert `error`, follow `--danger-*` instead of `--destructive`. Button and `text-destructive` still follow `--destructive`. - The warning Badge text is dark instead of white. Alert description text now has the same colour as the Alert title. diff --git a/docs-manifest.json b/docs-manifest.json index 5b7f144c6..a7a14f63c 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -1284,9 +1284,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "877fed470e8a6e66", + "outline": "c8a79ca086341d8c", "snapshot": null, - "outputMd": "eed1d4b3100db421", + "outputMd": "1277e06bb82432e6", "examples": "d3cca347b055a503" } }, @@ -1656,7 +1656,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": "2f3ff3f095c2b1cc", "packages/core/skills/app-shell-patterns/references/concepts/sidebar-navigation.md": "dc6b3afc049b4148", - "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "eed1d4b3100db421", + "packages/core/skills/app-shell-patterns/references/concepts/styling-theming.md": "1277e06bb82432e6", "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 4f6782b74..dff987ad7 100644 --- a/docs-src/concepts/styling-theming.docs.outline.md +++ b/docs-src/concepts/styling-theming.docs.outline.md @@ -11,7 +11,7 @@ Styling is done with **Tailwind CSS v4** against `@tailor-platform/app-shell`'s **The tokens are the rails.** Consistency across customers, apps, and AI runs comes from the token system, not from rules written in prose. A hand-typed `#fff` or `padding: 13px` is not a "small deviation" — it is the mechanism by which consistency dies. -**Every token in this document is verified against the shipped CSS.** If a token is not listed here, assume it does not exist. The hue scales (`--primitive-*`) are internal: they are not bridged to Tailwind and are not listed. Inventing a plausible-sounding token (`bg-surface-1`, `text-fg-muted`, `--space-4`) is the worst failure mode available: Tailwind emits **no CSS at all** for an unknown utility, so the class is silently dropped and the element renders unstyled — no error, no warning, nothing in the console. When unsure, read `node_modules/@tailor-platform/app-shell/dist/themes/default.css`; it is the ground truth. +**Every token in this document is verified against the shipped CSS.** If a token is not listed here, assume it does not exist. The colour scales (`--primitive-*`) are internal: they are not bridged to Tailwind and are not listed. Inventing a plausible-sounding token (`bg-surface-1`, `text-fg-muted`, `--space-4`) is the worst failure mode available: Tailwind emits **no CSS at all** for an unknown utility, so the class is silently dropped and the element renders unstyled — no error, no warning, nothing in the console. When unsure, read `node_modules/@tailor-platform/app-shell/dist/themes/default.css`; it is the ground truth. ## Setup @@ -579,7 +579,8 @@ These are visual-composition rules every screen must follow, regardless of patte | Destructive action (delete, void) | `Button variant="destructive"`; `bg-destructive` on custom surfaces; confirm in a dialog at `shadow-lg` | | Non-blocking caution | `Badge variant="warning"`, or `bg-warning-surface text-warning-text` | | Confirmation / completed state | `Badge variant="success"`, or `bg-success-surface text-success-text` | -| Neutral callout | `Badge variant="info"`, or `Alert variant="info"` | +| Informational callout | `Badge variant="info"`, or `Alert variant="info"` | +| Neutral callout | `Badge variant="neutral"`, or `Alert variant="neutral"` | | Persistent panel (sidebar, header) | `shadow-sm` | | Hovered / sticky surface | `shadow-md` | | Popover / menu / tooltip | `bg-popover`, `shadow-md`, `duration-150` | diff --git a/docs/concepts/styling-theming.md b/docs/concepts/styling-theming.md index c15bc32d7..b553fc46d 100644 --- a/docs/concepts/styling-theming.md +++ b/docs/concepts/styling-theming.md @@ -11,7 +11,7 @@ Styling is done with **Tailwind CSS v4** against `@tailor-platform/app-shell`'s **The tokens are the rails.** Consistency across customers, apps, and AI runs comes from the token system, not from rules written in prose. A hand-typed `#fff` or `padding: 13px` is not a "small deviation" — it is the mechanism by which consistency dies. -**Every token in this document is verified against the shipped CSS.** If a token is not listed here, assume it does not exist. The hue scales (`--primitive-*`) are internal: they are not bridged to Tailwind and are not listed. Inventing a plausible-sounding token (`bg-surface-1`, `text-fg-muted`, `--space-4`) is the worst failure mode available: Tailwind emits **no CSS at all** for an unknown utility, so the class is silently dropped and the element renders unstyled — no error, no warning, nothing in the console. When unsure, read `node_modules/@tailor-platform/app-shell/dist/themes/default.css`; it is the ground truth. +**Every token in this document is verified against the shipped CSS.** If a token is not listed here, assume it does not exist. The colour scales (`--primitive-*`) are internal: they are not bridged to Tailwind and are not listed. Inventing a plausible-sounding token (`bg-surface-1`, `text-fg-muted`, `--space-4`) is the worst failure mode available: Tailwind emits **no CSS at all** for an unknown utility, so the class is silently dropped and the element renders unstyled — no error, no warning, nothing in the console. When unsure, read `node_modules/@tailor-platform/app-shell/dist/themes/default.css`; it is the ground truth. ## Setup @@ -596,7 +596,8 @@ These are visual-composition rules every screen must follow, regardless of patte | Destructive action (delete, void) | `Button variant="destructive"`; `bg-destructive` on custom surfaces; confirm in a dialog at `shadow-lg` | | Non-blocking caution | `Badge variant="warning"`, or `bg-warning-surface text-warning-text` | | Confirmation / completed state | `Badge variant="success"`, or `bg-success-surface text-success-text` | -| Neutral callout | `Badge variant="info"`, or `Alert variant="info"` | +| Informational callout | `Badge variant="info"`, or `Alert variant="info"` | +| Neutral callout | `Badge variant="neutral"`, or `Alert variant="neutral"` | | Persistent panel (sidebar, header) | `shadow-sm` | | Hovered / sticky surface | `shadow-md` | | Popover / menu / tooltip | `bg-popover`, `shadow-md`, `duration-150` | From 66a2f7ec56b98cdceed8c7d4445f9a087e55a878 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Fri, 2 Oct 2026 20:47:15 +0900 Subject: [PATCH 26/29] docs(migrations): add the 1.17.0 entry for the status colour roles Requested in review: the old tokens keep working, but their values and the customization contract for Badge, Alert, CsvImporter and MetricCard change. Co-Authored-By: Claude Fable 5.1 --- docs-manifest.json | 14 +++---- docs-src/guides/migrations.docs.outline.md | 45 ++++++++++++++++------ docs/migrations.md | 45 ++++++++++++++++------ 3 files changed, 73 insertions(+), 31 deletions(-) diff --git a/docs-manifest.json b/docs-manifest.json index a7a14f63c..5f3e7da2e 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -605,7 +605,7 @@ "hashes": { "typeSurface": "b1614970f950ceb6", "outline": "bda40376d6e3e008", - "snapshot": "d4fa33f2da19d5ee", + "snapshot": "b845d4c54453b0f0", "outputMd": "ec6a0ac098d1d87a", "examples": "934c52d4e8761404" } @@ -1013,7 +1013,7 @@ "hashes": { "typeSurface": "943616def28cafb0", "outline": "8f06df28129676c8", - "snapshot": "02dfd02003105939", + "snapshot": "6233cd9de511210f", "outputMd": "ceb4a05822538d20", "examples": "cff0508bb5520ae6" } @@ -1046,9 +1046,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "ef857eb4425c36cf", + "outline": "1910bd8718ea6b25", "snapshot": null, - "outputMd": "87e819351e53b6bd", + "outputMd": "397a696ec2822549", "examples": null } }, @@ -1166,7 +1166,7 @@ "hashes": { "typeSurface": "429710973dfa912b", "outline": "721e8995f53f0e8f", - "snapshot": "f6fcbd73e127f2f4", + "snapshot": "0ceac9786fb4ce3c", "outputMd": "ae287e2bb5abc1c6", "examples": "1ce89cec30045466" } @@ -1183,7 +1183,7 @@ "hashes": { "typeSurface": "e760949efcbe4c38", "outline": "464d91acc48f7c9d", - "snapshot": "12c20fc6f44d1b4c", + "snapshot": "40d15c3f1003fdb0", "outputMd": "1f81ebd562c50371", "examples": "b0eebb8c5e2219dc" } @@ -1735,7 +1735,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": "8062ea0df9403fbb", "packages/core/skills/app-shell-patterns/references/pages/document-detail.md": "c3fb2588a0699c26", - "packages/core/skills/app-shell-patterns/references/migrations.md": "20fb5d88b76dd933", + "packages/core/skills/app-shell-patterns/references/migrations.md": "be5cf59a00aac335", "packages/core/skills/app-shell-patterns/SKILL.md": "814325f4ea06ffae" } } diff --git a/docs-src/guides/migrations.docs.outline.md b/docs-src/guides/migrations.docs.outline.md index 6fc02f02f..b15926b89 100644 --- a/docs-src/guides/migrations.docs.outline.md +++ b/docs-src/guides/migrations.docs.outline.md @@ -13,18 +13,39 @@ This page is deliberately narrow. It is **not** a changelog — see [`packages/c Each entry states which versions are affected, what breaks, how to detect it, and what to change. Entries stay here permanently; they are not pruned when they get old, because apps upgrade across arbitrary version gaps. -| Version | Change | -| ------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| 1.15.0 | [Base UI 1.8.0 breaks closed-`Select` and dialog-click tests](#1150-base-ui-180-breaks-closed-select-and-dialog-click-tests) | -| 1.12.0 | [`DateField` / `DatePicker` field chrome moved to `Field.Root`](#1120-datefield--datepicker-field-chrome-moved-to-fieldroot) | -| 1.11.0 | [React 19.2.7 and React Router v8 required](#1110-react-1927-and-react-router-v8-are-now-required) | -| 1.11.0 | [Non-modal `Sheet` renders no backdrop](#1110-non-modal-sheet-no-longer-renders-a-backdrop) | -| 1.8.0 | [`stream` removed from `useAIChat()`](#180-stream-removed-from-useaichat) | -| 1.5.0 → 1.7.0 | [Remove the theme bridge workaround](#150--170-remove-the-theme-bridge-workaround) | -| 1.5.0 | [`loader` removed from file-based pages](#150-loader-removed-from-file-based-page-definitions) | -| 1.3.0 | [Column inference and badge defaults changed](#130-column-inference-and-badge-defaults-changed) | -| 1.0.2 | [`Toaster` no longer accepts `richColors`](#102-toaster-no-longer-accepts-richcolors) | -| before 1.0 | [Pre-1.0 breaking changes](#before-10) | +| Version | Change | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| 1.17.0 | [Status colour tokens become aliases of the semantic colour roles](#1170-status-colour-tokens-become-aliases-of-the-semantic-colour-roles) | +| 1.15.0 | [Base UI 1.8.0 breaks closed-`Select` and dialog-click tests](#1150-base-ui-180-breaks-closed-select-and-dialog-click-tests) | +| 1.12.0 | [`DateField` / `DatePicker` field chrome moved to `Field.Root`](#1120-datefield--datepicker-field-chrome-moved-to-fieldroot) | +| 1.11.0 | [React 19.2.7 and React Router v8 required](#1110-react-1927-and-react-router-v8-are-now-required) | +| 1.11.0 | [Non-modal `Sheet` renders no backdrop](#1110-non-modal-sheet-no-longer-renders-a-backdrop) | +| 1.8.0 | [`stream` removed from `useAIChat()`](#180-stream-removed-from-useaichat) | +| 1.5.0 → 1.7.0 | [Remove the theme bridge workaround](#150--170-remove-the-theme-bridge-workaround) | +| 1.5.0 | [`loader` removed from file-based pages](#150-loader-removed-from-file-based-page-definitions) | +| 1.3.0 | [Column inference and badge defaults changed](#130-column-inference-and-badge-defaults-changed) | +| 1.0.2 | [`Toaster` no longer accepts `richColors`](#102-toaster-no-longer-accepts-richcolors) | +| before 1.0 | [Pre-1.0 breaking changes](#before-10) | + +## 1.17.0: status colour tokens become aliases of the semantic colour roles + +**Applies to:** apps that use `bg-status-*`, `text-status-*` or `bg-alert-*` utilities, and apps whose theme overrides `--status-*`, `--alert-*` or `--destructive` to recolour `Badge`, `Alert`, `CsvImporter` or `MetricCard`. + +1.17.0 adds semantic colour roles, `--{info|success|warning|danger|neutral}-{surface|surface-hover|border|solid|solid-hover|text|contrast|indicator}`, bridged as Tailwind colours such as `bg-success-surface` and `text-danger-text`. The old `--status-*` and `--alert-*` tokens still exist and still work, but they are now aliases of the roles and are deprecated; they will be removed in the next major. Nothing errors. Three things change silently: + +- **Colours change.** `bg-status-*` and `bg-alert-*` resolve to the role values. For example `bg-status-completed` is `#15803d` instead of `#22c55e`. `--status-default` follows `--muted-foreground` instead of a fixed `#737373`. +- **Component recolouring moves to the roles.** `Badge`, `Alert`, `CsvImporter` and `MetricCard` read the roles directly. A theme that overrides `--status-*` or `--alert-*` no longer changes these components; override `--{intent}-*` instead. `Badge` `error` / `subtle-error` and `Alert` `error` read `--danger-*`, so overriding `--destructive` no longer reaches them either. `Button variant="destructive"` and `text-destructive` still follow `--destructive`. +- **`text-status-*` loses contrast in dark mode.** The `--status-*` values are now the step-9 fills, which are below 4.5:1 as text on the dark card. + +How to detect it: search the app for `status-`, `alert-` and theme overrides of `--status-`, `--alert-` and `--destructive`. Compare `Badge` and `Alert` in light and dark mode after upgrading. + +What to change: + +- Replace `text-status-completed` with `text-success-text`, `text-status-attention` with `text-warning-text`, `text-status-danger` with `text-danger-text`, and `text-status-neutral` with `text-info-text`. +- Replace `bg-status-completed/10 border-status-completed/30` style tints with `bg-success-surface border-success-border`. The same applies to the other intents and to `bg-alert-*` / `border-alert-*`. +- In a theme that recolours the status components, set the roles (`--success-solid`, `--danger-text`, ...) instead of `--status-*`, `--alert-*` or `--destructive`. + +The roles are documented in [Styling & theming](../concepts/styling-theming.md#semantic-color-roles). The decision record is `decisions/semantic-color-roles.md`. ## 1.15.0: Base UI 1.8.0 breaks closed-`Select` and dialog-click tests diff --git a/docs/migrations.md b/docs/migrations.md index 3e59451ae..50e510b5a 100644 --- a/docs/migrations.md +++ b/docs/migrations.md @@ -13,18 +13,39 @@ This page is deliberately narrow. It is **not** a changelog — see [`packages/c Each entry states which versions are affected, what breaks, how to detect it, and what to change. Entries stay here permanently; they are not pruned when they get old, because apps upgrade across arbitrary version gaps. -| Version | Change | -| ------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| 1.15.0 | [Base UI 1.8.0 breaks closed-`Select` and dialog-click tests](#1150-base-ui-180-breaks-closed-select-and-dialog-click-tests) | -| 1.12.0 | [`DateField` / `DatePicker` field chrome moved to `Field.Root`](#1120-datefield--datepicker-field-chrome-moved-to-fieldroot) | -| 1.11.0 | [React 19.2.7 and React Router v8 required](#1110-react-1927-and-react-router-v8-are-now-required) | -| 1.11.0 | [Non-modal `Sheet` renders no backdrop](#1110-non-modal-sheet-no-longer-renders-a-backdrop) | -| 1.8.0 | [`stream` removed from `useAIChat()`](#180-stream-removed-from-useaichat) | -| 1.5.0 → 1.7.0 | [Remove the theme bridge workaround](#150--170-remove-the-theme-bridge-workaround) | -| 1.5.0 | [`loader` removed from file-based pages](#150-loader-removed-from-file-based-page-definitions) | -| 1.3.0 | [Column inference and badge defaults changed](#130-column-inference-and-badge-defaults-changed) | -| 1.0.2 | [`Toaster` no longer accepts `richColors`](#102-toaster-no-longer-accepts-richcolors) | -| before 1.0 | [Pre-1.0 breaking changes](#before-10) | +| Version | Change | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| 1.17.0 | [Status colour tokens become aliases of the semantic colour roles](#1170-status-colour-tokens-become-aliases-of-the-semantic-colour-roles) | +| 1.15.0 | [Base UI 1.8.0 breaks closed-`Select` and dialog-click tests](#1150-base-ui-180-breaks-closed-select-and-dialog-click-tests) | +| 1.12.0 | [`DateField` / `DatePicker` field chrome moved to `Field.Root`](#1120-datefield--datepicker-field-chrome-moved-to-fieldroot) | +| 1.11.0 | [React 19.2.7 and React Router v8 required](#1110-react-1927-and-react-router-v8-are-now-required) | +| 1.11.0 | [Non-modal `Sheet` renders no backdrop](#1110-non-modal-sheet-no-longer-renders-a-backdrop) | +| 1.8.0 | [`stream` removed from `useAIChat()`](#180-stream-removed-from-useaichat) | +| 1.5.0 → 1.7.0 | [Remove the theme bridge workaround](#150--170-remove-the-theme-bridge-workaround) | +| 1.5.0 | [`loader` removed from file-based pages](#150-loader-removed-from-file-based-page-definitions) | +| 1.3.0 | [Column inference and badge defaults changed](#130-column-inference-and-badge-defaults-changed) | +| 1.0.2 | [`Toaster` no longer accepts `richColors`](#102-toaster-no-longer-accepts-richcolors) | +| before 1.0 | [Pre-1.0 breaking changes](#before-10) | + +## 1.17.0: status colour tokens become aliases of the semantic colour roles + +**Applies to:** apps that use `bg-status-*`, `text-status-*` or `bg-alert-*` utilities, and apps whose theme overrides `--status-*`, `--alert-*` or `--destructive` to recolour `Badge`, `Alert`, `CsvImporter` or `MetricCard`. + +1.17.0 adds semantic colour roles, `--{info|success|warning|danger|neutral}-{surface|surface-hover|border|solid|solid-hover|text|contrast|indicator}`, bridged as Tailwind colours such as `bg-success-surface` and `text-danger-text`. The old `--status-*` and `--alert-*` tokens still exist and still work, but they are now aliases of the roles and are deprecated; they will be removed in the next major. Nothing errors. Three things change silently: + +- **Colours change.** `bg-status-*` and `bg-alert-*` resolve to the role values. For example `bg-status-completed` is `#15803d` instead of `#22c55e`. `--status-default` follows `--muted-foreground` instead of a fixed `#737373`. +- **Component recolouring moves to the roles.** `Badge`, `Alert`, `CsvImporter` and `MetricCard` read the roles directly. A theme that overrides `--status-*` or `--alert-*` no longer changes these components; override `--{intent}-*` instead. `Badge` `error` / `subtle-error` and `Alert` `error` read `--danger-*`, so overriding `--destructive` no longer reaches them either. `Button variant="destructive"` and `text-destructive` still follow `--destructive`. +- **`text-status-*` loses contrast in dark mode.** The `--status-*` values are now the step-9 fills, which are below 4.5:1 as text on the dark card. + +How to detect it: search the app for `status-`, `alert-` and theme overrides of `--status-`, `--alert-` and `--destructive`. Compare `Badge` and `Alert` in light and dark mode after upgrading. + +What to change: + +- Replace `text-status-completed` with `text-success-text`, `text-status-attention` with `text-warning-text`, `text-status-danger` with `text-danger-text`, and `text-status-neutral` with `text-info-text`. +- Replace `bg-status-completed/10 border-status-completed/30` style tints with `bg-success-surface border-success-border`. The same applies to the other intents and to `bg-alert-*` / `border-alert-*`. +- In a theme that recolours the status components, set the roles (`--success-solid`, `--danger-text`, ...) instead of `--status-*`, `--alert-*` or `--destructive`. + +The roles are documented in [Styling & theming](../concepts/styling-theming.md#semantic-color-roles). The decision record is `decisions/semantic-color-roles.md`. ## 1.15.0: Base UI 1.8.0 breaks closed-`Select` and dialog-click tests From 8525617264b5e18a4d5b6f29b16f83173b536d6b Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Fri, 2 Oct 2026 20:56:24 +0900 Subject: [PATCH 27/29] docs(migrations): retitle the 1.17.0 entry and keep the table width Co-Authored-By: Claude Fable 5.1 --- docs-manifest.json | 6 ++-- docs-src/guides/migrations.docs.outline.md | 32 +++++++++++----------- docs/migrations.md | 32 +++++++++++----------- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/docs-manifest.json b/docs-manifest.json index 5f3e7da2e..e7eeff609 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -1046,9 +1046,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "1910bd8718ea6b25", + "outline": "8e444a0229794e03", "snapshot": null, - "outputMd": "397a696ec2822549", + "outputMd": "24b4fb6777fa0ad6", "examples": null } }, @@ -1735,7 +1735,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": "8062ea0df9403fbb", "packages/core/skills/app-shell-patterns/references/pages/document-detail.md": "c3fb2588a0699c26", - "packages/core/skills/app-shell-patterns/references/migrations.md": "be5cf59a00aac335", + "packages/core/skills/app-shell-patterns/references/migrations.md": "d78e466e836d9186", "packages/core/skills/app-shell-patterns/SKILL.md": "814325f4ea06ffae" } } diff --git a/docs-src/guides/migrations.docs.outline.md b/docs-src/guides/migrations.docs.outline.md index b15926b89..2a7d1c216 100644 --- a/docs-src/guides/migrations.docs.outline.md +++ b/docs-src/guides/migrations.docs.outline.md @@ -13,25 +13,25 @@ This page is deliberately narrow. It is **not** a changelog — see [`packages/c Each entry states which versions are affected, what breaks, how to detect it, and what to change. Entries stay here permanently; they are not pruned when they get old, because apps upgrade across arbitrary version gaps. -| Version | Change | -| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -| 1.17.0 | [Status colour tokens become aliases of the semantic colour roles](#1170-status-colour-tokens-become-aliases-of-the-semantic-colour-roles) | -| 1.15.0 | [Base UI 1.8.0 breaks closed-`Select` and dialog-click tests](#1150-base-ui-180-breaks-closed-select-and-dialog-click-tests) | -| 1.12.0 | [`DateField` / `DatePicker` field chrome moved to `Field.Root`](#1120-datefield--datepicker-field-chrome-moved-to-fieldroot) | -| 1.11.0 | [React 19.2.7 and React Router v8 required](#1110-react-1927-and-react-router-v8-are-now-required) | -| 1.11.0 | [Non-modal `Sheet` renders no backdrop](#1110-non-modal-sheet-no-longer-renders-a-backdrop) | -| 1.8.0 | [`stream` removed from `useAIChat()`](#180-stream-removed-from-useaichat) | -| 1.5.0 → 1.7.0 | [Remove the theme bridge workaround](#150--170-remove-the-theme-bridge-workaround) | -| 1.5.0 | [`loader` removed from file-based pages](#150-loader-removed-from-file-based-page-definitions) | -| 1.3.0 | [Column inference and badge defaults changed](#130-column-inference-and-badge-defaults-changed) | -| 1.0.2 | [`Toaster` no longer accepts `richColors`](#102-toaster-no-longer-accepts-richcolors) | -| before 1.0 | [Pre-1.0 breaking changes](#before-10) | - -## 1.17.0: status colour tokens become aliases of the semantic colour roles +| Version | Change | +| ------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| 1.17.0 | [Status and alert tokens now resolve to the colour roles](#1170-status-and-alert-tokens-now-resolve-to-the-colour-roles) | +| 1.15.0 | [Base UI 1.8.0 breaks closed-`Select` and dialog-click tests](#1150-base-ui-180-breaks-closed-select-and-dialog-click-tests) | +| 1.12.0 | [`DateField` / `DatePicker` field chrome moved to `Field.Root`](#1120-datefield--datepicker-field-chrome-moved-to-fieldroot) | +| 1.11.0 | [React 19.2.7 and React Router v8 required](#1110-react-1927-and-react-router-v8-are-now-required) | +| 1.11.0 | [Non-modal `Sheet` renders no backdrop](#1110-non-modal-sheet-no-longer-renders-a-backdrop) | +| 1.8.0 | [`stream` removed from `useAIChat()`](#180-stream-removed-from-useaichat) | +| 1.5.0 → 1.7.0 | [Remove the theme bridge workaround](#150--170-remove-the-theme-bridge-workaround) | +| 1.5.0 | [`loader` removed from file-based pages](#150-loader-removed-from-file-based-page-definitions) | +| 1.3.0 | [Column inference and badge defaults changed](#130-column-inference-and-badge-defaults-changed) | +| 1.0.2 | [`Toaster` no longer accepts `richColors`](#102-toaster-no-longer-accepts-richcolors) | +| before 1.0 | [Pre-1.0 breaking changes](#before-10) | + +## 1.17.0: status and alert tokens now resolve to the colour roles **Applies to:** apps that use `bg-status-*`, `text-status-*` or `bg-alert-*` utilities, and apps whose theme overrides `--status-*`, `--alert-*` or `--destructive` to recolour `Badge`, `Alert`, `CsvImporter` or `MetricCard`. -1.17.0 adds semantic colour roles, `--{info|success|warning|danger|neutral}-{surface|surface-hover|border|solid|solid-hover|text|contrast|indicator}`, bridged as Tailwind colours such as `bg-success-surface` and `text-danger-text`. The old `--status-*` and `--alert-*` tokens still exist and still work, but they are now aliases of the roles and are deprecated; they will be removed in the next major. Nothing errors. Three things change silently: +1.17.0 adds semantic colour roles, `--{info|success|warning|danger|neutral}-{surface|surface-hover|border|solid|solid-hover|text|contrast|indicator}`, bridged as Tailwind colours such as `bg-success-surface` and `text-danger-text`. The old `--status-*` and `--alert-*` tokens still exist and still work, but they no longer hold their own colours: each one now resolves to a role (for example `--status-completed` is `var(--success-solid)`). They are deprecated and will be removed in the next major. Nothing errors. Three things change silently: - **Colours change.** `bg-status-*` and `bg-alert-*` resolve to the role values. For example `bg-status-completed` is `#15803d` instead of `#22c55e`. `--status-default` follows `--muted-foreground` instead of a fixed `#737373`. - **Component recolouring moves to the roles.** `Badge`, `Alert`, `CsvImporter` and `MetricCard` read the roles directly. A theme that overrides `--status-*` or `--alert-*` no longer changes these components; override `--{intent}-*` instead. `Badge` `error` / `subtle-error` and `Alert` `error` read `--danger-*`, so overriding `--destructive` no longer reaches them either. `Button variant="destructive"` and `text-destructive` still follow `--destructive`. diff --git a/docs/migrations.md b/docs/migrations.md index 50e510b5a..f88dfeb9b 100644 --- a/docs/migrations.md +++ b/docs/migrations.md @@ -13,25 +13,25 @@ This page is deliberately narrow. It is **not** a changelog — see [`packages/c Each entry states which versions are affected, what breaks, how to detect it, and what to change. Entries stay here permanently; they are not pruned when they get old, because apps upgrade across arbitrary version gaps. -| Version | Change | -| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -| 1.17.0 | [Status colour tokens become aliases of the semantic colour roles](#1170-status-colour-tokens-become-aliases-of-the-semantic-colour-roles) | -| 1.15.0 | [Base UI 1.8.0 breaks closed-`Select` and dialog-click tests](#1150-base-ui-180-breaks-closed-select-and-dialog-click-tests) | -| 1.12.0 | [`DateField` / `DatePicker` field chrome moved to `Field.Root`](#1120-datefield--datepicker-field-chrome-moved-to-fieldroot) | -| 1.11.0 | [React 19.2.7 and React Router v8 required](#1110-react-1927-and-react-router-v8-are-now-required) | -| 1.11.0 | [Non-modal `Sheet` renders no backdrop](#1110-non-modal-sheet-no-longer-renders-a-backdrop) | -| 1.8.0 | [`stream` removed from `useAIChat()`](#180-stream-removed-from-useaichat) | -| 1.5.0 → 1.7.0 | [Remove the theme bridge workaround](#150--170-remove-the-theme-bridge-workaround) | -| 1.5.0 | [`loader` removed from file-based pages](#150-loader-removed-from-file-based-page-definitions) | -| 1.3.0 | [Column inference and badge defaults changed](#130-column-inference-and-badge-defaults-changed) | -| 1.0.2 | [`Toaster` no longer accepts `richColors`](#102-toaster-no-longer-accepts-richcolors) | -| before 1.0 | [Pre-1.0 breaking changes](#before-10) | - -## 1.17.0: status colour tokens become aliases of the semantic colour roles +| Version | Change | +| ------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| 1.17.0 | [Status and alert tokens now resolve to the colour roles](#1170-status-and-alert-tokens-now-resolve-to-the-colour-roles) | +| 1.15.0 | [Base UI 1.8.0 breaks closed-`Select` and dialog-click tests](#1150-base-ui-180-breaks-closed-select-and-dialog-click-tests) | +| 1.12.0 | [`DateField` / `DatePicker` field chrome moved to `Field.Root`](#1120-datefield--datepicker-field-chrome-moved-to-fieldroot) | +| 1.11.0 | [React 19.2.7 and React Router v8 required](#1110-react-1927-and-react-router-v8-are-now-required) | +| 1.11.0 | [Non-modal `Sheet` renders no backdrop](#1110-non-modal-sheet-no-longer-renders-a-backdrop) | +| 1.8.0 | [`stream` removed from `useAIChat()`](#180-stream-removed-from-useaichat) | +| 1.5.0 → 1.7.0 | [Remove the theme bridge workaround](#150--170-remove-the-theme-bridge-workaround) | +| 1.5.0 | [`loader` removed from file-based pages](#150-loader-removed-from-file-based-page-definitions) | +| 1.3.0 | [Column inference and badge defaults changed](#130-column-inference-and-badge-defaults-changed) | +| 1.0.2 | [`Toaster` no longer accepts `richColors`](#102-toaster-no-longer-accepts-richcolors) | +| before 1.0 | [Pre-1.0 breaking changes](#before-10) | + +## 1.17.0: status and alert tokens now resolve to the colour roles **Applies to:** apps that use `bg-status-*`, `text-status-*` or `bg-alert-*` utilities, and apps whose theme overrides `--status-*`, `--alert-*` or `--destructive` to recolour `Badge`, `Alert`, `CsvImporter` or `MetricCard`. -1.17.0 adds semantic colour roles, `--{info|success|warning|danger|neutral}-{surface|surface-hover|border|solid|solid-hover|text|contrast|indicator}`, bridged as Tailwind colours such as `bg-success-surface` and `text-danger-text`. The old `--status-*` and `--alert-*` tokens still exist and still work, but they are now aliases of the roles and are deprecated; they will be removed in the next major. Nothing errors. Three things change silently: +1.17.0 adds semantic colour roles, `--{info|success|warning|danger|neutral}-{surface|surface-hover|border|solid|solid-hover|text|contrast|indicator}`, bridged as Tailwind colours such as `bg-success-surface` and `text-danger-text`. The old `--status-*` and `--alert-*` tokens still exist and still work, but they no longer hold their own colours: each one now resolves to a role (for example `--status-completed` is `var(--success-solid)`). They are deprecated and will be removed in the next major. Nothing errors. Three things change silently: - **Colours change.** `bg-status-*` and `bg-alert-*` resolve to the role values. For example `bg-status-completed` is `#15803d` instead of `#22c55e`. `--status-default` follows `--muted-foreground` instead of a fixed `#737373`. - **Component recolouring moves to the roles.** `Badge`, `Alert`, `CsvImporter` and `MetricCard` read the roles directly. A theme that overrides `--status-*` or `--alert-*` no longer changes these components; override `--{intent}-*` instead. `Badge` `error` / `subtle-error` and `Alert` `error` read `--danger-*`, so overriding `--destructive` no longer reaches them either. `Button variant="destructive"` and `text-destructive` still follow `--destructive`. From 7137fd30775ff45da4355f56badbb6b48aac0f18 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Fri, 2 Oct 2026 21:09:21 +0900 Subject: [PATCH 28/29] docs(migrations): fix the relative link to the styling guide Co-Authored-By: Claude Fable 5.1 --- docs-manifest.json | 6 +++--- docs-src/guides/migrations.docs.outline.md | 2 +- docs/migrations.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs-manifest.json b/docs-manifest.json index e7eeff609..e1afa52a5 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -1046,9 +1046,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "8e444a0229794e03", + "outline": "c967970ea4752d80", "snapshot": null, - "outputMd": "24b4fb6777fa0ad6", + "outputMd": "b12b5479d4f2368e", "examples": null } }, @@ -1735,7 +1735,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": "8062ea0df9403fbb", "packages/core/skills/app-shell-patterns/references/pages/document-detail.md": "c3fb2588a0699c26", - "packages/core/skills/app-shell-patterns/references/migrations.md": "d78e466e836d9186", + "packages/core/skills/app-shell-patterns/references/migrations.md": "013c3fef2303b096", "packages/core/skills/app-shell-patterns/SKILL.md": "814325f4ea06ffae" } } diff --git a/docs-src/guides/migrations.docs.outline.md b/docs-src/guides/migrations.docs.outline.md index 2a7d1c216..1966dcc33 100644 --- a/docs-src/guides/migrations.docs.outline.md +++ b/docs-src/guides/migrations.docs.outline.md @@ -45,7 +45,7 @@ What to change: - Replace `bg-status-completed/10 border-status-completed/30` style tints with `bg-success-surface border-success-border`. The same applies to the other intents and to `bg-alert-*` / `border-alert-*`. - In a theme that recolours the status components, set the roles (`--success-solid`, `--danger-text`, ...) instead of `--status-*`, `--alert-*` or `--destructive`. -The roles are documented in [Styling & theming](../concepts/styling-theming.md#semantic-color-roles). The decision record is `decisions/semantic-color-roles.md`. +The roles are documented in [Styling & theming](./concepts/styling-theming.md#semantic-color-roles). The decision record is `decisions/semantic-color-roles.md`. ## 1.15.0: Base UI 1.8.0 breaks closed-`Select` and dialog-click tests diff --git a/docs/migrations.md b/docs/migrations.md index f88dfeb9b..6c50578cc 100644 --- a/docs/migrations.md +++ b/docs/migrations.md @@ -45,7 +45,7 @@ What to change: - Replace `bg-status-completed/10 border-status-completed/30` style tints with `bg-success-surface border-success-border`. The same applies to the other intents and to `bg-alert-*` / `border-alert-*`. - In a theme that recolours the status components, set the roles (`--success-solid`, `--danger-text`, ...) instead of `--status-*`, `--alert-*` or `--destructive`. -The roles are documented in [Styling & theming](../concepts/styling-theming.md#semantic-color-roles). The decision record is `decisions/semantic-color-roles.md`. +The roles are documented in [Styling & theming](./concepts/styling-theming.md#semantic-color-roles). The decision record is `decisions/semantic-color-roles.md`. ## 1.15.0: Base UI 1.8.0 breaks closed-`Select` and dialog-click tests From c26a9c9caf01d55f75fc75dbafe8a148f8d3b5e0 Mon Sep 17 00:00:00 2001 From: Hiroki Uemura Date: Fri, 2 Oct 2026 21:15:43 +0900 Subject: [PATCH 29/29] docs(migrations): link the example stylesheet by repository path github.com blob URLs answer 503 to the link checker, so the pre-existing absolute link fails CI. A repository-relative path is checked on disk. Co-Authored-By: Claude Fable 5.1 --- docs-manifest.json | 4 ++-- docs-src/guides/migrations.docs.outline.md | 2 +- docs/migrations.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs-manifest.json b/docs-manifest.json index e1afa52a5..800f11bec 100644 --- a/docs-manifest.json +++ b/docs-manifest.json @@ -1046,9 +1046,9 @@ "symbols": [], "hashes": { "typeSurface": null, - "outline": "c967970ea4752d80", + "outline": "45dcfe4383a87ea9", "snapshot": null, - "outputMd": "b12b5479d4f2368e", + "outputMd": "2fa0b6a249d8bd4f", "examples": null } }, diff --git a/docs-src/guides/migrations.docs.outline.md b/docs-src/guides/migrations.docs.outline.md index 1966dcc33..cca666b24 100644 --- a/docs-src/guides/migrations.docs.outline.md +++ b/docs-src/guides/migrations.docs.outline.md @@ -167,7 +167,7 @@ grep -rnE "@theme inline|@custom-variant|app-shell/theme\.css|--(card|popover|mu Excluding `node_modules` matters: AppShell's own palette files declare these tokens too, and they must not be touched. In your own CSS, hits are either the workaround, which goes, or deliberate overrides, which should take the `:root` / `:root.dark` form described in [Overriding tokens](./concepts/styling-theming.md#overriding-tokens). -What remains is short — [`examples/vite-app/src/index.css`](https://github.com/tailor-platform/app-shell/blob/main/examples/vite-app/src/index.css) is a working reference for the shape (it also imports a branded palette, which is optional): +What remains is short — [`examples/vite-app/src/index.css`](../examples/vite-app/src/index.css) is a working reference for the shape (it also imports a branded palette, which is optional): ```css @import "tailwindcss"; diff --git a/docs/migrations.md b/docs/migrations.md index 6c50578cc..4ffc91542 100644 --- a/docs/migrations.md +++ b/docs/migrations.md @@ -167,7 +167,7 @@ grep -rnE "@theme inline|@custom-variant|app-shell/theme\.css|--(card|popover|mu Excluding `node_modules` matters: AppShell's own palette files declare these tokens too, and they must not be touched. In your own CSS, hits are either the workaround, which goes, or deliberate overrides, which should take the `:root` / `:root.dark` form described in [Overriding tokens](./concepts/styling-theming.md#overriding-tokens). -What remains is short — [`examples/vite-app/src/index.css`](https://github.com/tailor-platform/app-shell/blob/main/examples/vite-app/src/index.css) is a working reference for the shape (it also imports a branded palette, which is optional): +What remains is short — [`examples/vite-app/src/index.css`](../examples/vite-app/src/index.css) is a working reference for the shape (it also imports a branded palette, which is optional): ```css @import "tailwindcss";