From 08c49795d6d05b0e5656a03d3e824b93bd3e1bd5 Mon Sep 17 00:00:00 2001 From: Harshit Shah Date: Tue, 13 Oct 2026 11:22:02 +0530 Subject: [PATCH 1/3] Keep light and dark --om-* tokens in tokens.css only The dark values for the legacy --om-* aliases lived in app.less while their light values lived in tokens.css, so import order decided which won (#34936 made light win everywhere). Move the dark block next to the light values, drop a dead duplicate @grey-25, and guard the rule in theme-contract.test.js. Also clears the existing token-drift failures (stale generated block, link role expectation, broken card spec link) so the hook can gate on it. --- .claude/rules/frontend-styling.md | 6 ++ ...26-10-13-om-tokens-have-one-source-file.md | 40 ++++++++++ .../design-tokens/theme-contract.test.js | 70 ++++++++++++++++- .../resources/ui/specs/components/card.md | 6 +- .../ui/specs/tokens/token-reference.md | 21 +----- .../src/main/resources/ui/src/styles/app.less | 58 +------------- .../main/resources/ui/src/styles/tokens.css | 75 +++++++++++++++---- .../resources/ui/src/styles/variables.less | 7 +- 8 files changed, 188 insertions(+), 95 deletions(-) create mode 100644 docs/decisions/2026-10-13-om-tokens-have-one-source-file.md diff --git a/.claude/rules/frontend-styling.md b/.claude/rules/frontend-styling.md index f48069a35e88..4a3644645658 100644 --- a/.claude/rules/frontend-styling.md +++ b/.claude/rules/frontend-styling.md @@ -57,6 +57,12 @@ Light mode is frozen: reach dark with tokens that adapt, never by changing light [`docs/colors.md`](../../openmetadata-ui/src/main/resources/ui/docs/colors.md); dark values live under `.dark-mode` in `globals.css`. +- **One file per token layer:** core tokens (`--color-*`, `--tw-*`) are declared only in core + `globals.css`; project `--om-*` tokens only in `tokens.css`, light on `:root` and dark on + `:root.dark-mode` side by side. Never declare an `--om-*` in `app.less` or a component file — + cross-file import order decides the winner. `variables.less` fallbacks equal the `tokens.css` + light value. Guard: `theme-contract.test.js` (ADR:2026-10-13-om-tokens-have-one-source-file). + - **Scope:** dark mode is AI mode on core/Tailwind surfaces only. Classic mode gets no dark theme and antd-only surfaces (`.ant-*`) are not themed for dark — migrate them to core instead (ADR:2026-10-12-dark-mode-targets-ai-mode-core-surfaces-only). diff --git a/docs/decisions/2026-10-13-om-tokens-have-one-source-file.md b/docs/decisions/2026-10-13-om-tokens-have-one-source-file.md new file mode 100644 index 000000000000..8a3e95cf57da --- /dev/null +++ b/docs/decisions/2026-10-13-om-tokens-have-one-source-file.md @@ -0,0 +1,40 @@ +# Every --om-* token is declared in tokens.css, light and dark side by side + +- **Status:** Accepted +- **Revisions:** v1 2026-10-13 (initial) +- **Deciders:** Harshit Shah +- **Guard:** `openmetadata-ui/src/main/resources/ui/scripts/design-tokens/theme-contract.test.js` (run by `yarn token-drift` and the `token-drift` pre-commit hook) +- **Related:** open-metadata/OpenMetadata#34936, open-metadata/OpenMetadata#35271, ADR:2026-10-12-dark-mode-targets-ai-mode-core-surfaces-only + +## Context +Each legacy LESS palette var (`@grey-15`, `@blue-29`, the status tints, …) was defined in three +files: the LESS var and its fallback hex in `variables.less`, the light `--om-*` value on `:root` +in `tokens.css`, and the dark `--om-*` value under `.dark-mode` in `app.less`. #34936 added the +light values to `tokens.css`. That file is imported after `app.less`, and `:root` has the same +specificity as `.dark-mode` on ``, so the light values won and every bridged var +(the app-wide `@grey-15` border, `@grey-1/2/5/6/9/26`, `@blue-29/30`, the status tints) stayed +light in dark mode. #35271 patched the selector to `:root.dark-mode`, but the split itself, where +correctness depends on import order across files, remained. The same split had already let +`variables.less` carry two `@grey-25` definitions with different fallbacks. + +## Decision +- Core tokens (`--color-*`, `--tw-*`, and the other Tailwind theme namespaces) are declared only in + core `globals.css`, light in `@theme` and dark in `.dark-mode`. +- Project `--om-*` tokens are declared only in `openmetadata-ui/.../src/styles/tokens.css`. A + token's light value goes on `:root` and its dark value on `:root.dark-mode` in the same file. + `:root.dark-mode` out-ranks `:root` regardless of order. +- No other stylesheet (`app.less`, component `.less`/`.css`) declares an `--om-*` property. The one + exception is `--om-page-height`, a layout value each shell re-declares so its `calc()` resolves + against that shell's navbar height. It is not a themed token. +- Every dark `--om-*` override has a light value in `tokens.css`. +- A `variables.less` bridge (`@x: var(--om-x, )`) is defined once, and its fallback + equals the `tokens.css` light value. +- `theme-contract.test.js` enforces all of the above. + +## Consequences +Adding or changing a themed legacy colour is a one-file edit, and the guard fails the +`token-drift` hook if a second file starts declaring `--om-*`. Overrides that must beat inline +custom properties on `` (the `.dark-mode body` brand-accent block in `app.less`) target the +core `--tw-*` layer, not `--om-*`, so they are outside this rule. They stay where they are until +the brand injection stops writing inline vars. Revisit if `tokens.css` is split into per-theme +files, in which case the guard must check the import order instead. diff --git a/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/theme-contract.test.js b/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/theme-contract.test.js index 6238bd94f5e7..096a2cd626df 100644 --- a/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/theme-contract.test.js +++ b/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/theme-contract.test.js @@ -226,7 +226,7 @@ test('keeps dark text readable and exposes dedicated link roles', () => { light.get('--color-text-link-hover'), 'var(--color-text-link-hover, theme(--color-brand-700))' ); - assert.equal(dark.get('--color-text-link'), 'theme(--color-brand-300)'); + assert.equal(dark.get('--color-text-link'), 'theme(--color-brand-400)'); assert.equal(dark.get('--color-text-link-hover'), 'theme(--color-brand-200)'); assert.equal(dark.get('--tw-text-color-link'), 'var(--color-text-link)'); assert.equal( @@ -344,3 +344,71 @@ test('documents every approved go-forward utility', () => { assert.match(reference, new RegExp(`\\b${utility}\\b`)); } }); + +// ADR:2026-10-13-om-tokens-have-one-source-file +const SRC_DIR = path.resolve(__dirname, '../../src'); +// Layout height re-declared per shell so calc() re-resolves against that +// shell's navbar height; not a themed token. +const OM_DECLARATION_ALLOWLIST = new Set(['--om-page-height']); + +function styleFiles(dir) { + return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) { + return entry.name === 'node_modules' ? [] : styleFiles(full); + } + + return /\.(less|css)$/.test(entry.name) ? [full] : []; + }); +} + +test('declares --om-* tokens only in tokens.css', () => { + const offenders = styleFiles(SRC_DIR) + .filter((file) => file !== TOKENS_FILE) + .flatMap((file) => + [...fs.readFileSync(file, 'utf8').matchAll(/(--om-[\w-]+)\s*:/g)] + .map((match) => match[1]) + .filter((token) => !OM_DECLARATION_ALLOWLIST.has(token)) + .map((token) => `${path.relative(SRC_DIR, file)}: ${token}`) + ); + + // A second file declaring --om-* splits light and dark across stylesheets, + // so the winner depends on import order (light won and dark mode broke). + assert.deepEqual(offenders, []); +}); + +test('gives every dark --om-* override a light value in tokens.css', () => { + const css = fs.readFileSync(TOKENS_FILE, 'utf8'); + const dark = declarations(extractBlock(css, ':root.dark-mode')); + const light = declarations(extractBlock(css, ':root {')); + + assert.ok(dark.size > 0); + for (const token of dark.keys()) { + assert.ok(light.has(token), `${token} has a dark value but no light one`); + } +}); + +test('keeps variables.less fallbacks equal to the tokens.css light values', () => { + const css = fs.readFileSync(TOKENS_FILE, 'utf8'); + const light = declarations(extractBlock(css, ':root {')); + const less = fs.readFileSync( + path.resolve(SRC_DIR, 'styles/variables.less'), + 'utf8' + ); + const bridged = [ + ...less.matchAll(/^(@[\w-]+):\s*var\((--om-[\w-]+),\s*(.+)\);$/gm), + ]; + const names = bridged.map((match) => match[1]); + + // LESS keeps the last of two definitions, so an earlier one is dead code + // whose fallback silently disagrees with tokens.css. + assert.deepEqual( + names.filter((name, index) => names.indexOf(name) !== index), + [] + ); + for (const [, , token, fallback] of bridged) { + if (light.has(token)) { + assert.equal(fallback, light.get(token), `${token} fallback drifted`); + } + } +}); diff --git a/openmetadata-ui/src/main/resources/ui/specs/components/card.md b/openmetadata-ui/src/main/resources/ui/specs/components/card.md index da469e93e921..c410fc2898f0 100644 --- a/openmetadata-ui/src/main/resources/ui/specs/components/card.md +++ b/openmetadata-ui/src/main/resources/ui/specs/components/card.md @@ -7,7 +7,7 @@ | **Name** | Card | | **Category** | Base / container | | **Status** | Stable | -| **Styles** | [`src/styles/components/card.less`](../../src/styles/components/card.less) | +| **Styles** | [`core card.tsx`](../../../../../../../openmetadata-ui-core-components/src/main/resources/ui/src/components/base/card/card.tsx) | | **Component** | `@openmetadata/ui-core-components` → `Card` (`Card.Header` / `Card.Content` / `Card.Footer`) for new work; Ant Design `Card` (legacy) | ## Overview @@ -35,8 +35,8 @@ Parts: **root** (surface, border, radius, optional shadow), **header** ## Tokens used -Real `--om-*` tokens in `card.less` are the medium weight and the expand -transition; surface/border/radius map through the modern core-components Card. +Surface, border and radius come from the core-components Card; the medium +weight and expand transition below are the matching `--om-*` tokens. | Part | Token | | --- | --- | diff --git a/openmetadata-ui/src/main/resources/ui/specs/tokens/token-reference.md b/openmetadata-ui/src/main/resources/ui/specs/tokens/token-reference.md index ac59eb8fc75a..be65a4df0241 100644 --- a/openmetadata-ui/src/main/resources/ui/specs/tokens/token-reference.md +++ b/openmetadata-ui/src/main/resources/ui/specs/tokens/token-reference.md @@ -4,9 +4,9 @@ Master map of every **project (`--om-*`) token** — the tokens components reference. Each references the matching upstream `globals.css` token (or holds a raw value) and resolves to the value shown. Full layering: [../README.md](../README.md). -Total project tokens: **834**. +Total project tokens: **821**. -## Spacing (57) +## Spacing (56) padding / margin / gap. See foundations/spacing.md. @@ -36,7 +36,6 @@ padding / margin / gap. See foundations/spacing.md. | `--om-space-19` | `19px` | | `--om-space-20` | `20px` | | `--om-space-22` | `22px` | -| `--om-space-23` | `23px` | | `--om-space-24` | `24px` | | `--om-space-26` | `26px` | | `--om-space-28` | `28px` | @@ -706,11 +705,11 @@ Fixed swatches; do NOT adapt to dark mode. Prefer semantic tokens. -## Legacy colors (191) +## Legacy colors (179) Exact migrated one-offs (migration debt). Do not use in new code; re-express with a semantic token. -
Show all 191 +
Show all 179 | Token | Value | | --- | --- | @@ -725,7 +724,6 @@ Exact migrated one-offs (migration debt). Do not use in new code; re-express wit | `--om-legacy-color-0-0-0-0-8` | `rgba(0, 0, 0, 0.8)` | | `--om-legacy-color-0-0-0-0-9` | `rgba(0, 0, 0, 0.9)` | | `--om-legacy-color-0-0-0-0-12` | `rgba(0, 0, 0, 0.12)` | -| `--om-legacy-color-0-0-0-0-13` | `rgba(0, 0, 0, 0.13)` | | `--om-legacy-color-0-0-0-0-15` | `rgba(0, 0, 0, 0.15)` | | `--om-legacy-color-0-0-0-0-25` | `rgba(0, 0, 0, 0.25)` | | `--om-legacy-color-0-0-0-0-102` | `#0000001a` | @@ -748,7 +746,6 @@ Exact migrated one-offs (migration debt). Do not use in new code; re-express wit | `--om-legacy-color-10-13-18-0-04` | `rgba(10, 13, 18, 0.04)` | | `--om-legacy-color-10-13-18-0-05` | `rgba(10, 13, 18, 0.05)` | | `--om-legacy-color-10-13-18-0-08` | `rgba(10, 13, 18, 0.08)` | -| `--om-legacy-color-10-13-18-0-18` | `rgba(10, 13, 18, 0.18)` | | `--om-legacy-color-10-13-18-0-051` | `#0a0d120d` | | `--om-legacy-color-10-13-18-0-059` | `#0a0d120f` | | `--om-legacy-color-10-13-18-0-102` | `#0a0d121a` | @@ -772,7 +769,6 @@ Exact migrated one-offs (migration debt). Do not use in new code; re-express wit | `--om-legacy-color-52c41a` | `#52c41a` | | `--om-legacy-color-0056b3` | `#0056b3` | | `--om-legacy-color-0066cc` | `#06c` | -| `--om-legacy-color-82-196-26-0-05` | `rgba(82, 196, 26, 0.05)` | | `--om-legacy-color-85-155-238-0-2` | `rgba(85, 155, 238, 0.2)` | | `--om-legacy-color-96-167-251-0-2` | `rgba(96, 167, 251, 0.2)` | | `--om-legacy-color-102a72` | `#102a72` | @@ -780,7 +776,6 @@ Exact migrated one-offs (migration debt). Do not use in new code; re-express wit | `--om-legacy-color-113-71-232-0-6` | `#7147e899` | | `--om-legacy-color-113-71-232-0-35` | `rgba(113, 71, 232, 0.35)` | | `--om-legacy-color-113-71-232-0-102` | `#7147e81a` | -| `--om-legacy-color-117-117-117-0-3` | `rgba(117, 117, 117, 0.3)` | | `--om-legacy-color-203-36-49-0-059` | `#cb24310f` | | `--om-legacy-color-209-236-241-0-867` | `#d1ecf1dd` | | `--om-legacy-color-212-237-218-0-867` | `#d4eddadd` | @@ -804,7 +799,6 @@ Exact migrated one-offs (migration debt). Do not use in new code; re-express wit | `--om-legacy-color-356fe7` | `rgb(53, 111, 231)` | | `--om-legacy-color-373e44` | `#373e44` | | `--om-legacy-color-721c24` | `#721c24` | -| `--om-legacy-color-0950c5` | `#0950c5` | | `--om-legacy-color-1677ff` | `#1677ff` | | `--om-legacy-color-1890ff` | `#1890ff` | | `--om-legacy-color-2563eb` | `#2563eb` | @@ -815,7 +809,6 @@ Exact migrated one-offs (migration debt). Do not use in new code; re-express wit | `--om-legacy-color-52525b` | `#52525b` | | `--om-legacy-color-52607a` | `#52607a` | | `--om-legacy-color-76746f` | `#76746f` | -| `--om-legacy-color-101828` | `#101828` | | `--om-legacy-color-111827` | `#111827` | | `--om-legacy-color-155724` | `#155724` | | `--om-legacy-color-344054` | `#344054` | @@ -835,9 +828,7 @@ Exact migrated one-offs (migration debt). Do not use in new code; re-express wit | `--om-legacy-color-b93815` | `#b93815` | | `--om-legacy-color-bee5eb` | `#bee5eb` | | `--om-legacy-color-c3e6cb` | `#c3e6cb` | -| `--om-legacy-color-c6b5f6` | `#c6b5f6` | | `--om-legacy-color-c6c6c6` | `#c6c6c6` | -| `--om-legacy-color-c9d7ff` | `#c9d7ff` | | `--om-legacy-color-c45296` | `#c45296` | | `--om-legacy-color-cb2431` | `#cb2431` | | `--om-legacy-color-cccccc` | `#cccccc` | @@ -863,7 +854,6 @@ Exact migrated one-offs (migration debt). Do not use in new code; re-express wit | `--om-legacy-color-e8e8e9` | `#e8e8e9` | | `--om-legacy-color-e8ecf5` | `#e8ecf5` | | `--om-legacy-color-e60000` | `#e60000` | -| `--om-legacy-color-eaecf0` | `#eaecf0` | | `--om-legacy-color-ebeef1` | `#ebeef1` | | `--om-legacy-color-ecedee` | `#ecedee` | | `--om-legacy-color-eeeeee` | `#eeeeee` | @@ -871,7 +861,6 @@ Exact migrated one-offs (migration debt). Do not use in new code; re-express wit | `--om-legacy-color-efbdc1` | `#efbdc1` | | `--om-legacy-color-f0f0f0` | `#f0f0f0` | | `--om-legacy-color-f0f5ff` | `#f0f5ff` | -| `--om-legacy-color-f1edfd` | `#f1edfd` | | `--om-legacy-color-f1f1f1` | `#f1f1f1` | | `--om-legacy-color-f1f5ff` | `#f1f5ff` | | `--om-legacy-color-f3f7fd` | `#f3f7fd` | @@ -887,10 +876,8 @@ Exact migrated one-offs (migration debt). Do not use in new code; re-express wit | `--om-legacy-color-f7f9fc` | `#f7f9fc` | | `--om-legacy-color-f7fbf9` | `#f7fbf9` | | `--om-legacy-color-f8f8f8` | `#f8f8f8` | -| `--om-legacy-color-f8f9fa` | `#f8f9fa` | | `--om-legacy-color-f8faff` | `#f8faff` | | `--om-legacy-color-f9dbaf` | `#f9dbaf` | -| `--om-legacy-color-f9f5ff` | `#f9f5ff` | | `--om-legacy-color-f9816c` | `#f9816c` | | `--om-legacy-color-f44336` | `#f44336` | | `--om-legacy-color-fbf8f8` | `#fbf8f8` | diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/app.less b/openmetadata-ui/src/main/resources/ui/src/styles/app.less index b60f06dae839..9aaf98e3fcdd 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/app.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/app.less @@ -819,62 +819,8 @@ a[href].link-text-grey, color: @background-color !important; } -// Dark-mode bridge for fixed-hex legacy LESS palette vars. -// Each var in variables.less has a project token and original color fallback, so light -// mode resolves to the exact original hex (no drift) while dark mode picks up -// the semantic/utility override below. This themes every consumer of these -// legacy vars app-wide without per-file edits. Utility tokens auto-invert under -// .dark-mode, so referencing them here yields the dark value. Categorical ramps -// (severity, heatmap gradients) are intentionally excluded — they belong to the -// token foundation. -// `:root.dark-mode` (not `.dark-mode`): tokens.css declares the light values on -// `:root` at equal specificity and is imported after this file, so a bare -// `.dark-mode` lost and every bridged var stayed light in dark mode. -:root.dark-mode { - // Neutral surfaces / borders / muted text - --om-grey-26: var(--color-bg-secondary_subtle); - --om-grey-25: var(--color-bg-secondary_subtle); - --om-grey-9: var(--color-bg-secondary_subtle); - --om-grey-1: var(--color-bg-secondary); - --om-grey-2: var(--color-bg-secondary); - --om-grey-5: var(--color-bg-secondary); - --om-grey-6: var(--color-bg-secondary); - --om-grey-50: var(--color-bg-secondary); - // @grey-15 (= @border-color / @global-border) is the primary neutral border - // across antd + legacy components (cards, tabs, tables, panels, dividers). - // In dark, map it to the subtle white-alpha border (rgba(255,255,255,0.08)) - // so every antd surface reads consistently instead of a solid gray edge — - // one change themes all @border-color borders app-wide (OSS + Collate via - // submodule). Light keeps the frozen #eaecf5 fallback. - --om-grey-15: var(--color-border-subtle); - --om-grey-38: var(--color-border-subtle); - --om-grey-7: var(--color-text-quaternary); - --om-heading-color: var(--color-text-primary); - --om-border-color-base: var(--color-border-primary); - // @blue-29/@blue-30 are the fill/edge of legacy neutral chips (column data - // type, tag/glossary chips, async-select tags) — dark uses the core gray Badge pair. - --om-blue-29: var(--tw-color-utility-gray-50); - --om-purple-1: var(--tw-color-utility-purple-50); - --om-blue-30: var(--tw-color-utility-gray-200); - // Status — utility tokens auto-invert to readable dark values - --om-blue-13: var(--tw-color-utility-blue-light-100); - --om-blue-22: var(--tw-color-utility-blue-light-50); - --om-blue-500: var(--tw-color-utility-blue-light-500); - --om-green-9: var(--tw-color-utility-success-50); - --om-green-10: var(--tw-color-utility-success-700); - --om-green-12: var(--tw-color-utility-success-100); - --om-green-100: var(--tw-color-utility-success-100); - --om-green-500: var(--tw-color-utility-success-500); - --om-red-9: var(--tw-color-utility-error-50); - --om-red-10: var(--tw-color-utility-error-700); - --om-red-22: var(--tw-color-utility-error-500); - --om-yellow-10: var(--tw-color-utility-warning-50); - --om-alert-error-icon-bg-1: var(--tw-color-utility-error-100); - --om-alert-warning-icon: var(--tw-color-utility-warning-600); - --om-alert-warning-icon-bg-1: var(--tw-color-utility-warning-100); - --om-warning-100: var(--tw-color-utility-warning-100); - --om-warning-400: var(--tw-color-utility-warning-400); -} +// Dark values for the legacy --om-* aliases live in tokens.css, next to their +// light values (ADR:2026-10-13-om-tokens-have-one-source-file). // AI dark-mode brand ACCENTS -> blue-400 (#53b1fd), the devrel dark palette: // #1570ef carries fills/borders, #53b1fd labels/icons/links (~5.4:1 on dark). diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/tokens.css b/openmetadata-ui/src/main/resources/ui/src/styles/tokens.css index d0f8bf6478da..8a606212e1cd 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/tokens.css +++ b/openmetadata-ui/src/main/resources/ui/src/styles/tokens.css @@ -28,6 +28,10 @@ * values (spacing, z-index, legacy colors with no * upstream home) hold the raw value directly here — * this is the one place raw values may live. + * Light values go on :root, dark values on + * :root.dark-mode — both here, never in another + * stylesheet (ADR:2026-10-13-om-tokens-have-one- + * source-file). * * Components (the .less / .css files) reference Layer 2 via var(--om-*) and * never raw values. @@ -242,8 +246,8 @@ --om-color-utility-yellow-100: var(--color-utility-yellow-100, #fef7c3); --om-color-utility-yellow-700: var(--color-utility-yellow-700, #a15c07); - /* Legacy LESS aliases keep their original light values; app.less supplies - their semantic dark-mode overrides. */ + /* Legacy LESS aliases keep their original light values here; their dark + values are in the :root.dark-mode block below. */ --om-green-9: #ecfdf3; --om-green-10: #027a48; --om-green-12: #d1fadf; @@ -371,6 +375,60 @@ --om-color-transparent: transparent; } +/* Dark values for the legacy LESS aliases above. Kept in this file so light + and dark for every --om-* token sit in one place, with no cross-file import + order to get wrong. `:root.dark-mode` out-ranks `:root` whatever the order. + Each var in variables.less points at its --om-* alias, so light mode keeps + the original hex while dark picks the semantic/utility token below; utility + tokens auto-invert under .dark-mode. Categorical ramps (severity, heatmap + gradients) are excluded and belong to the token foundation. + ADR:2026-10-13-om-tokens-have-one-source-file */ +:root.dark-mode { + /* Neutral surfaces / borders / muted text */ + --om-grey-26: var(--color-bg-secondary_subtle); + --om-grey-25: var(--color-bg-secondary_subtle); + --om-grey-9: var(--color-bg-secondary_subtle); + --om-grey-1: var(--color-bg-secondary); + --om-grey-2: var(--color-bg-secondary); + --om-grey-5: var(--color-bg-secondary); + --om-grey-6: var(--color-bg-secondary); + --om-grey-50: var(--color-bg-secondary); + /* @grey-15 (= @border-color / @global-border) is the primary neutral border + across antd + legacy components (cards, tabs, tables, panels, dividers). + In dark, map it to the subtle white-alpha border (rgba(255,255,255,0.08)) + so every antd surface reads consistently instead of a solid gray edge — + one change themes all @border-color borders app-wide (OSS + Collate via + submodule). Light keeps the frozen #eaecf5 fallback. */ + --om-grey-15: var(--color-border-subtle); + --om-grey-38: var(--color-border-subtle); + --om-grey-7: var(--color-text-quaternary); + --om-heading-color: var(--color-text-primary); + --om-border-color-base: var(--color-border-primary); + /* @blue-29/@blue-30 are the fill/edge of legacy neutral chips (column data + type, tag/glossary chips, async-select tags) — dark uses the core gray Badge pair. */ + --om-blue-29: var(--tw-color-utility-gray-50); + --om-purple-1: var(--tw-color-utility-purple-50); + --om-blue-30: var(--tw-color-utility-gray-200); + /* Status — utility tokens auto-invert to readable dark values */ + --om-blue-13: var(--tw-color-utility-blue-light-100); + --om-blue-22: var(--tw-color-utility-blue-light-50); + --om-blue-500: var(--tw-color-utility-blue-light-500); + --om-green-9: var(--tw-color-utility-success-50); + --om-green-10: var(--tw-color-utility-success-700); + --om-green-12: var(--tw-color-utility-success-100); + --om-green-100: var(--tw-color-utility-success-100); + --om-green-500: var(--tw-color-utility-success-500); + --om-red-9: var(--tw-color-utility-error-50); + --om-red-10: var(--tw-color-utility-error-700); + --om-red-22: var(--tw-color-utility-error-500); + --om-yellow-10: var(--tw-color-utility-warning-50); + --om-alert-error-icon-bg-1: var(--tw-color-utility-error-100); + --om-alert-warning-icon: var(--tw-color-utility-warning-600); + --om-alert-warning-icon-bg-1: var(--tw-color-utility-warning-100); + --om-warning-100: var(--tw-color-utility-warning-100); + --om-warning-400: var(--tw-color-utility-warning-400); +} + /* @tokens:generated-begin — DO NOT EDIT BY HAND. Regenerate: node scripts/design-tokens/gen-tokens.js Full upstream palette + the project legacy/off-scale/z/motion tokens @@ -713,7 +771,6 @@ --om-legacy-color-0-0-0-0-1: rgba(0, 0, 0, 0.1); --om-legacy-color-0-0-0-0-102: #0000001a; --om-legacy-color-0-0-0-0-12: rgba(0, 0, 0, 0.12); - --om-legacy-color-0-0-0-0-13: rgba(0, 0, 0, 0.13); --om-legacy-color-0-0-0-0-15: rgba(0, 0, 0, 0.15); --om-legacy-color-0-0-0-0-2: rgba(0, 0, 0, 0.2); --om-legacy-color-0-0-0-0-25: rgba(0, 0, 0, 0.25); @@ -727,7 +784,6 @@ --om-legacy-color-008a00: #008a00; --om-legacy-color-027a48: #027a48; --om-legacy-color-06a4a4: #06a4a4; - --om-legacy-color-0950c5: #0950c5; --om-legacy-color-0c5460: #0c5460; --om-legacy-color-10-13-18-0-04: rgba(10, 13, 18, 0.04); --om-legacy-color-10-13-18-0-05: rgba(10, 13, 18, 0.05); @@ -735,15 +791,12 @@ --om-legacy-color-10-13-18-0-059: #0a0d120f; --om-legacy-color-10-13-18-0-08: rgba(10, 13, 18, 0.08); --om-legacy-color-10-13-18-0-102: #0a0d121a; - --om-legacy-color-10-13-18-0-18: rgba(10, 13, 18, 0.18); - --om-legacy-color-101828: #101828; --om-legacy-color-102a72: #102a72; --om-legacy-color-107-114-128-0-15: rgba(107, 114, 128, 0.15); --om-legacy-color-111827: #111827; --om-legacy-color-113-71-232-0-102: #7147e81a; --om-legacy-color-113-71-232-0-35: rgba(113, 71, 232, 0.35); --om-legacy-color-113-71-232-0-6: #7147e899; - --om-legacy-color-117-117-117-0-3: rgba(117, 117, 117, 0.3); --om-legacy-color-13c2c2: #13c2c2; --om-legacy-color-15-15-15-0-1: rgba(15, 15, 15, 0.1); --om-legacy-color-15-23-42-0-06: rgba(15, 23, 42, 0.06); @@ -811,7 +864,6 @@ --om-legacy-color-76746f: #76746f; --om-legacy-color-777777: #777; --om-legacy-color-7f56d9: #7f56d9; - --om-legacy-color-82-196-26-0-05: rgba(82, 196, 26, 0.05); --om-legacy-color-85-155-238-0-2: rgba(85, 155, 238, 0.2); --om-legacy-color-856404: #856404; --om-legacy-color-8c8c8c: #8c8c8c; @@ -827,9 +879,7 @@ --om-legacy-color-bee5eb: #bee5eb; --om-legacy-color-c3e6cb: #c3e6cb; --om-legacy-color-c45296: #c45296; - --om-legacy-color-c6b5f6: #c6b5f6; --om-legacy-color-c6c6c6: #c6c6c6; - --om-legacy-color-c9d7ff: #c9d7ff; --om-legacy-color-cb2431: #cb2431; --om-legacy-color-cccccc: #cccccc; --om-legacy-color-d4d4d8: #d4d4d8; @@ -854,7 +904,6 @@ --om-legacy-color-e8e8e8: #e8e8e8; --om-legacy-color-e8e8e9: #e8e8e9; --om-legacy-color-e8ecf5: #e8ecf5; - --om-legacy-color-eaecf0: #eaecf0; --om-legacy-color-ebeef1: #ebeef1; --om-legacy-color-ecedee: #ecedee; --om-legacy-color-eeeeee: #eeeeee; @@ -862,7 +911,6 @@ --om-legacy-color-efbdc1: #efbdc1; --om-legacy-color-f0f0f0: #f0f0f0; --om-legacy-color-f0f5ff: #f0f5ff; - --om-legacy-color-f1edfd: #f1edfd; --om-legacy-color-f1f1f1: #f1f1f1; --om-legacy-color-f1f5ff: #f1f5ff; --om-legacy-color-f3f7fd: #f3f7fd; @@ -879,10 +927,8 @@ --om-legacy-color-f7f9fc: #f7f9fc; --om-legacy-color-f7fbf9: #f7fbf9; --om-legacy-color-f8f8f8: #f8f8f8; - --om-legacy-color-f8f9fa: #f8f9fa; --om-legacy-color-f8faff: #f8faff; --om-legacy-color-f9816c: #f9816c; - --om-legacy-color-f9f5ff: #f9f5ff; --om-legacy-color-fbf8f8: #fbf8f8; --om-legacy-color-fbfaf9: #fbfaf9; --om-legacy-color-fbfcff: #fbfcff; @@ -912,7 +958,6 @@ --om-space-18: 18px; --om-space-19: 19px; --om-space-22: 22px; - --om-space-23: 23px; --om-space-26: 26px; --om-space-30: 30px; --om-space-35: 35px; diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/variables.less b/openmetadata-ui/src/main/resources/ui/src/styles/variables.less index 221672a6e997..2f2b80bc1632 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/variables.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/variables.less @@ -43,7 +43,9 @@ // DARK-MODE BRIDGE: palette vars written as `var(--om-*, #hex)` below (e.g. // @green-9/10/12/100, @red-9/10/22, @yellow-10, @blue-13/22/29/30/500, // @grey-1/2/5/6/7/9/15/25/26/38/50, @green-500, @warning-100/400, @alert-*) resolve -// through an --om-* custom property so they adapt in dark. LESS color functions +// through an --om-* custom property so they adapt in dark. Both the light and +// dark value of each --om-* live in tokens.css; the fallback here must equal +// the light one (ADR:2026-10-13-om-tokens-have-one-source-file). LESS color functions // (darken/lighten/fade/mix/tint/shade/…) CANNOT evaluate a var() — passing a // bridged var to one fails the build at compile time (this broke Collate's // `darken(@grey-9)` in code-widget). Never wrap a bridged var in a LESS color @@ -187,7 +189,6 @@ @grey-22: #d9d9d9; @grey-23: #f7f7f7; @grey-24: #979797; -@grey-25: var(--om-grey-25, #dbe0e7); @grey-26: var(--om-grey-26, #fcfcfd); @grey-27: #0a0d120d; @grey-28: #00000040; @@ -491,7 +492,7 @@ @body-background: var(--tw-color-utility-gray-blue-50); // antd defaults compile to static near-black text / light-gray edges. Bridged // like the palette vars above: the fallback is antd's exact light value, and -// .dark-mode (app.less) re-points the --om-* var. @heading-color feeds +// tokens.css re-points the --om-* var in dark. @heading-color feeds // @label-color, @modal-heading-color, @card-head-color, @table-header-color and // every antd title; @border-color-base feeds input/select/picker/button/radio // borders. antd runs no colour functions on either, so a var() is safe. From 8e149d4ef7de540c3bbfb94b3394bcb1a9f2f1aa Mon Sep 17 00:00:00 2001 From: "detail-app[bot]" <180357370+detail-app[bot]@users.noreply.github.com> Date: Wed, 14 Oct 2026 01:38:00 +0000 Subject: [PATCH 2/3] docs: correct dark-mode brand-primary and brand-border values in color reference --- openmetadata-ui/src/main/resources/ui/docs/colors.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/docs/colors.md b/openmetadata-ui/src/main/resources/ui/docs/colors.md index 2462ed576586..da11e927911d 100644 --- a/openmetadata-ui/src/main/resources/ui/docs/colors.md +++ b/openmetadata-ui/src/main/resources/ui/docs/colors.md @@ -85,7 +85,7 @@ Use background tokens for surfaces, containers, cards, modals, and page-level ba | Tailwind Class | CSS Variable | Light | Dark | When to Use | |---|---|---|---|---| -| `tw:bg-brand-primary` | `--color-bg-brand-primary` | `#eff8ff` | Brand 500 at 14% | Light brand tint surface | +| `tw:bg-brand-primary` | `--color-bg-brand-primary` | `#eff8ff` | Brand 600 at 20% | Light brand tint surface | | `tw:bg-brand-primary_alt` | `--color-bg-brand-primary_alt` | `#eff8ff` | `#191919` | Alternate brand tint | | `tw:bg-brand-secondary` | `--color-bg-brand-secondary` | `#d1e9ff` | `#1570ef` | Stronger brand tint | | `tw:bg-brand-solid` | `--color-bg-brand-solid` | `#1570ef` | `#1570ef` | Primary CTA button fill | @@ -195,7 +195,7 @@ Use border tokens for input outlines, card dividers, separators, and table borde | `tw:border-secondary` | `--color-border-secondary` | `#e9eaeb` (gray-200) | `#222222` (gray-800) | Existing subtle border | | `tw:border-secondary_alt` | `--color-border-secondary_alt` | `rgba(0,0,0,0.08)` | White at 8% | Existing alpha-transparent border | | `tw:border-tertiary` | `--color-border-tertiary` | `#f5f5f5` (gray-100) | `#222222` | Very subtle dividers | -| `tw:border-brand` | `--color-border-brand` | `#2e90fa` (brand-500) | `#53b1fd` (brand-400) | Focused inputs, selected states | +| `tw:border-brand` | `--color-border-brand` | `#2e90fa` (brand-500) | `#1570ef` (brand-600) | Focused inputs, selected states | | `tw:border-brand_alt` | `--color-border-brand_alt` | `#1570ef` (brand-600) | `#2e2e2e` | Alternate brand border | | `tw:border-brand-subtle` | `--color-border-brand-subtle` | `#84caff` | Brand 400 at 35% | Selected-state and info borders | | `tw:border-error` | `--color-border-error` | `#f04438` (error-500) | `#f97066` (error-400) | Error state input/container border | @@ -429,14 +429,14 @@ Brand tokens are for CTAs, highlighted navigation, brand-accented UI, and sectio |---|---|---|---| | `tw:bg-brand-solid` | `#1570ef` | `#1570ef` | Primary action button fill | | `tw:bg-brand-solid_hover` | `#175cd3` | `#2e90fa` | CTA button hover state | -| `tw:bg-brand-primary` | `#eff8ff` | `#2e90fa` | Brand-tinted content area | +| `tw:bg-brand-primary` | `#eff8ff` | Brand 600 at 20% | Brand-tinted content area | | `tw:bg-brand-secondary` | `#d1e9ff` | `#1570ef` | Stronger brand-tinted area | | `tw:bg-brand-section` | `#1849a9` | `#191919` | Full-width brand promo section | | `tw:bg-brand-section_subtle` | `#175cd3` | `#141414` | Subtle brand promo section | | `tw:text-brand-primary` | `#194185` | `#f0f0f0` | Strong brand text | | `tw:text-brand-secondary` | `#175cd3` | `#bfbfbf` | Brand links | | `tw:text-brand-tertiary` | `#1570ef` | `#a0a0a0` | Lighter brand text | -| `tw:border-brand` | `#2e90fa` | `#53b1fd` | Focused/selected border | +| `tw:border-brand` | `#2e90fa` | `#1570ef` | Focused/selected border | | `tw:text-fg-brand-primary` | `#1570ef` | `#2e90fa` | Brand icons | --- From a4d9075c7509f568eb3248342b3191e9ebda3c52 Mon Sep 17 00:00:00 2001 From: Harshit Shah Date: Wed, 14 Oct 2026 07:39:26 +0530 Subject: [PATCH 3/3] chore(ui): regenerate design tokens after merging main main added --om-space-17, --om-space-70 and --om-space-220, which nothing references, so the generated block in tokens.css and token-reference.md drifted again. Regenerate so check-drift passes. --- .../src/main/resources/ui/specs/tokens/token-reference.md | 7 ++----- .../src/main/resources/ui/src/styles/tokens.css | 3 --- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/specs/tokens/token-reference.md b/openmetadata-ui/src/main/resources/ui/specs/tokens/token-reference.md index 937dc9863bef..26cbfd315aba 100644 --- a/openmetadata-ui/src/main/resources/ui/specs/tokens/token-reference.md +++ b/openmetadata-ui/src/main/resources/ui/specs/tokens/token-reference.md @@ -4,9 +4,9 @@ Master map of every **project (`--om-*`) token** — the tokens components reference. Each references the matching upstream `globals.css` token (or holds a raw value) and resolves to the value shown. Full layering: [../README.md](../README.md). -Total project tokens: **819**. +Total project tokens: **816**. -## Spacing (56) +## Spacing (53) padding / margin / gap. See foundations/spacing.md. @@ -31,7 +31,6 @@ padding / margin / gap. See foundations/spacing.md. | `--om-space-14` | `14px` | | `--om-space-15` | `15px` | | `--om-space-16` | `16px` | -| `--om-space-17` | `17px` | | `--om-space-18` | `18px` | | `--om-space-19` | `19px` | | `--om-space-20` | `20px` | @@ -54,7 +53,6 @@ padding / margin / gap. See foundations/spacing.md. | `--om-space-56` | `56px` | | `--om-space-60` | `60px` | | `--om-space-64` | `64px` | -| `--om-space-70` | `70px` | | `--om-space-75` | `75px` | | `--om-space-80` | `80px` | | `--om-space-84` | `84px` | @@ -66,7 +64,6 @@ padding / margin / gap. See foundations/spacing.md. | `--om-space-176` | `176px` | | `--om-space-192` | `192px` | | `--om-space-208` | `208px` | -| `--om-space-220` | `220px` | | `--om-space-290` | `290px` | ## Layout (1) diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/tokens.css b/openmetadata-ui/src/main/resources/ui/src/styles/tokens.css index 491fb0db7185..3ab463fd056b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/tokens.css +++ b/openmetadata-ui/src/main/resources/ui/src/styles/tokens.css @@ -952,7 +952,6 @@ --om-space-9: 9px; --om-space-14: 14px; --om-space-15: 15px; - --om-space-17: 17px; --om-space-18: 18px; --om-space-19: 19px; --om-space-22: 22px; @@ -965,7 +964,6 @@ --om-space-46: 46px; --om-space-50: 50px; --om-space-60: 60px; - --om-space-70: 70px; --om-space-75: 75px; --om-space-84: 84px; --om-space-100: 100px; @@ -975,7 +973,6 @@ --om-space-176: 176px; --om-space-192: 192px; --om-space-208: 208px; - --om-space-220: 220px; --om-space-290: 290px; /* Extended radius — off-scale values in use. */