From d38977c7b4baf5e3a8baaf757c559a2411189f06 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Tue, 26 May 2026 15:08:53 +0200 Subject: [PATCH 01/16] feat(ui): introduce semantic color token families and deprecate legacy tokens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Redesigns the BUI color token system in `@backstage/ui`: - Adds a gray scale (`--bui-gray-1` through `--bui-gray-11`) - Adds new foreground tokens with explicit hex values (primary, secondary, disabled, positive, negative, warning, announcement) - Introduces five new semantic color families — Accent, Announcement, Warning, Negative, Positive — each with bg-base, bg-subdued, border, fg-on-base, and fg-on-subdued variants, for both light and dark themes - Moves all legacy tokens (`--bui-bg-solid-*`, `--bui-bg-neutral-*`, `--bui-bg-danger/warning/success/info`, `--bui-fg-solid`, `--bui-fg-danger/success/info`, `--bui-border-*`, `--bui-shadow`) into a clearly marked `/* Deprecated tokens */` section in both light and dark themes - Updates the Spotify theme overrides to use the new accent tokens and mark legacy overrides as deprecated - Rewrites the `Colors` Storybook story to display all token families as a live, theme-aware reference grid - Adds a new `@backstage/no-deprecated-bui-tokens` ESLint rule to `@backstage/eslint-plugin` that warns when any deprecated BUI token is referenced in JS/TS string literals; the rule is included in the `recommended` config so it applies to all plugin authors automatically Signed-off-by: Charles de Dreuille --- .storybook/themes/spotify.css | 35 +- packages/eslint-plugin/index.js | 2 + .../rules/no-deprecated-bui-tokens.js | 122 +++++ packages/ui/.eslintrc.js | 3 +- packages/ui/src/css/colors.stories.tsx | 448 +++++++++++------- packages/ui/src/css/tokens.css | 242 +++++++--- 6 files changed, 603 insertions(+), 249 deletions(-) create mode 100644 packages/eslint-plugin/rules/no-deprecated-bui-tokens.js diff --git a/.storybook/themes/spotify.css b/.storybook/themes/spotify.css index 7a34299817..1e8ef5090c 100644 --- a/.storybook/themes/spotify.css +++ b/.storybook/themes/spotify.css @@ -193,49 +193,48 @@ } [data-theme-mode='light'][data-theme-name='spotify'] { + --bui-ring: rgba(0, 0, 0, 0.2); + --bui-accent-bg: #1ed760; + --bui-accent-bg-hover: #3be477; + --bui-accent-bg-disabled: #0f6c30; + --bui-accent-fg: var(--bui-black); + --bui-accent-fg-disabled: #62ab7c; + + /* Deprecated tokens */ --bui-bg-solid: #1ed760; --bui-bg-solid-hover: #3be477; --bui-bg-solid-pressed: #1abc54; --bui-bg-solid-disabled: #0f6c30; - - --bui-fg-primary: var(--bui-black); - --bui-fg-secondary: #757575; --bui-fg-solid: var(--bui-black); --bui-fg-solid-disabled: #62ab7c; - - --bui-border-2: #d9d9d9; --bui-border-danger: #f87a7a; --bui-border-warning: #e36d05; --bui-border-success: #53db83; - - --bui-ring: rgba(0, 0, 0, 0.2); } [data-theme-mode='dark'][data-theme-name='spotify'] { - --bui-bg-app: var(--bui-black); + --bui-ring: rgba(255, 255, 255, 0.2); + --bui-accent-bg: #1ed760; + --bui-accent-bg-hover: #3be477; + --bui-accent-bg-disabled: #0f6c30; + --bui-accent-fg: var(--bui-black); + --bui-accent-fg-disabled: #62ab7c; + /* Deprecated tokens */ + --bui-bg-app: var(--bui-black); --bui-bg-solid: #1ed760; --bui-bg-solid-hover: #3be477; --bui-bg-solid-pressed: #1abc54; --bui-bg-solid-disabled: #0f6c30; - --bui-bg-danger: #3b1219; --bui-bg-warning: #302008; --bui-bg-success: #132d21; - - --bui-fg-primary: var(--bui-white); - --bui-fg-secondary: #9e9e9e; - --bui-fg-disabled: #575757; --bui-fg-solid: var(--bui-black); --bui-fg-solid-disabled: #072f15; --bui-fg-danger: #e22b2b; - --bui-fg-warning: #e36d05; --bui-fg-success: #1db954; - - --bui-border-2: #373737; + --bui-fg-warning: #e36d05; --bui-border-danger: #f87a7a; --bui-border-warning: #e36d05; --bui-border-success: #53db83; - - --bui-ring: rgba(255, 255, 255, 0.2); } diff --git a/packages/eslint-plugin/index.js b/packages/eslint-plugin/index.js index e31091ace3..69065f091d 100644 --- a/packages/eslint-plugin/index.js +++ b/packages/eslint-plugin/index.js @@ -25,6 +25,7 @@ module.exports = { '@backstage/no-mixed-plugin-imports': 'warn', '@backstage/no-ui-css-imports-in-non-frontend': 'error', '@backstage/no-self-package-imports': 'error', + '@backstage/no-deprecated-bui-tokens': 'warn', }, }, }, @@ -36,5 +37,6 @@ module.exports = { 'no-mixed-plugin-imports': require('./rules/no-mixed-plugin-imports'), 'no-ui-css-imports-in-non-frontend': require('./rules/no-ui-css-imports-in-non-frontend'), 'no-self-package-imports': require('./rules/no-self-package-imports'), + 'no-deprecated-bui-tokens': require('./rules/no-deprecated-bui-tokens'), }, }; diff --git a/packages/eslint-plugin/rules/no-deprecated-bui-tokens.js b/packages/eslint-plugin/rules/no-deprecated-bui-tokens.js new file mode 100644 index 0000000000..831688efd8 --- /dev/null +++ b/packages/eslint-plugin/rules/no-deprecated-bui-tokens.js @@ -0,0 +1,122 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-check + +/** @type {string[]} */ +const DEPRECATED_TOKENS = [ + '--bui-bg-solid', + '--bui-bg-solid-hover', + '--bui-bg-solid-pressed', + '--bui-bg-solid-disabled', + '--bui-bg-app', + '--bui-bg-neutral-1', + '--bui-bg-neutral-1-hover', + '--bui-bg-neutral-1-pressed', + '--bui-bg-neutral-1-disabled', + '--bui-bg-neutral-2', + '--bui-bg-neutral-2-hover', + '--bui-bg-neutral-2-pressed', + '--bui-bg-neutral-2-disabled', + '--bui-bg-neutral-3', + '--bui-bg-neutral-3-hover', + '--bui-bg-neutral-3-pressed', + '--bui-bg-neutral-3-disabled', + '--bui-bg-neutral-4', + '--bui-bg-neutral-4-hover', + '--bui-bg-neutral-4-pressed', + '--bui-bg-neutral-4-disabled', + '--bui-bg-danger', + '--bui-bg-warning', + '--bui-bg-success', + '--bui-bg-info', + '--bui-fg-solid', + '--bui-fg-solid-disabled', + '--bui-fg-danger-on-bg', + '--bui-fg-warning-on-bg', + '--bui-fg-success-on-bg', + '--bui-fg-info-on-bg', + '--bui-fg-danger', + '--bui-fg-success', + '--bui-fg-info', + '--bui-border-info', + '--bui-border-danger', + '--bui-border-warning', + '--bui-border-success', + '--bui-shadow', +]; + +const DEPRECATED_SET = new Set(DEPRECATED_TOKENS); + +/** + * Extracts all CSS custom property names referenced inside a string value, + * e.g. "var(--bui-bg-solid)" → ["--bui-bg-solid"] + * @param {string} value + * @returns {string[]} + */ +function extractTokenNames(value) { + const matches = value.match(/--bui-[\w-]+/g); + return matches ?? []; +} + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: + 'Warn when deprecated Backstage UI CSS tokens are referenced in JS/TS string literals.', + url: 'https://github.com/backstage/backstage/blob/master/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md', + }, + messages: { + deprecated: + "'{{token}}' is a deprecated BUI token. Use the new semantic token families instead: --bui-negative-*, --bui-positive-*, --bui-warning-*, --bui-announcement-*, or --bui-accent-*.", + }, + schema: [], + }, + + create(context) { + /** + * @param {import('estree').Node} node + * @param {string} value + */ + function checkValue(node, value) { + for (const token of extractTokenNames(value)) { + if (DEPRECATED_SET.has(token)) { + context.report({ + node, + messageId: 'deprecated', + data: { token }, + }); + } + } + } + + return { + Literal(node) { + if (typeof node.value === 'string') { + checkValue(node, node.value); + } + }, + + TemplateElement(node) { + if (node.value?.cooked) { + checkValue(node, node.value.cooked); + } + }, + }; + }, +}; diff --git a/packages/ui/.eslintrc.js b/packages/ui/.eslintrc.js index 9638ff6e45..b098bceebb 100644 --- a/packages/ui/.eslintrc.js +++ b/packages/ui/.eslintrc.js @@ -3,7 +3,8 @@ module.exports = { extends: ['plugin:storybook/recommended'], rules: { 'react/forbid-elements': 'off', - '@backstage/no-mixed-plugin-imports': 'off' + '@backstage/no-mixed-plugin-imports': 'off', + '@backstage/no-deprecated-bui-tokens': 'warn', }, }; diff --git a/packages/ui/src/css/colors.stories.tsx b/packages/ui/src/css/colors.stories.tsx index 49e3cdaec5..e7d86cf6c3 100644 --- a/packages/ui/src/css/colors.stories.tsx +++ b/packages/ui/src/css/colors.stories.tsx @@ -14,192 +14,296 @@ * limitations under the License. */ import preview from '../../../../.storybook/preview'; -import { Box } from '../components/Box'; -import { Flex } from '../components/Flex'; -import { Text } from '../components/Text'; const meta = preview.meta({ title: 'Backstage UI/Colors', tags: ['!manifest'], }); -/** - * A `
` styled with `background: var(--bui-bg-inherit)` should always - * resolve to the same color as the surrounding bg provider — at every level - * of the neutral chain and inside intent surfaces. - */ -const Probe = ({ label }: { label: string }) => ( +type SwatchItem = { label: string; token: string }; + +const Swatch = ({ label, token }: SwatchItem) => (
- {label} +
+
+ + {label} + + + {token} + +
); -export const Default = meta.story({ +const Section = ({ + title, + tokens, +}: { + title: string; + tokens: SwatchItem[]; +}) => ( +
+
+ {title} +
+ {tokens.map(t => ( + + ))} +
+); + +export const All = meta.story({ + name: 'All Colors', render: () => ( -
- - - Neutral 1 - - Hover - - - Pressed - - - Disabled - - - - - Neutral 2 - - Hover - - - Pressed - - - Disabled - - - - - Neutral 3 - - Hover - - - Pressed - - - - Disabled - - - - - - Neutral 4 - - Hover - - - Pressed - - - - Disabled - - - - - - - +
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
), }); - -export const BgInherit = meta.story({ - render: () => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ), -}); diff --git a/packages/ui/src/css/tokens.css b/packages/ui/src/css/tokens.css index 9426993ea5..d50822613b 100644 --- a/packages/ui/src/css/tokens.css +++ b/packages/ui/src/css/tokens.css @@ -67,150 +67,276 @@ --bui-radius-6: calc(1.25rem); --bui-radius-full: 9999px; + /* Animations */ + --bui-animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; + /* Base Colors */ --bui-black: #000000; --bui-white: #ffffff; + --bui-ring: #1f5493; + --bui-scrollbar: #a0a0a03b; + --bui-scrollbar-thumb: #a0a0a0; - /* Solid background colors */ + /* Gray scale */ + --bui-gray-1: #fafafa; + --bui-gray-2: #f5f5f5; + --bui-gray-3: #e5e5e5; + --bui-gray-4: #d4d4d4; + --bui-gray-5: #a1a1a1; + --bui-gray-6: #737373; + --bui-gray-7: #525252; + --bui-gray-8: #404040; + --bui-gray-9: #262626; + --bui-gray-10: #171717; + --bui-gray-11: #0a0a0a; + + /* Surface colors */ + --bui-surface-1: #f5f5f5; + --bui-surface-2: var(--bui-white); + --bui-surface-3: #f7f7f7; + --bui-surface-4: var(--bui-white); + --bui-surface-5: #f7f7f7; + + /* Foreground colors */ + --bui-fg-primary: var(--bui-black); + --bui-fg-secondary: #696969; + --bui-fg-disabled: #aaaaaa; + --bui-fg-positive: #1abc54; + --bui-fg-negative: #ee3a4c; + --bui-fg-warning: #f18900; + --bui-fg-announcement: #2a86f3; + + /* Border colors */ + --bui-border-1: var(--bui-gray-3); + --bui-border-2: var(--bui-gray-6); + + /* Accent */ + --bui-accent-bg: #1f5493; + --bui-accent-bg-hover: #163a66; + --bui-accent-bg-disabled: #163a66; + --bui-accent-fg: #ffffff; + --bui-accent-fg-disabled: #98a8bc; + + /* Announcement */ + --bui-announcement-bg: #0d72ea; + --bui-announcement-bg-hover: #2b88f5; + --bui-announcement-bg-disabled: #0d72ea; + --bui-announcement-bg-subdued: #c8e0fc; + --bui-announcement-bg-subdued-hover: #e0edfd; + --bui-announcement-bg-subdued-disabled: #c8e0fc; + --bui-announcement-border: #549ef4; + --bui-announcement-fg: #ffffff; + --bui-announcement-fg-disabled: #ffffff; + --bui-announcement-fg-subdued: #173da6; + --bui-announcement-fg-subdued-disabled: #173da6; + + /* Warning */ + --bui-warning-bg: #ff9400; + --bui-warning-bg-hover: #ffb656; + --bui-warning-bg-disabled: #ff9400; + --bui-warning-bg-subdued: #ffedd5; + --bui-warning-bg-subdued-hover: #f8ddb9; + --bui-warning-bg-subdued-disabled: #f8ddb9; + --bui-warning-border: #ff9400; + --bui-warning-fg: #000000; + --bui-warning-fg-disabled: #000000; + --bui-warning-fg-subdued: #aa4d00; + --bui-warning-fg-subdued-disabled: #aa4d00; + + /* Negative */ + --bui-negative-bg: #dc2626; + --bui-negative-bg-hover: #f44b4b; + --bui-negative-bg-disabled: #ff8686; + --bui-negative-bg-subdued: #ffcbcb; + --bui-negative-bg-subdued-hover: #f7b6b6; + --bui-negative-bg-subdued-disabled: #f7b6b6; + --bui-negative-border: #d06565; + --bui-negative-fg: #ffffff; + --bui-negative-fg-disabled: #ffffff; + --bui-negative-fg-subdued: #991919; + --bui-negative-fg-subdued-disabled: #bb7272; + + /* Positive */ + --bui-positive-bg: #1ed760; + --bui-positive-bg-hover: #3be477; + --bui-positive-bg-disabled: #1abc54; + --bui-positive-bg-subdued: #96f0b6; + --bui-positive-bg-subdued-hover: #c5f7d7; + --bui-positive-bg-subdued-disabled: #96f0b6; + --bui-positive-border: #54a671; + --bui-positive-fg: #000000; + --bui-positive-fg-disabled: #ffffff; + --bui-positive-fg-subdued: #0c632b; + --bui-positive-fg-subdued-disabled: #0c632b; + + /* Deprecated tokens */ --bui-bg-solid: #1f5493; --bui-bg-solid-hover: #163a66; --bui-bg-solid-pressed: #0f2b4e; --bui-bg-solid-disabled: #163a66; - - /* Neutral background colors */ --bui-bg-app: #f8f8f8; - --bui-bg-neutral-1: #fff; --bui-bg-neutral-1-hover: oklch(0% 0 0 / 6%); --bui-bg-neutral-1-pressed: oklch(0% 0 0 / 12%); --bui-bg-neutral-1-disabled: oklch(0% 0 0 / 6%); - --bui-bg-neutral-2: oklch(0% 0 0 / 6%); --bui-bg-neutral-2-hover: oklch(0% 0 0 / 12%); --bui-bg-neutral-2-pressed: oklch(0% 0 0 / 16%); --bui-bg-neutral-2-disabled: oklch(0% 0 0 / 6%); - --bui-bg-neutral-3: oklch(0% 0 0 / 6%); --bui-bg-neutral-3-hover: oklch(0% 0 0 / 12%); --bui-bg-neutral-3-pressed: oklch(0% 0 0 / 16%); --bui-bg-neutral-3-disabled: oklch(0% 0 0 / 6%); - --bui-bg-neutral-4: oklch(0% 0 0 / 6%); --bui-bg-neutral-4-hover: oklch(0% 0 0 / 12%); --bui-bg-neutral-4-pressed: oklch(0% 0 0 / 16%); --bui-bg-neutral-4-disabled: oklch(0% 0 0 / 6%); - - /* Status background colors */ --bui-bg-danger: #ffe2e2; --bui-bg-warning: #ffedd5; --bui-bg-success: #dcfce7; --bui-bg-info: #dbeafe; - - /* Foreground colors */ - --bui-fg-primary: var(--bui-black); - --bui-fg-secondary: oklch(0% 0 0 / 50%); - --bui-fg-disabled: oklch(0% 0 0 / 28%); --bui-fg-solid: var(--bui-white); --bui-fg-solid-disabled: #98a8bc; - - /* Foreground Statuses */ --bui-fg-danger-on-bg: #991919; --bui-fg-warning-on-bg: #92310a; --bui-fg-success-on-bg: #116932; --bui-fg-info-on-bg: #173da6; --bui-fg-danger: #ec3b18; - --bui-fg-warning: #ef7a32; --bui-fg-success: #1aaf4f; --bui-fg-info: #0d74ce; - - /* Border colors */ - --bui-border-1: #d5d5d5; - --bui-border-2: #bababa; --bui-border-info: #7ea9d6; --bui-border-danger: #f87a7a; --bui-border-warning: #e36d05; --bui-border-success: #53db83; - - /* Special colors */ - --bui-ring: #1f5493; - --bui-scrollbar: #a0a0a03b; - --bui-scrollbar-thumb: #a0a0a0; - - /* Shadows */ --bui-shadow: 0 10px 15px -3px rgba(0 0 0 / 0.1), 0 4px 6px -4px rgba(0 0 0 / 0.1); - - --bui-animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; } /* Dark theme tokens */ [data-theme-mode='dark'] { - /* Solid background colors */ + /* Base colors */ + --bui-ring: #1f5493; + --bui-scrollbar: #3636363a; + --bui-scrollbar-thumb: #575757; + + /* Surface colors */ + --bui-surface-1: #333333; + --bui-surface-2: #1a1a1a; + --bui-surface-3: #282828; + --bui-surface-4: #393939; + --bui-surface-5: #474747; + + /* Foreground colors */ + --bui-fg-primary: var(--bui-white); + --bui-fg-secondary: #a3a3a3; + --bui-fg-disabled: var(--bui-white); + --bui-fg-positive: #21b656; + --bui-fg-negative: #ee3a4c; + --bui-fg-warning: #f18900; + --bui-fg-announcement: #2a86f3; + + /* Border colors */ + --bui-border-1: var(--bui-gray-6); + --bui-border-2: var(--bui-gray-5); + + /* Accent */ + --bui-accent-bg: #9cc9ff; + --bui-accent-bg-hover: #83b9fd; + --bui-accent-bg-disabled: #3b6293; + --bui-accent-fg: #101821; + --bui-accent-fg-disabled: #6191cc; + + /* Announcement */ + --bui-announcement-bg: #0d72ea; + --bui-announcement-bg-hover: #2b88f5; + --bui-announcement-bg-disabled: #0d72ea; + --bui-announcement-bg-subdued: #052a56; + --bui-announcement-bg-subdued-hover: #0c3d77; + --bui-announcement-bg-subdued-disabled: #052a56; + --bui-announcement-border: #2363af; + --bui-announcement-fg: #ffffff; + --bui-announcement-fg-disabled: #ffffff; + --bui-announcement-fg-subdued: #70b8ff; + --bui-announcement-fg-subdued-disabled: #70b8ff; + + /* Warning */ + --bui-warning-bg: #ffa42c; + --bui-warning-bg-hover: #ffb656; + --bui-warning-bg-disabled: #ffb656; + --bui-warning-bg-subdued: #491e00; + --bui-warning-bg-subdued-hover: #612901; + --bui-warning-bg-subdued-disabled: #582400; + --bui-warning-border: #7c4903; + --bui-warning-fg: #000000; + --bui-warning-fg-disabled: #000000; + --bui-warning-fg-subdued: #f2ab4a; + --bui-warning-fg-subdued-disabled: #f2ab4a; + + /* Negative */ + --bui-negative-bg: #dc2626; + --bui-negative-bg-hover: #f44b4b; + --bui-negative-bg-disabled: #dc2626; + --bui-negative-bg-subdued: #350708; + --bui-negative-bg-subdued-hover: #5e1a1b; + --bui-negative-bg-subdued-disabled: #5e1a1b; + --bui-negative-border: #6f101c; + --bui-negative-fg: #ffffff; + --bui-negative-fg-disabled: #ffffff; + --bui-negative-fg-subdued: #fca5a5; + --bui-negative-fg-subdued-disabled: #ffffff; + + /* Positive */ + --bui-positive-bg: #1ed760; + --bui-positive-bg-hover: #3be477; + --bui-positive-bg-disabled: #1abc54; + --bui-positive-bg-subdued: #073116; + --bui-positive-bg-subdued-hover: #0b431f; + --bui-positive-bg-subdued-disabled: #073116; + --bui-positive-border: #136d33; + --bui-positive-fg: #000000; + --bui-positive-fg-disabled: #ffffff; + --bui-positive-fg-subdued: #8ddeaa; + --bui-positive-fg-subdued-disabled: #8ddeaa; + + /* Deprecated tokens */ --bui-bg-solid: #9cc9ff; --bui-bg-solid-hover: #83b9fd; --bui-bg-solid-pressed: #83b9fd; --bui-bg-solid-disabled: #1b3d68; - - /* Neutral background colors */ --bui-bg-app: #333333; - --bui-bg-neutral-1: oklch(100% 0 0 / 10%); --bui-bg-neutral-1-hover: oklch(100% 0 0 / 14%); --bui-bg-neutral-1-pressed: oklch(100% 0 0 / 20%); --bui-bg-neutral-1-disabled: oklch(100% 0 0 / 10%); - --bui-bg-neutral-2: oklch(100% 0 0 / 6%); --bui-bg-neutral-2-hover: oklch(100% 0 0 / 10%); --bui-bg-neutral-2-pressed: oklch(100% 0 0 / 16%); --bui-bg-neutral-2-disabled: oklch(100% 0 0 / 6%); - --bui-bg-neutral-3: oklch(100% 0 0 / 8%); --bui-bg-neutral-3-hover: oklch(100% 0 0 / 12%); --bui-bg-neutral-3-pressed: oklch(100% 0 0 / 20%); --bui-bg-neutral-3-disabled: oklch(100% 0 0 / 8%); - --bui-bg-neutral-4: oklch(100% 0 0 / 8%); --bui-bg-neutral-4-hover: oklch(100% 0 0 / 12%); --bui-bg-neutral-4-pressed: oklch(100% 0 0 / 20%); --bui-bg-neutral-4-disabled: oklch(100% 0 0 / 8%); - - /* Status background colors */ --bui-bg-danger: #300c0c; --bui-bg-warning: #302008; --bui-bg-success: #042713; --bui-bg-info: #132049; - - /* Foreground colors */ - --bui-fg-primary: var(--bui-white); - --bui-fg-secondary: oklch(100% 0 0 / 50%); - --bui-fg-disabled: oklch(100% 0 0 / 28%); --bui-fg-solid: #101821; --bui-fg-solid-disabled: #6191cc; - - /* Foreground statuses */ --bui-fg-danger-on-bg: #fca5a5; --bui-fg-warning-on-bg: #fdba74; --bui-fg-success-on-bg: #86efac; --bui-fg-info-on-bg: #a3cfff; --bui-fg-danger: #ff5a30; - --bui-fg-warning: #ffa057; --bui-fg-success: #1ed760; --bui-fg-info: #70b8ff; - - /* Border colors */ - --bui-border-1: #434343; - --bui-border-2: #585858; --bui-border-info: #7ea9d6; --bui-border-danger: #f87a7a; --bui-border-warning: #e36d05; --bui-border-success: #53db83; - - /* Special colors */ - --bui-ring: #1f5493; - --bui-scrollbar: #3636363a; - --bui-scrollbar-thumb: #575757; - - /* Shadows */ --bui-shadow: none; } } From ae87db249a36253ee0c2d2ce3c287a43caf99b38 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Tue, 26 May 2026 15:46:15 +0200 Subject: [PATCH 02/16] test(eslint-plugin): add RuleTester tests for no-deprecated-bui-tokens Covers plain string literals, template literals, multiple tokens in one string, non-deprecated --bui-* tokens (valid cases), and JSX inline style attribute usage. Signed-off-by: Charles de Dreuille --- .../src/no-deprecated-bui-tokens.test.ts | 263 ++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 packages/eslint-plugin/src/no-deprecated-bui-tokens.test.ts diff --git a/packages/eslint-plugin/src/no-deprecated-bui-tokens.test.ts b/packages/eslint-plugin/src/no-deprecated-bui-tokens.test.ts new file mode 100644 index 0000000000..62a8dfd7e9 --- /dev/null +++ b/packages/eslint-plugin/src/no-deprecated-bui-tokens.test.ts @@ -0,0 +1,263 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RuleTester } from 'eslint'; +import rule from '../rules/no-deprecated-bui-tokens'; + +const ruleTester = new RuleTester({ + parserOptions: { + sourceType: 'module', + ecmaVersion: 2021, + }, +}); + +ruleTester.run('no-deprecated-bui-tokens', rule, { + valid: [ + // Non-deprecated tokens — should not warn + { code: `const s = 'var(--bui-fg-primary)'` }, + { code: `const s = 'var(--bui-fg-secondary)'` }, + { code: `const s = 'var(--bui-fg-disabled)'` }, + { code: `const s = 'var(--bui-fg-warning)'` }, + { code: `const s = 'var(--bui-fg-positive)'` }, + { code: `const s = 'var(--bui-fg-negative)'` }, + { code: `const s = 'var(--bui-fg-announcement)'` }, + { code: `const s = 'var(--bui-border-1)'` }, + { code: `const s = 'var(--bui-border-2)'` }, + { code: `const s = 'var(--bui-accent-bg)'` }, + { code: `const s = 'var(--bui-accent-bg-hover)'` }, + { code: `const s = 'var(--bui-accent-bg-disabled)'` }, + { code: `const s = 'var(--bui-accent-fg)'` }, + { code: `const s = 'var(--bui-accent-fg-disabled)'` }, + { code: `const s = 'var(--bui-negative-bg)'` }, + { code: `const s = 'var(--bui-positive-bg)'` }, + { code: `const s = 'var(--bui-warning-bg)'` }, + { code: `const s = 'var(--bui-announcement-bg)'` }, + { code: `const s = 'var(--bui-gray-1)'` }, + { code: `const s = 'var(--bui-surface-1)'` }, + // Unrelated strings — should not warn + { code: `const s = 'some-other-string'` }, + { code: `const n = 42` }, + ], + + invalid: [ + // Plain string literals + { + code: `const s = 'var(--bui-bg-solid)'`, + errors: [{ messageId: 'deprecated', data: { token: '--bui-bg-solid' } }], + }, + { + code: `const s = 'var(--bui-bg-solid-hover)'`, + errors: [ + { messageId: 'deprecated', data: { token: '--bui-bg-solid-hover' } }, + ], + }, + { + code: `const s = 'var(--bui-bg-solid-pressed)'`, + errors: [ + { messageId: 'deprecated', data: { token: '--bui-bg-solid-pressed' } }, + ], + }, + { + code: `const s = 'var(--bui-bg-solid-disabled)'`, + errors: [ + { + messageId: 'deprecated', + data: { token: '--bui-bg-solid-disabled' }, + }, + ], + }, + { + code: `const s = 'var(--bui-bg-app)'`, + errors: [{ messageId: 'deprecated', data: { token: '--bui-bg-app' } }], + }, + { + code: `const s = 'var(--bui-bg-neutral-1)'`, + errors: [ + { messageId: 'deprecated', data: { token: '--bui-bg-neutral-1' } }, + ], + }, + { + code: `const s = 'var(--bui-bg-neutral-2-hover)'`, + errors: [ + { + messageId: 'deprecated', + data: { token: '--bui-bg-neutral-2-hover' }, + }, + ], + }, + { + code: `const s = 'var(--bui-bg-neutral-3-pressed)'`, + errors: [ + { + messageId: 'deprecated', + data: { token: '--bui-bg-neutral-3-pressed' }, + }, + ], + }, + { + code: `const s = 'var(--bui-bg-neutral-4-disabled)'`, + errors: [ + { + messageId: 'deprecated', + data: { token: '--bui-bg-neutral-4-disabled' }, + }, + ], + }, + { + code: `const s = 'var(--bui-bg-danger)'`, + errors: [{ messageId: 'deprecated', data: { token: '--bui-bg-danger' } }], + }, + { + code: `const s = 'var(--bui-bg-warning)'`, + errors: [ + { messageId: 'deprecated', data: { token: '--bui-bg-warning' } }, + ], + }, + { + code: `const s = 'var(--bui-bg-success)'`, + errors: [ + { messageId: 'deprecated', data: { token: '--bui-bg-success' } }, + ], + }, + { + code: `const s = 'var(--bui-bg-info)'`, + errors: [{ messageId: 'deprecated', data: { token: '--bui-bg-info' } }], + }, + { + code: `const s = 'var(--bui-fg-solid)'`, + errors: [{ messageId: 'deprecated', data: { token: '--bui-fg-solid' } }], + }, + { + code: `const s = 'var(--bui-fg-solid-disabled)'`, + errors: [ + { messageId: 'deprecated', data: { token: '--bui-fg-solid-disabled' } }, + ], + }, + { + code: `const s = 'var(--bui-fg-danger-on-bg)'`, + errors: [ + { messageId: 'deprecated', data: { token: '--bui-fg-danger-on-bg' } }, + ], + }, + { + code: `const s = 'var(--bui-fg-warning-on-bg)'`, + errors: [ + { messageId: 'deprecated', data: { token: '--bui-fg-warning-on-bg' } }, + ], + }, + { + code: `const s = 'var(--bui-fg-success-on-bg)'`, + errors: [ + { messageId: 'deprecated', data: { token: '--bui-fg-success-on-bg' } }, + ], + }, + { + code: `const s = 'var(--bui-fg-info-on-bg)'`, + errors: [ + { messageId: 'deprecated', data: { token: '--bui-fg-info-on-bg' } }, + ], + }, + { + code: `const s = 'var(--bui-fg-danger)'`, + errors: [{ messageId: 'deprecated', data: { token: '--bui-fg-danger' } }], + }, + { + code: `const s = 'var(--bui-fg-success)'`, + errors: [ + { messageId: 'deprecated', data: { token: '--bui-fg-success' } }, + ], + }, + { + code: `const s = 'var(--bui-fg-info)'`, + errors: [{ messageId: 'deprecated', data: { token: '--bui-fg-info' } }], + }, + { + code: `const s = 'var(--bui-border-info)'`, + errors: [ + { messageId: 'deprecated', data: { token: '--bui-border-info' } }, + ], + }, + { + code: `const s = 'var(--bui-border-danger)'`, + errors: [ + { messageId: 'deprecated', data: { token: '--bui-border-danger' } }, + ], + }, + { + code: `const s = 'var(--bui-border-warning)'`, + errors: [ + { messageId: 'deprecated', data: { token: '--bui-border-warning' } }, + ], + }, + { + code: `const s = 'var(--bui-border-success)'`, + errors: [ + { messageId: 'deprecated', data: { token: '--bui-border-success' } }, + ], + }, + { + code: `const s = 'var(--bui-shadow)'`, + errors: [{ messageId: 'deprecated', data: { token: '--bui-shadow' } }], + }, + + // Template literals + { + code: 'const s = `var(--bui-bg-solid)`', + errors: [{ messageId: 'deprecated', data: { token: '--bui-bg-solid' } }], + }, + { + code: 'const s = `var(--bui-fg-danger)`', + errors: [{ messageId: 'deprecated', data: { token: '--bui-fg-danger' } }], + }, + { + code: 'const s = `border: 1px solid var(--bui-border-danger)`', + errors: [ + { messageId: 'deprecated', data: { token: '--bui-border-danger' } }, + ], + }, + + // Multiple deprecated tokens in one string — one error per token + { + code: `const s = 'background: var(--bui-bg-solid); color: var(--bui-fg-solid)'`, + errors: [ + { messageId: 'deprecated', data: { token: '--bui-bg-solid' } }, + { messageId: 'deprecated', data: { token: '--bui-fg-solid' } }, + ], + }, + + // Inline style usage patterns (JSX) + { + code: `const el =
`, + parserOptions: { + sourceType: 'module', + ecmaVersion: 2021, + ecmaFeatures: { jsx: true }, + }, + errors: [{ messageId: 'deprecated', data: { token: '--bui-bg-danger' } }], + }, + { + code: `const el =
`, + parserOptions: { + sourceType: 'module', + ecmaVersion: 2021, + ecmaFeatures: { jsx: true }, + }, + errors: [ + { messageId: 'deprecated', data: { token: '--bui-fg-success' } }, + ], + }, + ], +}); From ad1936ee31284ef9f2a582a3d8b1c3d42129cd36 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Tue, 26 May 2026 15:46:51 +0200 Subject: [PATCH 03/16] Update spotify.css Signed-off-by: Charles de Dreuille --- .storybook/themes/spotify.css | 1 - 1 file changed, 1 deletion(-) diff --git a/.storybook/themes/spotify.css b/.storybook/themes/spotify.css index 1e8ef5090c..20ecb34062 100644 --- a/.storybook/themes/spotify.css +++ b/.storybook/themes/spotify.css @@ -233,7 +233,6 @@ --bui-fg-solid-disabled: #072f15; --bui-fg-danger: #e22b2b; --bui-fg-success: #1db954; - --bui-fg-warning: #e36d05; --bui-border-danger: #f87a7a; --bui-border-warning: #e36d05; --bui-border-success: #53db83; From d80062d18e428fe47671916fdc126e58d8910e4b Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Tue, 26 May 2026 15:49:06 +0200 Subject: [PATCH 04/16] docs(eslint-plugin): add docs page for no-deprecated-bui-tokens rule Adds the missing documentation page at the URL referenced in the rule meta, consistent with all other rules in the plugin. Includes incorrect/ correct code examples and a migration table mapping each deprecated token to its semantic replacement. Signed-off-by: Charles de Dreuille --- .../docs/rules/no-deprecated-bui-tokens.md | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md diff --git a/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md b/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md new file mode 100644 index 0000000000..5159ab3a5c --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md @@ -0,0 +1,64 @@ +# no-deprecated-bui-tokens + +Warns when a deprecated `@backstage/ui` CSS token is referenced inside a JS/TS string or template literal. + +The `@backstage/ui` design token system has been updated with a new set of semantic color families. The old tokens still work for backward compatibility, but they are scheduled for removal in a future major version. + +## Rule Details + +This rule reports a warning for any string value that contains a reference to a deprecated BUI CSS custom property, such as `var(--bui-bg-solid)` or `var(--bui-fg-danger)`. + +Examples of **incorrect** code for this rule: + +```js +// Deprecated background token +const style = { background: 'var(--bui-bg-solid)' }; + +// Deprecated foreground token +const style = { color: 'var(--bui-fg-danger)' }; + +// Deprecated token in a template literal +const css = `border: 1px solid var(--bui-border-danger)`; +``` + +Examples of **correct** code for this rule: + +```js +// New accent family +const style = { background: 'var(--bui-accent-bg)' }; + +// New semantic negative family +const style = { color: 'var(--bui-negative-fg-subdued)' }; + +// New semantic positive family +const style = { borderColor: 'var(--bui-positive-border)' }; +``` + +## Migration Guide + +Replace deprecated tokens with their equivalents from the new semantic families: + +| Deprecated token | Replacement | +| ------------------------- | ------------------------------- | +| `--bui-bg-solid` | `--bui-accent-bg` | +| `--bui-bg-solid-hover` | `--bui-accent-bg-hover` | +| `--bui-bg-solid-disabled` | `--bui-accent-bg-disabled` | +| `--bui-fg-solid` | `--bui-accent-fg` | +| `--bui-fg-solid-disabled` | `--bui-accent-fg-disabled` | +| `--bui-bg-danger` | `--bui-negative-bg-subdued` | +| `--bui-bg-warning` | `--bui-warning-bg-subdued` | +| `--bui-bg-success` | `--bui-positive-bg-subdued` | +| `--bui-bg-info` | `--bui-announcement-bg-subdued` | +| `--bui-fg-danger` | `--bui-negative-fg-subdued` | +| `--bui-fg-success` | `--bui-positive-fg-subdued` | +| `--bui-fg-info` | `--bui-announcement-fg-subdued` | +| `--bui-fg-danger-on-bg` | `--bui-negative-fg-subdued` | +| `--bui-fg-warning-on-bg` | `--bui-warning-fg-subdued` | +| `--bui-fg-success-on-bg` | `--bui-positive-fg-subdued` | +| `--bui-fg-info-on-bg` | `--bui-announcement-fg-subdued` | +| `--bui-border-danger` | `--bui-negative-border` | +| `--bui-border-warning` | `--bui-warning-border` | +| `--bui-border-success` | `--bui-positive-border` | +| `--bui-border-info` | `--bui-announcement-border` | +| `--bui-bg-neutral-1` | `--bui-surface-1` | +| `--bui-bg-app` | `--bui-surface-1` | From 5d80f77e43aff4e31f1dcb105d9a43380edc8f97 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Tue, 26 May 2026 21:25:52 +0200 Subject: [PATCH 05/16] chore: add changesets and fix colors story layout - Add patch changeset for @backstage/ui (new semantic color token families) - Add patch changeset for @backstage/eslint-plugin (no-deprecated-bui-tokens rule) - Remove redundant wrapper div in the Colors story and drop minHeight that was preventing scroll in the Storybook canvas Signed-off-by: Charles de Dreuille --- ...slint-plugin-deprecated-bui-tokens-rule.md | 5 + .changeset/ui-semantic-color-tokens.md | 7 + packages/ui/src/css/colors.stories.tsx | 383 +++++++++--------- 3 files changed, 200 insertions(+), 195 deletions(-) create mode 100644 .changeset/eslint-plugin-deprecated-bui-tokens-rule.md create mode 100644 .changeset/ui-semantic-color-tokens.md diff --git a/.changeset/eslint-plugin-deprecated-bui-tokens-rule.md b/.changeset/eslint-plugin-deprecated-bui-tokens-rule.md new file mode 100644 index 0000000000..a49e90b2ec --- /dev/null +++ b/.changeset/eslint-plugin-deprecated-bui-tokens-rule.md @@ -0,0 +1,5 @@ +--- +'@backstage/eslint-plugin': patch +--- + +Adds a new `@backstage/no-deprecated-bui-tokens` lint rule that warns when a deprecated `@backstage/ui` CSS token is referenced in a JavaScript or TypeScript file. The rule is included in the `recommended` config, so plugin authors using `plugin:@backstage/recommended` will receive warnings automatically when using tokens that have been superseded by the new semantic color families. diff --git a/.changeset/ui-semantic-color-tokens.md b/.changeset/ui-semantic-color-tokens.md new file mode 100644 index 0000000000..65ad89e6e8 --- /dev/null +++ b/.changeset/ui-semantic-color-tokens.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Introduces a new set of semantic color token families — Accent, Announcement, Warning, Negative, and Positive — each providing a consistent set of background, foreground, and border tokens for both light and dark themes. A gray scale (`--bui-gray-1` through `--bui-gray-11`) and updated foreground tokens are also included. + +The previous tokens (`--bui-bg-solid-*`, `--bui-bg-neutral-*`, `--bui-bg-danger/warning/success/info`, `--bui-fg-solid`, `--bui-fg-danger/success/info`, `--bui-border-*`, and `--bui-shadow`) remain in place for backward compatibility but are now deprecated and will be removed in a future release. diff --git a/packages/ui/src/css/colors.stories.tsx b/packages/ui/src/css/colors.stories.tsx index e7d86cf6c3..83425cc85a 100644 --- a/packages/ui/src/css/colors.stories.tsx +++ b/packages/ui/src/css/colors.stories.tsx @@ -98,212 +98,205 @@ export const All = meta.story({ render: () => (
-
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
), }); From 4d223156a2b22d48af7489dd15149667945d018f Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Tue, 26 May 2026 21:29:47 +0200 Subject: [PATCH 06/16] docs(ui): add full migration table to changeset Maps every deprecated token to its semantic replacement, grouped by family: Surfaces, Foreground, Accent, Positive, Negative, Warning, and Announcement. Signed-off-by: Charles de Dreuille --- .changeset/ui-semantic-color-tokens.md | 64 +++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/.changeset/ui-semantic-color-tokens.md b/.changeset/ui-semantic-color-tokens.md index 65ad89e6e8..7798193f7e 100644 --- a/.changeset/ui-semantic-color-tokens.md +++ b/.changeset/ui-semantic-color-tokens.md @@ -4,4 +4,66 @@ Introduces a new set of semantic color token families — Accent, Announcement, Warning, Negative, and Positive — each providing a consistent set of background, foreground, and border tokens for both light and dark themes. A gray scale (`--bui-gray-1` through `--bui-gray-11`) and updated foreground tokens are also included. -The previous tokens (`--bui-bg-solid-*`, `--bui-bg-neutral-*`, `--bui-bg-danger/warning/success/info`, `--bui-fg-solid`, `--bui-fg-danger/success/info`, `--bui-border-*`, and `--bui-shadow`) remain in place for backward compatibility but are now deprecated and will be removed in a future release. +The previous tokens remain in place for backward compatibility but are now deprecated and will be removed in a future release. Migrate using the table below. + +## Migration + +### Surfaces + +| Deprecated | Replacement | +| -------------------- | ----------------- | +| `--bui-bg-app` | `--bui-surface-1` | +| `--bui-bg-neutral-1` | `--bui-surface-2` | +| `--bui-bg-neutral-2` | `--bui-surface-3` | +| `--bui-bg-neutral-3` | `--bui-surface-4` | +| `--bui-bg-neutral-4` | `--bui-surface-5` | + +### Foreground + +| Deprecated | Replacement | +| ------------------ | ----------------------- | +| `--bui-fg-danger` | `--bui-fg-negative` | +| `--bui-fg-success` | `--bui-fg-positive` | +| `--bui-fg-info` | `--bui-fg-announcement` | + +### Accent + +| Deprecated | Replacement | +| ------------------------- | -------------------------- | +| `--bui-bg-solid` | `--bui-accent-bg` | +| `--bui-bg-solid-hover` | `--bui-accent-bg-hover` | +| `--bui-bg-solid-disabled` | `--bui-accent-bg-disabled` | +| `--bui-fg-solid` | `--bui-accent-fg` | +| `--bui-fg-solid-disabled` | `--bui-accent-fg-disabled` | + +### Positive + +| Deprecated | Replacement | +| ------------------------ | --------------------------- | +| `--bui-bg-success` | `--bui-positive-bg-subdued` | +| `--bui-fg-success-on-bg` | `--bui-positive-fg-subdued` | +| `--bui-border-success` | `--bui-positive-border` | + +### Negative + +| Deprecated | Replacement | +| ----------------------- | --------------------------- | +| `--bui-bg-danger` | `--bui-negative-bg-subdued` | +| `--bui-fg-danger-on-bg` | `--bui-negative-fg-subdued` | +| `--bui-border-danger` | `--bui-negative-border` | + +### Warning + +| Deprecated | Replacement | +| ------------------------ | -------------------------- | +| `--bui-bg-warning` | `--bui-warning-bg-subdued` | +| `--bui-fg-warning-on-bg` | `--bui-warning-fg-subdued` | +| `--bui-border-warning` | `--bui-warning-border` | + +### Announcement + +| Deprecated | Replacement | +| --------------------- | ------------------------------- | +| `--bui-bg-info` | `--bui-announcement-bg-subdued` | +| `--bui-fg-info-on-bg` | `--bui-announcement-fg-subdued` | +| `--bui-border-info` | `--bui-announcement-border` | From 1caf3573fa15725efd3768f85e6d39a85d3a9920 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Tue, 26 May 2026 21:39:34 +0200 Subject: [PATCH 07/16] docs(ui): reformat changeset to follow ui changeset conventions Replace markdown headings with bold markers and add Affected components line as required by the .changeset/README.md format guide. Signed-off-by: Charles de Dreuille --- .changeset/ui-semantic-color-tokens.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/.changeset/ui-semantic-color-tokens.md b/.changeset/ui-semantic-color-tokens.md index 7798193f7e..1fe3ad77a3 100644 --- a/.changeset/ui-semantic-color-tokens.md +++ b/.changeset/ui-semantic-color-tokens.md @@ -4,11 +4,11 @@ Introduces a new set of semantic color token families — Accent, Announcement, Warning, Negative, and Positive — each providing a consistent set of background, foreground, and border tokens for both light and dark themes. A gray scale (`--bui-gray-1` through `--bui-gray-11`) and updated foreground tokens are also included. -The previous tokens remain in place for backward compatibility but are now deprecated and will be removed in a future release. Migrate using the table below. +The previous tokens remain in place for backward compatibility but are now deprecated and will be removed in a future release. -## Migration +**Migration:** -### Surfaces +**Surfaces** | Deprecated | Replacement | | -------------------- | ----------------- | @@ -18,7 +18,7 @@ The previous tokens remain in place for backward compatibility but are now depre | `--bui-bg-neutral-3` | `--bui-surface-4` | | `--bui-bg-neutral-4` | `--bui-surface-5` | -### Foreground +**Foreground** | Deprecated | Replacement | | ------------------ | ----------------------- | @@ -26,7 +26,7 @@ The previous tokens remain in place for backward compatibility but are now depre | `--bui-fg-success` | `--bui-fg-positive` | | `--bui-fg-info` | `--bui-fg-announcement` | -### Accent +**Accent** | Deprecated | Replacement | | ------------------------- | -------------------------- | @@ -36,7 +36,7 @@ The previous tokens remain in place for backward compatibility but are now depre | `--bui-fg-solid` | `--bui-accent-fg` | | `--bui-fg-solid-disabled` | `--bui-accent-fg-disabled` | -### Positive +**Positive** | Deprecated | Replacement | | ------------------------ | --------------------------- | @@ -44,7 +44,7 @@ The previous tokens remain in place for backward compatibility but are now depre | `--bui-fg-success-on-bg` | `--bui-positive-fg-subdued` | | `--bui-border-success` | `--bui-positive-border` | -### Negative +**Negative** | Deprecated | Replacement | | ----------------------- | --------------------------- | @@ -52,7 +52,7 @@ The previous tokens remain in place for backward compatibility but are now depre | `--bui-fg-danger-on-bg` | `--bui-negative-fg-subdued` | | `--bui-border-danger` | `--bui-negative-border` | -### Warning +**Warning** | Deprecated | Replacement | | ------------------------ | -------------------------- | @@ -60,10 +60,12 @@ The previous tokens remain in place for backward compatibility but are now depre | `--bui-fg-warning-on-bg` | `--bui-warning-fg-subdued` | | `--bui-border-warning` | `--bui-warning-border` | -### Announcement +**Announcement** | Deprecated | Replacement | | --------------------- | ------------------------------- | | `--bui-bg-info` | `--bui-announcement-bg-subdued` | | `--bui-fg-info-on-bg` | `--bui-announcement-fg-subdued` | | `--bui-border-info` | `--bui-announcement-border` | + +**Affected components:** Colors From 53388ccb5eaa6630b954f49fe1f0d27ba3dc79b0 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Tue, 26 May 2026 21:43:50 +0200 Subject: [PATCH 08/16] docs(eslint-plugin): fix migration table in no-deprecated-bui-tokens docs Group migrations by family (Surfaces, Foreground, Accent, Positive, Negative, Warning, Announcement) and correct the surface token mappings to match the full --bui-surface-1 through --bui-surface-5 scale. Signed-off-by: Charles de Dreuille --- .../docs/rules/no-deprecated-bui-tokens.md | 85 +++++++++++++------ 1 file changed, 60 insertions(+), 25 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md b/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md index 5159ab3a5c..39713b4c70 100644 --- a/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md +++ b/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md @@ -36,29 +36,64 @@ const style = { borderColor: 'var(--bui-positive-border)' }; ## Migration Guide -Replace deprecated tokens with their equivalents from the new semantic families: +Replace deprecated tokens with their equivalents from the new semantic families. -| Deprecated token | Replacement | -| ------------------------- | ------------------------------- | -| `--bui-bg-solid` | `--bui-accent-bg` | -| `--bui-bg-solid-hover` | `--bui-accent-bg-hover` | -| `--bui-bg-solid-disabled` | `--bui-accent-bg-disabled` | -| `--bui-fg-solid` | `--bui-accent-fg` | -| `--bui-fg-solid-disabled` | `--bui-accent-fg-disabled` | -| `--bui-bg-danger` | `--bui-negative-bg-subdued` | -| `--bui-bg-warning` | `--bui-warning-bg-subdued` | -| `--bui-bg-success` | `--bui-positive-bg-subdued` | -| `--bui-bg-info` | `--bui-announcement-bg-subdued` | -| `--bui-fg-danger` | `--bui-negative-fg-subdued` | -| `--bui-fg-success` | `--bui-positive-fg-subdued` | -| `--bui-fg-info` | `--bui-announcement-fg-subdued` | -| `--bui-fg-danger-on-bg` | `--bui-negative-fg-subdued` | -| `--bui-fg-warning-on-bg` | `--bui-warning-fg-subdued` | -| `--bui-fg-success-on-bg` | `--bui-positive-fg-subdued` | -| `--bui-fg-info-on-bg` | `--bui-announcement-fg-subdued` | -| `--bui-border-danger` | `--bui-negative-border` | -| `--bui-border-warning` | `--bui-warning-border` | -| `--bui-border-success` | `--bui-positive-border` | -| `--bui-border-info` | `--bui-announcement-border` | -| `--bui-bg-neutral-1` | `--bui-surface-1` | -| `--bui-bg-app` | `--bui-surface-1` | +**Surfaces** + +| Deprecated | Replacement | +| -------------------- | ----------------- | +| `--bui-bg-app` | `--bui-surface-1` | +| `--bui-bg-neutral-1` | `--bui-surface-2` | +| `--bui-bg-neutral-2` | `--bui-surface-3` | +| `--bui-bg-neutral-3` | `--bui-surface-4` | +| `--bui-bg-neutral-4` | `--bui-surface-5` | + +**Foreground** + +| Deprecated | Replacement | +| ------------------ | ----------------------- | +| `--bui-fg-danger` | `--bui-fg-negative` | +| `--bui-fg-success` | `--bui-fg-positive` | +| `--bui-fg-info` | `--bui-fg-announcement` | + +**Accent** + +| Deprecated | Replacement | +| ------------------------- | -------------------------- | +| `--bui-bg-solid` | `--bui-accent-bg` | +| `--bui-bg-solid-hover` | `--bui-accent-bg-hover` | +| `--bui-bg-solid-disabled` | `--bui-accent-bg-disabled` | +| `--bui-fg-solid` | `--bui-accent-fg` | +| `--bui-fg-solid-disabled` | `--bui-accent-fg-disabled` | + +**Positive** + +| Deprecated | Replacement | +| ------------------------ | --------------------------- | +| `--bui-bg-success` | `--bui-positive-bg-subdued` | +| `--bui-fg-success-on-bg` | `--bui-positive-fg-subdued` | +| `--bui-border-success` | `--bui-positive-border` | + +**Negative** + +| Deprecated | Replacement | +| ----------------------- | --------------------------- | +| `--bui-bg-danger` | `--bui-negative-bg-subdued` | +| `--bui-fg-danger-on-bg` | `--bui-negative-fg-subdued` | +| `--bui-border-danger` | `--bui-negative-border` | + +**Warning** + +| Deprecated | Replacement | +| ------------------------ | -------------------------- | +| `--bui-bg-warning` | `--bui-warning-bg-subdued` | +| `--bui-fg-warning-on-bg` | `--bui-warning-fg-subdued` | +| `--bui-border-warning` | `--bui-warning-border` | + +**Announcement** + +| Deprecated | Replacement | +| --------------------- | ------------------------------- | +| `--bui-bg-info` | `--bui-announcement-bg-subdued` | +| `--bui-fg-info-on-bg` | `--bui-announcement-fg-subdued` | +| `--bui-border-info` | `--bui-announcement-border` | From 27891fda919ae4e8c71edb26d9e6d42f3175c130 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Tue, 26 May 2026 21:45:02 +0200 Subject: [PATCH 09/16] docs(eslint-plugin): use ### headings for migration sections in no-deprecated-bui-tokens docs Signed-off-by: Charles de Dreuille --- .../docs/rules/no-deprecated-bui-tokens.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md b/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md index 39713b4c70..5d676344d6 100644 --- a/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md +++ b/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md @@ -38,7 +38,7 @@ const style = { borderColor: 'var(--bui-positive-border)' }; Replace deprecated tokens with their equivalents from the new semantic families. -**Surfaces** +### Surfaces | Deprecated | Replacement | | -------------------- | ----------------- | @@ -48,7 +48,7 @@ Replace deprecated tokens with their equivalents from the new semantic families. | `--bui-bg-neutral-3` | `--bui-surface-4` | | `--bui-bg-neutral-4` | `--bui-surface-5` | -**Foreground** +### Foreground | Deprecated | Replacement | | ------------------ | ----------------------- | @@ -56,7 +56,7 @@ Replace deprecated tokens with their equivalents from the new semantic families. | `--bui-fg-success` | `--bui-fg-positive` | | `--bui-fg-info` | `--bui-fg-announcement` | -**Accent** +### Accent | Deprecated | Replacement | | ------------------------- | -------------------------- | @@ -66,7 +66,7 @@ Replace deprecated tokens with their equivalents from the new semantic families. | `--bui-fg-solid` | `--bui-accent-fg` | | `--bui-fg-solid-disabled` | `--bui-accent-fg-disabled` | -**Positive** +### Positive | Deprecated | Replacement | | ------------------------ | --------------------------- | @@ -74,7 +74,7 @@ Replace deprecated tokens with their equivalents from the new semantic families. | `--bui-fg-success-on-bg` | `--bui-positive-fg-subdued` | | `--bui-border-success` | `--bui-positive-border` | -**Negative** +### Negative | Deprecated | Replacement | | ----------------------- | --------------------------- | @@ -82,7 +82,7 @@ Replace deprecated tokens with their equivalents from the new semantic families. | `--bui-fg-danger-on-bg` | `--bui-negative-fg-subdued` | | `--bui-border-danger` | `--bui-negative-border` | -**Warning** +### Warning | Deprecated | Replacement | | ------------------------ | -------------------------- | @@ -90,7 +90,7 @@ Replace deprecated tokens with their equivalents from the new semantic families. | `--bui-fg-warning-on-bg` | `--bui-warning-fg-subdued` | | `--bui-border-warning` | `--bui-warning-border` | -**Announcement** +### Announcement | Deprecated | Replacement | | --------------------- | ------------------------------- | From 9c3b5ef2bd713bf85b61c0b616e3d4f178c22099 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Tue, 26 May 2026 21:45:34 +0200 Subject: [PATCH 10/16] Update no-deprecated-bui-tokens.js Signed-off-by: Charles de Dreuille --- packages/eslint-plugin/rules/no-deprecated-bui-tokens.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/rules/no-deprecated-bui-tokens.js b/packages/eslint-plugin/rules/no-deprecated-bui-tokens.js index 831688efd8..597909eec1 100644 --- a/packages/eslint-plugin/rules/no-deprecated-bui-tokens.js +++ b/packages/eslint-plugin/rules/no-deprecated-bui-tokens.js @@ -83,7 +83,7 @@ module.exports = { }, messages: { deprecated: - "'{{token}}' is a deprecated BUI token. Use the new semantic token families instead: --bui-negative-*, --bui-positive-*, --bui-warning-*, --bui-announcement-*, or --bui-accent-*.", + "'{{token}}' is a deprecated BUI token. Replace it with the appropriate current BUI token for its usage (for example, a semantic intent, surface/background, or shadow token); see this rule's migration guide for the correct mapping.", }, schema: [], }, From c2966d6a843758b5a5f40a0e3b19f20ffe9e23f9 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Tue, 26 May 2026 22:06:28 +0200 Subject: [PATCH 11/16] Update tokens.css Signed-off-by: Charles de Dreuille --- packages/ui/src/css/tokens.css | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/ui/src/css/tokens.css b/packages/ui/src/css/tokens.css index d50822613b..c149474d27 100644 --- a/packages/ui/src/css/tokens.css +++ b/packages/ui/src/css/tokens.css @@ -126,9 +126,9 @@ --bui-announcement-bg-subdued-disabled: #c8e0fc; --bui-announcement-border: #549ef4; --bui-announcement-fg: #ffffff; - --bui-announcement-fg-disabled: #ffffff; + --bui-announcement-fg-disabled: #a4ccfb; --bui-announcement-fg-subdued: #173da6; - --bui-announcement-fg-subdued-disabled: #173da6; + --bui-announcement-fg-subdued-disabled: #7391e3; /* Warning */ --bui-warning-bg: #ff9400; @@ -139,9 +139,9 @@ --bui-warning-bg-subdued-disabled: #f8ddb9; --bui-warning-border: #ff9400; --bui-warning-fg: #000000; - --bui-warning-fg-disabled: #000000; + --bui-warning-fg-disabled: #aa4d00; --bui-warning-fg-subdued: #aa4d00; - --bui-warning-fg-subdued-disabled: #aa4d00; + --bui-warning-fg-subdued-disabled: #e3a572; /* Negative */ --bui-negative-bg: #dc2626; @@ -152,7 +152,7 @@ --bui-negative-bg-subdued-disabled: #f7b6b6; --bui-negative-border: #d06565; --bui-negative-fg: #ffffff; - --bui-negative-fg-disabled: #ffffff; + --bui-negative-fg-disabled: #f7b6b6; --bui-negative-fg-subdued: #991919; --bui-negative-fg-subdued-disabled: #bb7272; @@ -165,9 +165,9 @@ --bui-positive-bg-subdued-disabled: #96f0b6; --bui-positive-border: #54a671; --bui-positive-fg: #000000; - --bui-positive-fg-disabled: #ffffff; + --bui-positive-fg-disabled: #0c632b; --bui-positive-fg-subdued: #0c632b; - --bui-positive-fg-subdued-disabled: #0c632b; + --bui-positive-fg-subdued-disabled: #4b9666; /* Deprecated tokens */ --bui-bg-solid: #1f5493; @@ -255,9 +255,9 @@ --bui-announcement-bg-subdued-disabled: #052a56; --bui-announcement-border: #2363af; --bui-announcement-fg: #ffffff; - --bui-announcement-fg-disabled: #ffffff; + --bui-announcement-fg-disabled: #70b8ff; --bui-announcement-fg-subdued: #70b8ff; - --bui-announcement-fg-subdued-disabled: #70b8ff; + --bui-announcement-fg-subdued-disabled: #4275a6; /* Warning */ --bui-warning-bg: #ffa42c; @@ -268,9 +268,9 @@ --bui-warning-bg-subdued-disabled: #582400; --bui-warning-border: #7c4903; --bui-warning-fg: #000000; - --bui-warning-fg-disabled: #000000; + --bui-warning-fg-disabled: #aa4d00; --bui-warning-fg-subdued: #f2ab4a; - --bui-warning-fg-subdued-disabled: #f2ab4a; + --bui-warning-fg-subdued-disabled: #aa4d00; /* Negative */ --bui-negative-bg: #dc2626; @@ -281,9 +281,9 @@ --bui-negative-bg-subdued-disabled: #5e1a1b; --bui-negative-border: #6f101c; --bui-negative-fg: #ffffff; - --bui-negative-fg-disabled: #ffffff; + --bui-negative-fg-disabled: #f7b6b6; --bui-negative-fg-subdued: #fca5a5; - --bui-negative-fg-subdued-disabled: #ffffff; + --bui-negative-fg-subdued-disabled: #bb7272; /* Positive */ --bui-positive-bg: #1ed760; @@ -294,9 +294,9 @@ --bui-positive-bg-subdued-disabled: #073116; --bui-positive-border: #136d33; --bui-positive-fg: #000000; - --bui-positive-fg-disabled: #ffffff; + --bui-positive-fg-disabled: #0c632b; --bui-positive-fg-subdued: #8ddeaa; - --bui-positive-fg-subdued-disabled: #8ddeaa; + --bui-positive-fg-subdued-disabled: #509c6b; /* Deprecated tokens */ --bui-bg-solid: #9cc9ff; From 657aad6cf032cbc26849f25cd8a906247cef0fde Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Wed, 27 May 2026 13:11:38 +0200 Subject: [PATCH 12/16] refactor(ui): rename surface tokens to neutral background tokens Replace --bui-surface-1..5 with --bui-bg-neutral-1..5 for both light and dark themes. --bui-bg-neutral-1 replaces the deprecated --bui-bg-app, and --bui-bg-neutral-2..5 extend the scale. The old bare --bui-bg-neutral-1..4 entries are removed from the deprecated section since their names are now reused; the hover/pressed/disabled variants remain deprecated. Updates colors.stories.tsx, the ESLint rule and tests, and all migration documentation and the PR description accordingly. Signed-off-by: Charles de Dreuille --- .changeset/ui-semantic-color-tokens.md | 12 +++---- .../docs/rules/no-deprecated-bui-tokens.md | 12 +++---- .../rules/no-deprecated-bui-tokens.js | 4 --- .../src/no-deprecated-bui-tokens.test.ts | 12 +++---- packages/ui/src/css/colors.stories.tsx | 12 +++---- packages/ui/src/css/tokens.css | 32 +++++++------------ 6 files changed, 31 insertions(+), 53 deletions(-) diff --git a/.changeset/ui-semantic-color-tokens.md b/.changeset/ui-semantic-color-tokens.md index 1fe3ad77a3..41dc2de160 100644 --- a/.changeset/ui-semantic-color-tokens.md +++ b/.changeset/ui-semantic-color-tokens.md @@ -8,15 +8,11 @@ The previous tokens remain in place for backward compatibility but are now depre **Migration:** -**Surfaces** +**Neutral backgrounds** -| Deprecated | Replacement | -| -------------------- | ----------------- | -| `--bui-bg-app` | `--bui-surface-1` | -| `--bui-bg-neutral-1` | `--bui-surface-2` | -| `--bui-bg-neutral-2` | `--bui-surface-3` | -| `--bui-bg-neutral-3` | `--bui-surface-4` | -| `--bui-bg-neutral-4` | `--bui-surface-5` | +| Deprecated | Replacement | +| -------------- | -------------------- | +| `--bui-bg-app` | `--bui-bg-neutral-1` | **Foreground** diff --git a/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md b/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md index 5d676344d6..c31617c333 100644 --- a/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md +++ b/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md @@ -38,15 +38,11 @@ const style = { borderColor: 'var(--bui-positive-border)' }; Replace deprecated tokens with their equivalents from the new semantic families. -### Surfaces +### Neutral backgrounds -| Deprecated | Replacement | -| -------------------- | ----------------- | -| `--bui-bg-app` | `--bui-surface-1` | -| `--bui-bg-neutral-1` | `--bui-surface-2` | -| `--bui-bg-neutral-2` | `--bui-surface-3` | -| `--bui-bg-neutral-3` | `--bui-surface-4` | -| `--bui-bg-neutral-4` | `--bui-surface-5` | +| Deprecated | Replacement | +| -------------- | -------------------- | +| `--bui-bg-app` | `--bui-bg-neutral-1` | ### Foreground diff --git a/packages/eslint-plugin/rules/no-deprecated-bui-tokens.js b/packages/eslint-plugin/rules/no-deprecated-bui-tokens.js index 597909eec1..eb38707c5c 100644 --- a/packages/eslint-plugin/rules/no-deprecated-bui-tokens.js +++ b/packages/eslint-plugin/rules/no-deprecated-bui-tokens.js @@ -23,19 +23,15 @@ const DEPRECATED_TOKENS = [ '--bui-bg-solid-pressed', '--bui-bg-solid-disabled', '--bui-bg-app', - '--bui-bg-neutral-1', '--bui-bg-neutral-1-hover', '--bui-bg-neutral-1-pressed', '--bui-bg-neutral-1-disabled', - '--bui-bg-neutral-2', '--bui-bg-neutral-2-hover', '--bui-bg-neutral-2-pressed', '--bui-bg-neutral-2-disabled', - '--bui-bg-neutral-3', '--bui-bg-neutral-3-hover', '--bui-bg-neutral-3-pressed', '--bui-bg-neutral-3-disabled', - '--bui-bg-neutral-4', '--bui-bg-neutral-4-hover', '--bui-bg-neutral-4-pressed', '--bui-bg-neutral-4-disabled', diff --git a/packages/eslint-plugin/src/no-deprecated-bui-tokens.test.ts b/packages/eslint-plugin/src/no-deprecated-bui-tokens.test.ts index 62a8dfd7e9..5e7dbe57ea 100644 --- a/packages/eslint-plugin/src/no-deprecated-bui-tokens.test.ts +++ b/packages/eslint-plugin/src/no-deprecated-bui-tokens.test.ts @@ -46,7 +46,11 @@ ruleTester.run('no-deprecated-bui-tokens', rule, { { code: `const s = 'var(--bui-warning-bg)'` }, { code: `const s = 'var(--bui-announcement-bg)'` }, { code: `const s = 'var(--bui-gray-1)'` }, - { code: `const s = 'var(--bui-surface-1)'` }, + { code: `const s = 'var(--bui-bg-neutral-1)'` }, + { code: `const s = 'var(--bui-bg-neutral-2)'` }, + { code: `const s = 'var(--bui-bg-neutral-3)'` }, + { code: `const s = 'var(--bui-bg-neutral-4)'` }, + { code: `const s = 'var(--bui-bg-neutral-5)'` }, // Unrelated strings — should not warn { code: `const s = 'some-other-string'` }, { code: `const n = 42` }, @@ -83,12 +87,6 @@ ruleTester.run('no-deprecated-bui-tokens', rule, { code: `const s = 'var(--bui-bg-app)'`, errors: [{ messageId: 'deprecated', data: { token: '--bui-bg-app' } }], }, - { - code: `const s = 'var(--bui-bg-neutral-1)'`, - errors: [ - { messageId: 'deprecated', data: { token: '--bui-bg-neutral-1' } }, - ], - }, { code: `const s = 'var(--bui-bg-neutral-2-hover)'`, errors: [ diff --git a/packages/ui/src/css/colors.stories.tsx b/packages/ui/src/css/colors.stories.tsx index 83425cc85a..7f46d922a6 100644 --- a/packages/ui/src/css/colors.stories.tsx +++ b/packages/ui/src/css/colors.stories.tsx @@ -122,13 +122,13 @@ export const All = meta.story({ />
diff --git a/packages/ui/src/css/tokens.css b/packages/ui/src/css/tokens.css index c149474d27..d149523ecd 100644 --- a/packages/ui/src/css/tokens.css +++ b/packages/ui/src/css/tokens.css @@ -90,12 +90,12 @@ --bui-gray-10: #171717; --bui-gray-11: #0a0a0a; - /* Surface colors */ - --bui-surface-1: #f5f5f5; - --bui-surface-2: var(--bui-white); - --bui-surface-3: #f7f7f7; - --bui-surface-4: var(--bui-white); - --bui-surface-5: #f7f7f7; + /* Neutral background colors */ + --bui-bg-neutral-1: #f5f5f5; + --bui-bg-neutral-2: var(--bui-white); + --bui-bg-neutral-3: #f7f7f7; + --bui-bg-neutral-4: var(--bui-white); + --bui-bg-neutral-5: #f7f7f7; /* Foreground colors */ --bui-fg-primary: var(--bui-black); @@ -175,19 +175,15 @@ --bui-bg-solid-pressed: #0f2b4e; --bui-bg-solid-disabled: #163a66; --bui-bg-app: #f8f8f8; - --bui-bg-neutral-1: #fff; --bui-bg-neutral-1-hover: oklch(0% 0 0 / 6%); --bui-bg-neutral-1-pressed: oklch(0% 0 0 / 12%); --bui-bg-neutral-1-disabled: oklch(0% 0 0 / 6%); - --bui-bg-neutral-2: oklch(0% 0 0 / 6%); --bui-bg-neutral-2-hover: oklch(0% 0 0 / 12%); --bui-bg-neutral-2-pressed: oklch(0% 0 0 / 16%); --bui-bg-neutral-2-disabled: oklch(0% 0 0 / 6%); - --bui-bg-neutral-3: oklch(0% 0 0 / 6%); --bui-bg-neutral-3-hover: oklch(0% 0 0 / 12%); --bui-bg-neutral-3-pressed: oklch(0% 0 0 / 16%); --bui-bg-neutral-3-disabled: oklch(0% 0 0 / 6%); - --bui-bg-neutral-4: oklch(0% 0 0 / 6%); --bui-bg-neutral-4-hover: oklch(0% 0 0 / 12%); --bui-bg-neutral-4-pressed: oklch(0% 0 0 / 16%); --bui-bg-neutral-4-disabled: oklch(0% 0 0 / 6%); @@ -219,12 +215,12 @@ --bui-scrollbar: #3636363a; --bui-scrollbar-thumb: #575757; - /* Surface colors */ - --bui-surface-1: #333333; - --bui-surface-2: #1a1a1a; - --bui-surface-3: #282828; - --bui-surface-4: #393939; - --bui-surface-5: #474747; + /* Neutral background colors */ + --bui-bg-neutral-1: #333333; + --bui-bg-neutral-2: #1a1a1a; + --bui-bg-neutral-3: #282828; + --bui-bg-neutral-4: #393939; + --bui-bg-neutral-5: #474747; /* Foreground colors */ --bui-fg-primary: var(--bui-white); @@ -304,19 +300,15 @@ --bui-bg-solid-pressed: #83b9fd; --bui-bg-solid-disabled: #1b3d68; --bui-bg-app: #333333; - --bui-bg-neutral-1: oklch(100% 0 0 / 10%); --bui-bg-neutral-1-hover: oklch(100% 0 0 / 14%); --bui-bg-neutral-1-pressed: oklch(100% 0 0 / 20%); --bui-bg-neutral-1-disabled: oklch(100% 0 0 / 10%); - --bui-bg-neutral-2: oklch(100% 0 0 / 6%); --bui-bg-neutral-2-hover: oklch(100% 0 0 / 10%); --bui-bg-neutral-2-pressed: oklch(100% 0 0 / 16%); --bui-bg-neutral-2-disabled: oklch(100% 0 0 / 6%); - --bui-bg-neutral-3: oklch(100% 0 0 / 8%); --bui-bg-neutral-3-hover: oklch(100% 0 0 / 12%); --bui-bg-neutral-3-pressed: oklch(100% 0 0 / 20%); --bui-bg-neutral-3-disabled: oklch(100% 0 0 / 8%); - --bui-bg-neutral-4: oklch(100% 0 0 / 8%); --bui-bg-neutral-4-hover: oklch(100% 0 0 / 12%); --bui-bg-neutral-4-pressed: oklch(100% 0 0 / 20%); --bui-bg-neutral-4-disabled: oklch(100% 0 0 / 8%); From 9f1dd4ae9b11a1b1c1fb931aa8c58a35e1003545 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Wed, 27 May 2026 13:14:30 +0200 Subject: [PATCH 13/16] docs(ui): expand neutral background migration notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add full shift mapping (old --bui-bg-neutral-1..4 → new --bui-bg-neutral-2..5) to the changeset, ESLint rule docs, and PR description so adopters understand the complete renaming of the neutral background scale. Signed-off-by: Charles de Dreuille --- .changeset/ui-semantic-color-tokens.md | 12 +++++++++--- .../docs/rules/no-deprecated-bui-tokens.md | 14 +++++++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/.changeset/ui-semantic-color-tokens.md b/.changeset/ui-semantic-color-tokens.md index 41dc2de160..c7ebad471a 100644 --- a/.changeset/ui-semantic-color-tokens.md +++ b/.changeset/ui-semantic-color-tokens.md @@ -10,9 +10,15 @@ The previous tokens remain in place for backward compatibility but are now depre **Neutral backgrounds** -| Deprecated | Replacement | -| -------------- | -------------------- | -| `--bui-bg-app` | `--bui-bg-neutral-1` | +The neutral background scale has been renamed and extended. `--bui-bg-app` is replaced by the new `--bui-bg-neutral-1`, and the old overlay-based `--bui-bg-neutral-1..4` shift up by one to become `--bui-bg-neutral-2..5`: + +| Deprecated | Replacement | +| ---------------------------------- | -------------------- | +| `--bui-bg-app` | `--bui-bg-neutral-1` | +| `--bui-bg-neutral-1` (old overlay) | `--bui-bg-neutral-2` | +| `--bui-bg-neutral-2` (old overlay) | `--bui-bg-neutral-3` | +| `--bui-bg-neutral-3` (old overlay) | `--bui-bg-neutral-4` | +| `--bui-bg-neutral-4` (old overlay) | `--bui-bg-neutral-5` | **Foreground** diff --git a/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md b/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md index c31617c333..ab2b3e9578 100644 --- a/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md +++ b/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md @@ -40,9 +40,17 @@ Replace deprecated tokens with their equivalents from the new semantic families. ### Neutral backgrounds -| Deprecated | Replacement | -| -------------- | -------------------- | -| `--bui-bg-app` | `--bui-bg-neutral-1` | +The neutral background scale has been renamed and extended. `--bui-bg-app` is replaced by the new `--bui-bg-neutral-1`, and the old overlay-based `--bui-bg-neutral-1..4` shift up by one to become `--bui-bg-neutral-2..5`: + +| Deprecated | Replacement | +| ---------------------------------- | -------------------- | +| `--bui-bg-app` | `--bui-bg-neutral-1` | +| `--bui-bg-neutral-1` (old overlay) | `--bui-bg-neutral-2` | +| `--bui-bg-neutral-2` (old overlay) | `--bui-bg-neutral-3` | +| `--bui-bg-neutral-3` (old overlay) | `--bui-bg-neutral-4` | +| `--bui-bg-neutral-4` (old overlay) | `--bui-bg-neutral-5` | + +> Note: the old `--bui-bg-neutral-1..4` names are now reused for the new solid-color tokens, so this rule does not warn on them directly. The `-hover`, `-pressed`, and `-disabled` variants of those tokens remain deprecated. ### Foreground From 64c8108166689f0300b629b8a3d28fa9d8351771 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Fri, 29 May 2026 13:35:11 +0200 Subject: [PATCH 14/16] Move to minor Signed-off-by: Charles de Dreuille --- .changeset/eslint-plugin-deprecated-bui-tokens-rule.md | 2 +- .changeset/ui-semantic-color-tokens.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/eslint-plugin-deprecated-bui-tokens-rule.md b/.changeset/eslint-plugin-deprecated-bui-tokens-rule.md index a49e90b2ec..5e1dd6de6f 100644 --- a/.changeset/eslint-plugin-deprecated-bui-tokens-rule.md +++ b/.changeset/eslint-plugin-deprecated-bui-tokens-rule.md @@ -2,4 +2,4 @@ '@backstage/eslint-plugin': patch --- -Adds a new `@backstage/no-deprecated-bui-tokens` lint rule that warns when a deprecated `@backstage/ui` CSS token is referenced in a JavaScript or TypeScript file. The rule is included in the `recommended` config, so plugin authors using `plugin:@backstage/recommended` will receive warnings automatically when using tokens that have been superseded by the new semantic color families. +Adds a new `@backstage/no-deprecated-bui-tokens` lint rule that warns when a deprecated `@backstage/ui` CSS token is referenced in a JavaScript or TypeScript file (including CSS-in-JS patterns and template literals). The rule is included in the `recommended` config, so plugin authors using `plugin:@backstage/recommended` will receive warnings automatically when using tokens that have been superseded by the new semantic color families. Note that plain CSS and CSS module files are outside ESLint's scope and are not covered by this rule. diff --git a/.changeset/ui-semantic-color-tokens.md b/.changeset/ui-semantic-color-tokens.md index c7ebad471a..44426e2b33 100644 --- a/.changeset/ui-semantic-color-tokens.md +++ b/.changeset/ui-semantic-color-tokens.md @@ -1,8 +1,8 @@ --- -'@backstage/ui': patch +'@backstage/ui': minor --- -Introduces a new set of semantic color token families — Accent, Announcement, Warning, Negative, and Positive — each providing a consistent set of background, foreground, and border tokens for both light and dark themes. A gray scale (`--bui-gray-1` through `--bui-gray-11`) and updated foreground tokens are also included. +**BREAKING**: Introduces a new set of semantic color token families — Accent, Announcement, Warning, Negative, and Positive — each providing a consistent set of background, foreground, and border tokens for both light and dark themes. A gray scale (`--bui-gray-1` through `--bui-gray-11`) and updated foreground tokens are also included. The previous tokens remain in place for backward compatibility but are now deprecated and will be removed in a future release. From a4f2c5611053359134f0630976509a6647e29470 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Fri, 29 May 2026 15:59:39 +0200 Subject: [PATCH 15/16] Bring back correct neutral tokens Signed-off-by: Charles de Dreuille --- .changeset/ui-semantic-color-tokens.md | 14 +++-------- .../docs/rules/no-deprecated-bui-tokens.md | 25 +++++++++++-------- .../rules/no-deprecated-bui-tokens.js | 1 - .../src/no-deprecated-bui-tokens.test.ts | 6 +---- packages/ui/src/css/colors.stories.tsx | 2 +- packages/ui/src/css/tokens.css | 22 ++++++++-------- 6 files changed, 30 insertions(+), 40 deletions(-) diff --git a/.changeset/ui-semantic-color-tokens.md b/.changeset/ui-semantic-color-tokens.md index 44426e2b33..3db985db55 100644 --- a/.changeset/ui-semantic-color-tokens.md +++ b/.changeset/ui-semantic-color-tokens.md @@ -1,8 +1,8 @@ --- -'@backstage/ui': minor +'@backstage/ui': patch --- -**BREAKING**: Introduces a new set of semantic color token families — Accent, Announcement, Warning, Negative, and Positive — each providing a consistent set of background, foreground, and border tokens for both light and dark themes. A gray scale (`--bui-gray-1` through `--bui-gray-11`) and updated foreground tokens are also included. +Introduces a new set of semantic color token families — Accent, Announcement, Warning, Negative, and Positive — each providing a consistent set of background, foreground, and border tokens for both light and dark themes. A gray scale (`--bui-gray-1` through `--bui-gray-11`) and updated foreground tokens are also included. The previous tokens remain in place for backward compatibility but are now deprecated and will be removed in a future release. @@ -10,15 +10,7 @@ The previous tokens remain in place for backward compatibility but are now depre **Neutral backgrounds** -The neutral background scale has been renamed and extended. `--bui-bg-app` is replaced by the new `--bui-bg-neutral-1`, and the old overlay-based `--bui-bg-neutral-1..4` shift up by one to become `--bui-bg-neutral-2..5`: - -| Deprecated | Replacement | -| ---------------------------------- | -------------------- | -| `--bui-bg-app` | `--bui-bg-neutral-1` | -| `--bui-bg-neutral-1` (old overlay) | `--bui-bg-neutral-2` | -| `--bui-bg-neutral-2` (old overlay) | `--bui-bg-neutral-3` | -| `--bui-bg-neutral-3` (old overlay) | `--bui-bg-neutral-4` | -| `--bui-bg-neutral-4` (old overlay) | `--bui-bg-neutral-5` | +The neutral background tokens keep their existing names (`--bui-bg-app`, `--bui-bg-neutral-1` through `--bui-bg-neutral-4`) but are updated with new solid-color values for both light and dark themes. No token renaming is required. The `-hover`, `-pressed`, and `-disabled` interaction variants of these tokens are deprecated and should be removed. **Foreground** diff --git a/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md b/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md index ab2b3e9578..8f211e39c6 100644 --- a/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md +++ b/packages/eslint-plugin/docs/rules/no-deprecated-bui-tokens.md @@ -40,17 +40,22 @@ Replace deprecated tokens with their equivalents from the new semantic families. ### Neutral backgrounds -The neutral background scale has been renamed and extended. `--bui-bg-app` is replaced by the new `--bui-bg-neutral-1`, and the old overlay-based `--bui-bg-neutral-1..4` shift up by one to become `--bui-bg-neutral-2..5`: +The neutral background tokens (`--bui-bg-app`, `--bui-bg-neutral-1..4`) are now the active semantic tokens with updated solid-color values. The `-hover`, `-pressed`, and `-disabled` interaction variants remain deprecated: -| Deprecated | Replacement | -| ---------------------------------- | -------------------- | -| `--bui-bg-app` | `--bui-bg-neutral-1` | -| `--bui-bg-neutral-1` (old overlay) | `--bui-bg-neutral-2` | -| `--bui-bg-neutral-2` (old overlay) | `--bui-bg-neutral-3` | -| `--bui-bg-neutral-3` (old overlay) | `--bui-bg-neutral-4` | -| `--bui-bg-neutral-4` (old overlay) | `--bui-bg-neutral-5` | - -> Note: the old `--bui-bg-neutral-1..4` names are now reused for the new solid-color tokens, so this rule does not warn on them directly. The `-hover`, `-pressed`, and `-disabled` variants of those tokens remain deprecated. +| Deprecated | Replacement | +| ----------------------------- | --------------------- | +| `--bui-bg-neutral-1-hover` | _(remove or restyle)_ | +| `--bui-bg-neutral-1-pressed` | _(remove or restyle)_ | +| `--bui-bg-neutral-1-disabled` | _(remove or restyle)_ | +| `--bui-bg-neutral-2-hover` | _(remove or restyle)_ | +| `--bui-bg-neutral-2-pressed` | _(remove or restyle)_ | +| `--bui-bg-neutral-2-disabled` | _(remove or restyle)_ | +| `--bui-bg-neutral-3-hover` | _(remove or restyle)_ | +| `--bui-bg-neutral-3-pressed` | _(remove or restyle)_ | +| `--bui-bg-neutral-3-disabled` | _(remove or restyle)_ | +| `--bui-bg-neutral-4-hover` | _(remove or restyle)_ | +| `--bui-bg-neutral-4-pressed` | _(remove or restyle)_ | +| `--bui-bg-neutral-4-disabled` | _(remove or restyle)_ | ### Foreground diff --git a/packages/eslint-plugin/rules/no-deprecated-bui-tokens.js b/packages/eslint-plugin/rules/no-deprecated-bui-tokens.js index eb38707c5c..7b42d2419b 100644 --- a/packages/eslint-plugin/rules/no-deprecated-bui-tokens.js +++ b/packages/eslint-plugin/rules/no-deprecated-bui-tokens.js @@ -22,7 +22,6 @@ const DEPRECATED_TOKENS = [ '--bui-bg-solid-hover', '--bui-bg-solid-pressed', '--bui-bg-solid-disabled', - '--bui-bg-app', '--bui-bg-neutral-1-hover', '--bui-bg-neutral-1-pressed', '--bui-bg-neutral-1-disabled', diff --git a/packages/eslint-plugin/src/no-deprecated-bui-tokens.test.ts b/packages/eslint-plugin/src/no-deprecated-bui-tokens.test.ts index 5e7dbe57ea..0cc305b723 100644 --- a/packages/eslint-plugin/src/no-deprecated-bui-tokens.test.ts +++ b/packages/eslint-plugin/src/no-deprecated-bui-tokens.test.ts @@ -46,11 +46,11 @@ ruleTester.run('no-deprecated-bui-tokens', rule, { { code: `const s = 'var(--bui-warning-bg)'` }, { code: `const s = 'var(--bui-announcement-bg)'` }, { code: `const s = 'var(--bui-gray-1)'` }, + { code: `const s = 'var(--bui-bg-app)'` }, { code: `const s = 'var(--bui-bg-neutral-1)'` }, { code: `const s = 'var(--bui-bg-neutral-2)'` }, { code: `const s = 'var(--bui-bg-neutral-3)'` }, { code: `const s = 'var(--bui-bg-neutral-4)'` }, - { code: `const s = 'var(--bui-bg-neutral-5)'` }, // Unrelated strings — should not warn { code: `const s = 'some-other-string'` }, { code: `const n = 42` }, @@ -83,10 +83,6 @@ ruleTester.run('no-deprecated-bui-tokens', rule, { }, ], }, - { - code: `const s = 'var(--bui-bg-app)'`, - errors: [{ messageId: 'deprecated', data: { token: '--bui-bg-app' } }], - }, { code: `const s = 'var(--bui-bg-neutral-2-hover)'`, errors: [ diff --git a/packages/ui/src/css/colors.stories.tsx b/packages/ui/src/css/colors.stories.tsx index 7f46d922a6..44a1e4342b 100644 --- a/packages/ui/src/css/colors.stories.tsx +++ b/packages/ui/src/css/colors.stories.tsx @@ -124,11 +124,11 @@ export const All = meta.story({
diff --git a/packages/ui/src/css/tokens.css b/packages/ui/src/css/tokens.css index d149523ecd..cd333428ee 100644 --- a/packages/ui/src/css/tokens.css +++ b/packages/ui/src/css/tokens.css @@ -91,11 +91,11 @@ --bui-gray-11: #0a0a0a; /* Neutral background colors */ - --bui-bg-neutral-1: #f5f5f5; - --bui-bg-neutral-2: var(--bui-white); - --bui-bg-neutral-3: #f7f7f7; - --bui-bg-neutral-4: var(--bui-white); - --bui-bg-neutral-5: #f7f7f7; + --bui-bg-app: #f5f5f5; + --bui-bg-neutral-1: var(--bui-white); + --bui-bg-neutral-2: #f7f7f7; + --bui-bg-neutral-3: var(--bui-white); + --bui-bg-neutral-4: #f7f7f7; /* Foreground colors */ --bui-fg-primary: var(--bui-black); @@ -174,7 +174,6 @@ --bui-bg-solid-hover: #163a66; --bui-bg-solid-pressed: #0f2b4e; --bui-bg-solid-disabled: #163a66; - --bui-bg-app: #f8f8f8; --bui-bg-neutral-1-hover: oklch(0% 0 0 / 6%); --bui-bg-neutral-1-pressed: oklch(0% 0 0 / 12%); --bui-bg-neutral-1-disabled: oklch(0% 0 0 / 6%); @@ -216,11 +215,11 @@ --bui-scrollbar-thumb: #575757; /* Neutral background colors */ - --bui-bg-neutral-1: #333333; - --bui-bg-neutral-2: #1a1a1a; - --bui-bg-neutral-3: #282828; - --bui-bg-neutral-4: #393939; - --bui-bg-neutral-5: #474747; + --bui-bg-app: #333333; + --bui-bg-neutral-1: #1a1a1a; + --bui-bg-neutral-2: #282828; + --bui-bg-neutral-3: #393939; + --bui-bg-neutral-4: #474747; /* Foreground colors */ --bui-fg-primary: var(--bui-white); @@ -299,7 +298,6 @@ --bui-bg-solid-hover: #83b9fd; --bui-bg-solid-pressed: #83b9fd; --bui-bg-solid-disabled: #1b3d68; - --bui-bg-app: #333333; --bui-bg-neutral-1-hover: oklch(100% 0 0 / 14%); --bui-bg-neutral-1-pressed: oklch(100% 0 0 / 20%); --bui-bg-neutral-1-disabled: oklch(100% 0 0 / 10%); From 78f6fd3b5bfcd1152f338e02f11503f927776f30 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Fri, 29 May 2026 16:07:56 +0200 Subject: [PATCH 16/16] Small fixes Signed-off-by: Charles de Dreuille --- .changeset/ui-semantic-color-tokens.md | 2 -- .storybook/themes/spotify.css | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.changeset/ui-semantic-color-tokens.md b/.changeset/ui-semantic-color-tokens.md index 3db985db55..6ed0ac9c39 100644 --- a/.changeset/ui-semantic-color-tokens.md +++ b/.changeset/ui-semantic-color-tokens.md @@ -61,5 +61,3 @@ The neutral background tokens keep their existing names (`--bui-bg-app`, `--bui- | `--bui-bg-info` | `--bui-announcement-bg-subdued` | | `--bui-fg-info-on-bg` | `--bui-announcement-fg-subdued` | | `--bui-border-info` | `--bui-announcement-border` | - -**Affected components:** Colors diff --git a/.storybook/themes/spotify.css b/.storybook/themes/spotify.css index 20ecb34062..532a086e57 100644 --- a/.storybook/themes/spotify.css +++ b/.storybook/themes/spotify.css @@ -213,6 +213,7 @@ } [data-theme-mode='dark'][data-theme-name='spotify'] { + --bui-bg-app: var(--bui-black); --bui-ring: rgba(255, 255, 255, 0.2); --bui-accent-bg: #1ed760; --bui-accent-bg-hover: #3be477; @@ -221,7 +222,6 @@ --bui-accent-fg-disabled: #62ab7c; /* Deprecated tokens */ - --bui-bg-app: var(--bui-black); --bui-bg-solid: #1ed760; --bui-bg-solid-hover: #3be477; --bui-bg-solid-pressed: #1abc54;