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
+17 -18
View File
@@ -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);
}
+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);
}
},
};
},
};
+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',
},
};
+276 -172
View File
@@ -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 `<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={{
padding: '2rem',
backgroundColor: 'var(--bui-surface-1)',
minHeight: '100vh',
}}
>
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))',
gap: '2.5rem 3rem',
}}
>
<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="Surfaces"
tokens={[
{ label: 'Surface 1', token: '--bui-surface-1' },
{ label: 'Surface 2', token: '--bui-surface-2' },
{ label: 'Surface 3', token: '--bui-surface-3' },
{ label: 'Surface 4', token: '--bui-surface-4' },
{ label: 'Surface 5', token: '--bui-surface-5' },
]}
/>
<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>
</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 -58
View File
@@ -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;
}
}