feat(ui): introduce semantic color token families and deprecate legacy tokens

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 <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2026-05-26 15:08:53 +02:00
parent e223e89103
commit d38977c7b4
6 changed files with 603 additions and 249 deletions
+2
View File
@@ -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'),
},
};
@@ -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);
}
},
};
},
};