Merge pull request #34400 from backstage/feat/bui-token-redesign

feat(ui): introduce semantic color token families and deprecate legacy tokens
This commit is contained in:
Charles de Dreuille
2026-05-29 15:40:20 +01:00
committed by GitHub
10 changed files with 1022 additions and 258 deletions
@@ -0,0 +1,108 @@
# 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.
### Neutral backgrounds
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-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
| 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` |
+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,117 @@
/*
* 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-neutral-1-hover',
'--bui-bg-neutral-1-pressed',
'--bui-bg-neutral-1-disabled',
'--bui-bg-neutral-2-hover',
'--bui-bg-neutral-2-pressed',
'--bui-bg-neutral-2-disabled',
'--bui-bg-neutral-3-hover',
'--bui-bg-neutral-3-pressed',
'--bui-bg-neutral-3-disabled',
'--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. 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: [],
},
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);
}
},
};
},
};
@@ -0,0 +1,257 @@
/*
* 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-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)'` },
// 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-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 = <div style={{ background: 'var(--bui-bg-danger)' }} />`,
parserOptions: {
sourceType: 'module',
ecmaVersion: 2021,
ecmaFeatures: { jsx: true },
},
errors: [{ messageId: 'deprecated', data: { token: '--bui-bg-danger' } }],
},
{
code: `const el = <div style={{ color: 'var(--bui-fg-success)' }} />`,
parserOptions: {
sourceType: 'module',
ecmaVersion: 2021,
ecmaFeatures: { jsx: true },
},
errors: [
{ messageId: 'deprecated', data: { token: '--bui-fg-success' } },
],
},
],
});
+2 -1
View File
@@ -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',
},
};
+269 -172
View File
@@ -14,192 +14,289 @@
* 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 `<div>` 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) => (
<div
style={{
backgroundColor: 'var(--bui-bg-inherit)',
padding: '0.5rem 0.75rem',
borderRadius: '0.25rem',
outline: '1px dashed var(--bui-fg-secondary)',
display: 'flex',
alignItems: 'center',
gap: '0.75rem',
padding: '0.375rem 0',
}}
>
<Text>{label}</Text>
<div
style={{
width: 28,
height: 28,
borderRadius: 6,
backgroundColor: `var(${token})`,
border: '1px solid rgba(127 127 127 / 20%)',
flexShrink: 0,
}}
/>
<div style={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<span
style={{
fontSize: '0.8125rem',
color: 'var(--bui-fg-primary)',
lineHeight: 1.3,
}}
>
{label}
</span>
<span
style={{
fontSize: '0.6875rem',
color: 'var(--bui-fg-secondary)',
fontFamily: 'var(--bui-font-monospace)',
lineHeight: 1.3,
}}
>
{token}
</span>
</div>
</div>
);
export const Default = meta.story({
const Section = ({
title,
tokens,
}: {
title: string;
tokens: SwatchItem[];
}) => (
<div style={{ display: 'flex', flexDirection: 'column', minWidth: 260 }}>
<div
style={{
fontSize: '0.75rem',
fontWeight: 600,
textTransform: 'uppercase',
letterSpacing: '0.06em',
color: 'var(--bui-fg-secondary)',
padding: '0.5rem 0',
marginBottom: '0.25rem',
borderBottom: '1px solid var(--bui-border-1)',
}}
>
{title}
</div>
{tokens.map(t => (
<Swatch key={t.token} {...t} />
))}
</div>
);
export const All = meta.story({
name: 'All Colors',
render: () => (
<div style={{ backgroundColor: 'var(--bui-bg-app)' }}>
<Box p="4" style={{ backgroundColor: 'var(--bui-bg-neutral-1)' }}>
<Flex direction="row" gap="4" align="center">
<Text>Neutral 1</Text>
<Flex
px="2"
py="1"
style={{ backgroundColor: 'var(--bui-bg-neutral-1-hover)' }}
>
<Text>Hover</Text>
</Flex>
<Flex
px="2"
py="1"
style={{ backgroundColor: 'var(--bui-bg-neutral-1-pressed)' }}
>
<Text>Pressed</Text>
</Flex>
<Flex
px="2"
py="1"
style={{ backgroundColor: 'var(--bui-bg-neutral-1-disabled)' }}
>
<Text style={{ color: 'var(--bui-fg-disabled)' }}>Disabled</Text>
</Flex>
</Flex>
<Box
p="4"
mt="4"
style={{ backgroundColor: 'var(--bui-bg-neutral-2)' }}
>
<Flex direction="row" gap="4" align="center">
<Text>Neutral 2</Text>
<Flex
px="2"
py="1"
style={{ backgroundColor: 'var(--bui-bg-neutral-2-hover)' }}
>
<Text>Hover</Text>
</Flex>
<Flex
px="2"
py="1"
style={{ backgroundColor: 'var(--bui-bg-neutral-2-pressed)' }}
>
<Text>Pressed</Text>
</Flex>
<Flex
px="2"
py="1"
style={{ backgroundColor: 'var(--bui-bg-neutral-2-disabled)' }}
>
<Text style={{ color: 'var(--bui-fg-disabled)' }}>Disabled</Text>
</Flex>
</Flex>
<Box
p="4"
mt="4"
style={{ backgroundColor: 'var(--bui-bg-neutral-3)' }}
>
<Flex direction="row" gap="4" align="center">
<Text>Neutral 3</Text>
<Flex
px="2"
py="1"
style={{ backgroundColor: 'var(--bui-bg-neutral-3-hover)' }}
>
<Text>Hover</Text>
</Flex>
<Flex
px="2"
py="1"
style={{ backgroundColor: 'var(--bui-bg-neutral-3-pressed)' }}
>
<Text>Pressed</Text>
</Flex>
<Flex
px="2"
py="1"
style={{ backgroundColor: 'var(--bui-bg-neutral-3-disabled)' }}
>
<Text style={{ color: 'var(--bui-fg-disabled)' }}>
Disabled
</Text>
</Flex>
</Flex>
<Box
p="4"
mt="4"
style={{ backgroundColor: 'var(--bui-bg-neutral-4)' }}
>
<Flex direction="row" gap="4" align="center">
<Text>Neutral 4</Text>
<Flex
px="2"
py="1"
style={{ backgroundColor: 'var(--bui-bg-neutral-4-hover)' }}
>
<Text>Hover</Text>
</Flex>
<Flex
px="2"
py="1"
style={{ backgroundColor: 'var(--bui-bg-neutral-4-pressed)' }}
>
<Text>Pressed</Text>
</Flex>
<Flex
px="2"
py="1"
style={{
backgroundColor: 'var(--bui-bg-neutral-4-disabled)',
}}
>
<Text style={{ color: 'var(--bui-fg-disabled)' }}>
Disabled
</Text>
</Flex>
</Flex>
</Box>
</Box>
</Box>
</Box>
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))',
gap: '2.5rem 3rem',
padding: '2rem',
}}
>
<Section
title="Gray Scale"
tokens={[
{ label: 'Gray 1', token: '--bui-gray-1' },
{ label: 'Gray 2', token: '--bui-gray-2' },
{ label: 'Gray 3', token: '--bui-gray-3' },
{ label: 'Gray 4', token: '--bui-gray-4' },
{ label: 'Gray 5', token: '--bui-gray-5' },
{ label: 'Gray 6', token: '--bui-gray-6' },
{ label: 'Gray 7', token: '--bui-gray-7' },
{ label: 'Gray 8', token: '--bui-gray-8' },
{ label: 'Gray 9', token: '--bui-gray-9' },
{ label: 'Gray 10', token: '--bui-gray-10' },
{ label: 'Gray 11', token: '--bui-gray-11' },
]}
/>
<Section
title="Neutral Backgrounds"
tokens={[
{ label: 'App', token: '--bui-bg-app' },
{ label: 'Neutral 1', token: '--bui-bg-neutral-1' },
{ label: 'Neutral 2', token: '--bui-bg-neutral-2' },
{ label: 'Neutral 3', token: '--bui-bg-neutral-3' },
{ label: 'Neutral 4', token: '--bui-bg-neutral-4' },
]}
/>
<Section
title="Foreground"
tokens={[
{ label: 'Primary', token: '--bui-fg-primary' },
{ label: 'Secondary', token: '--bui-fg-secondary' },
{ label: 'Disabled', token: '--bui-fg-disabled' },
{ label: 'Positive', token: '--bui-fg-positive' },
{ label: 'Negative', token: '--bui-fg-negative' },
{ label: 'Warning', token: '--bui-fg-warning' },
{ label: 'Announcement', token: '--bui-fg-announcement' },
]}
/>
<Section
title="Border"
tokens={[
{ label: 'Border 1', token: '--bui-border-1' },
{ label: 'Border 2', token: '--bui-border-2' },
]}
/>
<Section
title="Accent"
tokens={[
{ label: 'Bg', token: '--bui-accent-bg' },
{ label: 'Bg - Hover', token: '--bui-accent-bg-hover' },
{ label: 'Bg - Disabled', token: '--bui-accent-bg-disabled' },
{ label: 'Fg', token: '--bui-accent-fg' },
{ label: 'Fg - Disabled', token: '--bui-accent-fg-disabled' },
]}
/>
<Section
title="Announcement"
tokens={[
{ label: 'Bg - Base', token: '--bui-announcement-bg' },
{
label: 'Bg - Base - Hover',
token: '--bui-announcement-bg-hover',
},
{
label: 'Bg - Base - Disabled',
token: '--bui-announcement-bg-disabled',
},
{ label: 'Bg - Subdued', token: '--bui-announcement-bg-subdued' },
{
label: 'Bg - Subdued - Hover',
token: '--bui-announcement-bg-subdued-hover',
},
{
label: 'Bg - Subdued - Disabled',
token: '--bui-announcement-bg-subdued-disabled',
},
{ label: 'Border', token: '--bui-announcement-border' },
{ label: 'Fg - On Base', token: '--bui-announcement-fg' },
{
label: 'Fg - On Base - Disabled',
token: '--bui-announcement-fg-disabled',
},
{
label: 'Fg - On Subdued',
token: '--bui-announcement-fg-subdued',
},
{
label: 'Fg - On Subdued - Disabled',
token: '--bui-announcement-fg-subdued-disabled',
},
]}
/>
<Section
title="Warning"
tokens={[
{ label: 'Bg - Base', token: '--bui-warning-bg' },
{ label: 'Bg - Base - Hover', token: '--bui-warning-bg-hover' },
{
label: 'Bg - Base - Disabled',
token: '--bui-warning-bg-disabled',
},
{ label: 'Bg - Subdued', token: '--bui-warning-bg-subdued' },
{
label: 'Bg - Subdued - Hover',
token: '--bui-warning-bg-subdued-hover',
},
{
label: 'Bg - Subdued - Disabled',
token: '--bui-warning-bg-subdued-disabled',
},
{ label: 'Border', token: '--bui-warning-border' },
{ label: 'Fg - On Base', token: '--bui-warning-fg' },
{
label: 'Fg - On Base - Disabled',
token: '--bui-warning-fg-disabled',
},
{ label: 'Fg - On Subdued', token: '--bui-warning-fg-subdued' },
{
label: 'Fg - On Subdued - Disabled',
token: '--bui-warning-fg-subdued-disabled',
},
]}
/>
<Section
title="Negative"
tokens={[
{ label: 'Bg - Base', token: '--bui-negative-bg' },
{ label: 'Bg - Base - Hover', token: '--bui-negative-bg-hover' },
{
label: 'Bg - Base - Disabled',
token: '--bui-negative-bg-disabled',
},
{ label: 'Bg - Subdued', token: '--bui-negative-bg-subdued' },
{
label: 'Bg - Subdued - Hover',
token: '--bui-negative-bg-subdued-hover',
},
{
label: 'Bg - Subdued - Disabled',
token: '--bui-negative-bg-subdued-disabled',
},
{ label: 'Border', token: '--bui-negative-border' },
{ label: 'Fg - On Base', token: '--bui-negative-fg' },
{
label: 'Fg - On Base - Disabled',
token: '--bui-negative-fg-disabled',
},
{ label: 'Fg - On Subdued', token: '--bui-negative-fg-subdued' },
{
label: 'Fg - On Subdued - Disabled',
token: '--bui-negative-fg-subdued-disabled',
},
]}
/>
<Section
title="Positive"
tokens={[
{ label: 'Bg - Base', token: '--bui-positive-bg' },
{ label: 'Bg - Base - Hover', token: '--bui-positive-bg-hover' },
{
label: 'Bg - Base - Disabled',
token: '--bui-positive-bg-disabled',
},
{ label: 'Bg - Subdued', token: '--bui-positive-bg-subdued' },
{
label: 'Bg - Subdued - Hover',
token: '--bui-positive-bg-subdued-hover',
},
{
label: 'Bg - Subdued - Disabled',
token: '--bui-positive-bg-subdued-disabled',
},
{ label: 'Border', token: '--bui-positive-border' },
{ label: 'Fg - On Base', token: '--bui-positive-fg' },
{
label: 'Fg - On Base - Disabled',
token: '--bui-positive-fg-disabled',
},
{ label: 'Fg - On Subdued', token: '--bui-positive-fg-subdued' },
{
label: 'Fg - On Subdued - Disabled',
token: '--bui-positive-fg-subdued-disabled',
},
]}
/>
</div>
),
});
export const BgInherit = meta.story({
render: () => (
<Flex direction="column" gap="4">
<Probe label="App level (no provider) — resolves to --bui-bg-app" />
<Box bg="neutral" p="4">
<Flex direction="column" gap="3">
<Probe label="Inside neutral-1 — resolves to --bui-bg-neutral-1" />
<Box bg="neutral" p="4">
<Flex direction="column" gap="3">
<Probe label="Inside neutral-2 — resolves to --bui-bg-neutral-2" />
<Box bg="neutral" p="4">
<Probe label="Inside neutral-3 — resolves to --bui-bg-neutral-3" />
</Box>
</Flex>
</Box>
</Flex>
</Box>
<Box bg="danger" p="4">
<Probe label="Inside danger — resolves to --bui-bg-danger" />
</Box>
<Box bg="warning" p="4">
<Probe label="Inside warning — resolves to --bui-bg-warning" />
</Box>
<Box bg="success" p="4">
<Probe label="Inside success — resolves to --bui-bg-success" />
</Box>
</Flex>
),
});
+184 -68
View File
@@ -67,150 +67,266 @@
--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;
/* Neutral background colors */
--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);
--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: #a4ccfb;
--bui-announcement-fg-subdued: #173da6;
--bui-announcement-fg-subdued-disabled: #7391e3;
/* 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: #aa4d00;
--bui-warning-fg-subdued: #aa4d00;
--bui-warning-fg-subdued-disabled: #e3a572;
/* 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: #f7b6b6;
--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: #0c632b;
--bui-positive-fg-subdued: #0c632b;
--bui-positive-fg-subdued-disabled: #4b9666;
/* 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;
/* Neutral background colors */
--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);
--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: #70b8ff;
--bui-announcement-fg-subdued: #70b8ff;
--bui-announcement-fg-subdued-disabled: #4275a6;
/* 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: #aa4d00;
--bui-warning-fg-subdued: #f2ab4a;
--bui-warning-fg-subdued-disabled: #aa4d00;
/* 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: #f7b6b6;
--bui-negative-fg-subdued: #fca5a5;
--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: #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: #0c632b;
--bui-positive-fg-subdued: #8ddeaa;
--bui-positive-fg-subdued-disabled: #509c6b;
/* 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;
}
}