1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-21 11:53:34 +00:00

[CL-913] add new color palette to theme and tailwind config (#17998)

* add new color palette to theme and tailwind config

* updated docs color names

* remove safelist changes

* add missing accent colors to docs

* updated color mappings

* combined docs in colors.mdx and reference in Claude.md

* add variables for white and black

* updated docs

* updated list rendering style

* more specific docs instructions

* revert to simpler colors docs reference

* remove changes to claude.md

* use rgb color variables to compose semantic
This commit is contained in:
Bryan Cunningham
2025-12-26 16:18:31 -05:00
committed by GitHub
parent 5c13b07366
commit 2da44bb300
4 changed files with 1315 additions and 114 deletions

View File

@@ -2,127 +2,772 @@ import { Meta } from "@storybook/addon-docs/blocks";
<Meta title="Documentation/Colors" />
export const Row = (name) => (
<tr class="tw-h-16">
<td class="!tw-border-none">{name}</td>
<td class={"tw-bg-" + name + " !tw-border-secondary-300"}></td>
</tr>
);
# Color System
export const Table = (args) => (
<table class={"border tw-table-auto !tw-text-main " + args.class}>
<thead>
<tr>
<th class="tw-w-40">General usage</th>
<th class="tw-w-20"></th>
</tr>
</thead>
<tbody>
{Row("background")}
{Row("background-alt")}
{Row("background-alt2")}
{Row("background-alt3")}
{Row("background-alt4")}
</tbody>
<tbody>
{Row("primary-100")}
{Row("primary-300")}
{Row("primary-600")}
{Row("primary-700")}
</tbody>
<tbody>
{Row("secondary-100")}
{Row("secondary-300")}
{Row("secondary-500")}
{Row("secondary-600")}
{Row("secondary-700")}
</tbody>
<tbody>
{Row("success-100")}
{Row("success-600")}
{Row("success-700")}
</tbody>
<tbody>
{Row("danger-100")}
{Row("danger-600")}
{Row("danger-700")}
</tbody>
<tbody>
{Row("warning-100")}
{Row("warning-600")}
{Row("warning-700")}
</tbody>
<tbody>
{Row("info-100")}
{Row("info-600")}
{Row("info-700")}
</tbody>
<tbody>
{Row("notification-100")}
{Row("notification-600")}
</tbody>
<tbody>
{Row("illustration-outline")}
{Row("illustration-bg-primary")}
{Row("illustration-bg-secondary")}
{Row("illustration-bg-tertiary")}
{Row("illustration-tertiary")}
{Row("illustration-logo")}
</tbody>
Bitwarden uses a three-tier color token architecture:
<thead>
<tr>
<th>Text</th>
<th class="tw-w-20"></th>
</tr>
</thead>
<tbody>
{Row("text-main")}
{Row("text-muted")}
{Row("text-contrast")}
{Row("text-alt2")}
{Row("text-code")}
</tbody>
- **Primitive Colors** - Raw color values from the Figma design system
- **Semantic Tokens** - Meaningful names that reference primitives
- **Tailwind Utilities** - CSS classes for components
</table>
);
## Color Token Structure
<style>
{`
table {
border-spacing: 0.5rem;
border-collapse: separate !important;
}
### Primitive Colors (Hex format)
tr {
background: none !important;
border: none !important;
}
Location: `libs/components/src/tw-theme.css`
td, th {
color: inherit !important;
}
- 10 color families: `brand`, `gray`, `red`, `orange`, `yellow`, `green`, `pink`, `coral`, `teal`,
`purple`
- 11 shades each: `050`, `100`, `200`, `300`, `400`, `500`, `600`, `700`, `800`, `900`, `950`
- Format: `--color-{family}-{shade}` (e.g., `--color-brand-600`)
th {
border: none !important;
}
`}
</style>
### Semantic Foreground Tokens
# Colors
- **Neutral**: `fg-white`, `fg-dark`, `fg-contrast`, `fg-heading`, `fg-body`, `fg-body-subtle`,
`fg-disabled`
- **Brand**: `fg-brand-soft`, `fg-brand`, `fg-brand-strong`
- **Status**: `fg-success`, `fg-success-strong`, `fg-danger`, `fg-danger-strong`, `fg-warning`,
`fg-warning-strong`, `fg-sensitive`
- **Accent**: `fg-accent-primary`, `fg-accent-secondary`, `fg-accent-tertiary` (with `-soft` and
`-strong` variants)
- Format: `--color-fg-{name}`
Tailwind traditionally has a very large color palette. Bitwarden has their own more limited color
palette instead.
### Semantic Background Tokens
This has a couple of advantages:
- **Neutral**: `bg-white`, `bg-dark`, `bg-contrast`, `bg-contrast-strong`, `bg-primary`,
`bg-secondary`, `bg-tertiary`, `bg-quaternary`, `bg-gray`, `bg-disabled`
- **Brand**: `bg-brand-softer`, `bg-brand-soft`, `bg-brand-medium`, `bg-brand`, `bg-brand-strong`
- **Status**: `bg-success-soft`, `bg-success-medium`, `bg-success`, `bg-success-strong`,
`bg-danger-soft`, `bg-danger-medium`, `bg-danger`, `bg-danger-strong`, `bg-warning-soft`,
`bg-warning-medium`, `bg-warning`, `bg-warning-strong`
- **Accent**: `bg-accent-primary-soft`, `bg-accent-primary-medium`, `bg-accent-primary`,
`bg-accent-secondary-soft`, `bg-accent-secondary-medium`, `bg-accent-secondary`,
`bg-accent-tertiary-soft`, `bg-accent-tertiary-medium`, `bg-accent-tertiary`
- **Special**: `bg-hover`, `bg-overlay`
- Format: `--color-bg-{name}`
- Promotes consistency across the application.
- Easier to maintain and make adjustments.
- Allows us to support more than two themes light & dark, should it be needed.
### Semantic Border Tokens
Below are all the permited colors. Please consult design before considering adding a new color.
- **Neutral**: `border-muted`, `border-light`, `border-base`, `border-strong`, `border-buffer`
- **Brand**: `border-brand-soft`, `border-brand`, `border-brand-strong`
- **Status**: `border-success-soft`, `border-success`, `border-success-strong`,
`border-danger-soft`, `border-danger`, `border-danger-strong`, `border-warning-soft`,
`border-warning`, `border-warning-strong`
- **Accent**: `border-accent-primary-soft`, `border-accent-primary`, `border-accent-secondary-soft`,
`border-accent-secondary`, `border-accent-tertiary-soft`, `border-accent-tertiary`
- **Focus**: `border-focus`
- Format: `--color-border-{name}`
<div class="tw-flex tw-space-x-4">
<Table />
<Table class="theme_dark tw-bg-background" />
## Semantic Color Tokens
> **Note:** Due to Tailwind's utility naming and our semantic token structure, class names will
> appear repetitive (e.g., `tw-bg-bg-primary`). This repetition is intentional:
>
> - `tw-` = Tailwind prefix
> - `bg-` = Tailwind utility type (background)
> - `bg-primary` = Our semantic token name
### Background Colors
Use `tw-bg-bg-*` for background colors. These tokens automatically adapt to dark mode.
export const Swatch = ({ name }) => {
const swatchClass = `tw-h-10 tw-w-10 tw-shrink-0 tw-rounded-lg tw-border tw-border-border-base tw-bg-${name}`;
return <div className={swatchClass} />;
};
export const BackgroundCard = ({ name, primitiveColor }) => {
const bgClass = `tw-flex tw-items-center tw-gap-3 tw-rounded-xl tw-p-4 tw-border tw-border-border-base tw-bg-bg-primary`;
const swatchClass = `tw-h-10 tw-w-10 tw-shrink-0 tw-rounded-lg tw-border tw-border-base tw-bg-bg-${name}`;
return (
<div className={bgClass}>
<div className="tw-flex-1 tw-min-w-0">
<div className="tw-font-mono tw-text-sm tw-font-semibold tw-text-fg-heading">bg-{name}</div>
<div className="tw-text-xs tw-text-fg-body-subtle tw-mt-0.5">({primitiveColor})</div>
</div>
<Swatch name={`bg-${name}`} />
</div>
);
};
<div class="sb-unstyled tw-grid tw-grid-cols-2 tw-gap-8 tw-my-6">
<div class="tw-bg-bg-primary tw-p-6 tw-rounded-lg tw-border tw-border-border-base">
<h3 class="sb-unstyled sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Light mode</h3>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Neutral</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<BackgroundCard name="white" primitiveColor="white" />
<BackgroundCard name="dark" primitiveColor="gray-800" />
<BackgroundCard name="contrast" primitiveColor="gray-800" />
<BackgroundCard name="contrast-strong" primitiveColor="gray-950" />
<BackgroundCard name="primary" primitiveColor="white" />
<BackgroundCard name="secondary" primitiveColor="gray-050" />
<BackgroundCard name="tertiary" primitiveColor="gray-050" />
<BackgroundCard name="quaternary" primitiveColor="gray-200" />
<BackgroundCard name="gray" primitiveColor="gray-300" />
<BackgroundCard name="disabled" primitiveColor="gray-100" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Brand</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<BackgroundCard name="brand-softer" primitiveColor="brand-050" />
<BackgroundCard name="brand-soft" primitiveColor="brand-100" />
<BackgroundCard name="brand-medium" primitiveColor="brand-200" />
<BackgroundCard name="brand" primitiveColor="brand-700" />
<BackgroundCard name="brand-strong" primitiveColor="brand-800" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Status</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<BackgroundCard name="success-soft" primitiveColor="green-050" />
<BackgroundCard name="success-medium" primitiveColor="green-100" />
<BackgroundCard name="success" primitiveColor="green-600" />
<BackgroundCard name="success-strong" primitiveColor="green-700" />
<BackgroundCard name="danger-soft" primitiveColor="red-050" />
<BackgroundCard name="danger-medium" primitiveColor="red-100" />
<BackgroundCard name="danger" primitiveColor="red-600" />
<BackgroundCard name="danger-strong" primitiveColor="red-700" />
<BackgroundCard name="warning-soft" primitiveColor="orange-050" />
<BackgroundCard name="warning-medium" primitiveColor="orange-100" />
<BackgroundCard name="warning" primitiveColor="orange-600" />
<BackgroundCard name="warning-strong" primitiveColor="orange-700" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Accent</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<BackgroundCard name="accent-primary-soft" primitiveColor="teal-050" />
<BackgroundCard name="accent-primary-medium" primitiveColor="teal-100" />
<BackgroundCard name="accent-primary" primitiveColor="teal-400" />
<BackgroundCard name="accent-secondary-soft" primitiveColor="coral-050" />
<BackgroundCard name="accent-secondary-medium" primitiveColor="coral-100" />
<BackgroundCard name="accent-secondary" primitiveColor="coral-400" />
<BackgroundCard name="accent-tertiary-soft" primitiveColor="purple-050" />
<BackgroundCard name="accent-tertiary-medium" primitiveColor="purple-100" />
<BackgroundCard name="accent-tertiary" primitiveColor="purple-600" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Hover</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<BackgroundCard name="hover" primitiveColor="rgba" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Overlay</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<BackgroundCard name="overlay" primitiveColor="rgba" />
</div>
</div>
</div>
<div class="theme_dark tw-bg-bg-primary tw-p-6 tw-rounded-lg">
<h3 class="sb-unstyled sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Dark mode</h3>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Neutral</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<BackgroundCard name="white" primitiveColor="white" />
<BackgroundCard name="dark" primitiveColor="gray-800" />
<BackgroundCard name="contrast" primitiveColor="gray-050" />
<BackgroundCard name="contrast-strong" primitiveColor="gray-950" />
<BackgroundCard name="primary" primitiveColor="gray-900" />
<BackgroundCard name="secondary" primitiveColor="gray-800" />
<BackgroundCard name="tertiary" primitiveColor="gray-950" />
<BackgroundCard name="quaternary" primitiveColor="gray-950" />
<BackgroundCard name="gray" primitiveColor="gray-600" />
<BackgroundCard name="disabled" primitiveColor="gray-950" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Brand</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<BackgroundCard name="brand-softer" primitiveColor="brand-950" />
<BackgroundCard name="brand-soft" primitiveColor="brand-900" />
<BackgroundCard name="brand-medium" primitiveColor="brand-800" />
<BackgroundCard name="brand" primitiveColor="brand-400" />
<BackgroundCard name="brand-strong" primitiveColor="brand-300" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Status</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<BackgroundCard name="success-soft" primitiveColor="green-950" />
<BackgroundCard name="success-medium" primitiveColor="green-900" />
<BackgroundCard name="success" primitiveColor="green-400" />
<BackgroundCard name="success-strong" primitiveColor="green-300" />
<BackgroundCard name="danger-soft" primitiveColor="red-950" />
<BackgroundCard name="danger-medium" primitiveColor="red-900" />
<BackgroundCard name="danger" primitiveColor="red-400" />
<BackgroundCard name="danger-strong" primitiveColor="red-300" />
<BackgroundCard name="warning-soft" primitiveColor="orange-950" />
<BackgroundCard name="warning-medium" primitiveColor="orange-900" />
<BackgroundCard name="warning" primitiveColor="orange-400" />
<BackgroundCard name="warning-strong" primitiveColor="orange-300" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Accent</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<BackgroundCard name="accent-primary-soft" primitiveColor="teal-950" />
<BackgroundCard name="accent-primary-medium" primitiveColor="teal-900" />
<BackgroundCard name="accent-primary" primitiveColor="teal-400" />
<BackgroundCard name="accent-secondary-soft" primitiveColor="coral-950" />
<BackgroundCard name="accent-secondary-medium" primitiveColor="coral-900" />
<BackgroundCard name="accent-secondary" primitiveColor="coral-400" />
<BackgroundCard name="accent-tertiary-soft" primitiveColor="purple-950" />
<BackgroundCard name="accent-tertiary-medium" primitiveColor="purple-900" />
<BackgroundCard name="accent-tertiary" primitiveColor="purple-600" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Hover</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<BackgroundCard name="hover" primitiveColor="rgba" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Overlay</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<BackgroundCard name="overlay" primitiveColor="rgba" />
</div>
</div>
</div>
</div>
---
### Foreground Colors
Use `tw-text-fg-*` for text colors. These tokens automatically adapt to dark mode.
export const ForegroundCard = ({ name, primitiveColor }) => {
const textClass = `tw-text-fg-${name} tw-text-2xl tw-font-bold tw-shrink-0`;
return (
<div className="tw-flex tw-items-center tw-gap-3 tw-rounded-xl tw-p-4 tw-border tw-border-border-base tw-bg-bg-primary">
<div className="tw-flex-1 tw-min-w-0">
<div className="tw-font-mono tw-text-sm tw-font-semibold tw-text-fg-heading">fg-{name}</div>
<div className="tw-text-xs tw-text-fg-body-subtle tw-mt-0.5">({primitiveColor})</div>
</div>
<Swatch name={`fg-${name}`} />
</div>
);
};
<div class="sb-unstyled tw-grid tw-grid-cols-2 tw-gap-8 tw-my-6">
<div class="tw-bg-bg-primary tw-p-6 tw-rounded-lg tw-border tw-border-border-base">
<h3 class="sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Light mode</h3>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Neutral</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<ForegroundCard name="white" primitiveColor="#ffffff" />
<ForegroundCard name="dark" primitiveColor="gray-900" />
<ForegroundCard name="contrast" primitiveColor="white" />
<ForegroundCard name="heading" primitiveColor="gray-900" />
<ForegroundCard name="body" primitiveColor="gray-600" />
<ForegroundCard name="body-subtle" primitiveColor="gray-500" />
<ForegroundCard name="disabled" primitiveColor="gray-400" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Brand</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<ForegroundCard name="brand-soft" primitiveColor="brand-200" />
<ForegroundCard name="brand" primitiveColor="brand-700" />
<ForegroundCard name="brand-strong" primitiveColor="brand-900" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Status</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<ForegroundCard name="success" primitiveColor="green-700" />
<ForegroundCard name="success-strong" primitiveColor="green-900" />
<ForegroundCard name="danger" primitiveColor="red-700" />
<ForegroundCard name="danger-strong" primitiveColor="red-900" />
<ForegroundCard name="warning" primitiveColor="orange-600" />
<ForegroundCard name="warning-strong" primitiveColor="orange-900" />
<ForegroundCard name="sensitive" primitiveColor="pink-600" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Accent</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<ForegroundCard name="accent-primary-soft" primitiveColor="teal-200" />
<ForegroundCard name="accent-primary" primitiveColor="teal-400" />
<ForegroundCard name="accent-primary-strong" primitiveColor="teal-800" />
<ForegroundCard name="accent-secondary-soft" primitiveColor="coral-200" />
<ForegroundCard name="accent-secondary" primitiveColor="coral-400" />
<ForegroundCard name="accent-secondary-strong" primitiveColor="coral-900" />
<ForegroundCard name="accent-tertiary-soft" primitiveColor="purple-200" />
<ForegroundCard name="accent-tertiary" primitiveColor="purple-700" />
<ForegroundCard name="accent-tertiary-strong" primitiveColor="purple-900" />
</div>
</div>
</div>
<div class="theme_dark tw-bg-bg-primary tw-p-6 tw-rounded-lg">
<h3 class="sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Dark mode</h3>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Neutral</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<ForegroundCard name="white" primitiveColor="#ffffff" />
<ForegroundCard name="dark" primitiveColor="gray-900" />
<ForegroundCard name="contrast" primitiveColor="gray-900" />
<ForegroundCard name="heading" primitiveColor="gray-050" />
<ForegroundCard name="body" primitiveColor="gray-200" />
<ForegroundCard name="body-subtle" primitiveColor="gray-400" />
<ForegroundCard name="disabled" primitiveColor="gray-600" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Brand</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<ForegroundCard name="brand-soft" primitiveColor="brand-500" />
<ForegroundCard name="brand" primitiveColor="brand-400" />
<ForegroundCard name="brand-strong" primitiveColor="brand-200" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Status</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<ForegroundCard name="success" primitiveColor="green-400" />
<ForegroundCard name="success-strong" primitiveColor="green-100" />
<ForegroundCard name="danger" primitiveColor="red-400" />
<ForegroundCard name="danger-strong" primitiveColor="red-100" />
<ForegroundCard name="warning" primitiveColor="orange-400" />
<ForegroundCard name="warning-strong" primitiveColor="orange-100" />
<ForegroundCard name="sensitive" primitiveColor="pink-300" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Accent</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<ForegroundCard name="accent-primary-soft" primitiveColor="teal-400" />
<ForegroundCard name="accent-primary" primitiveColor="teal-300" />
<ForegroundCard name="accent-primary-strong" primitiveColor="teal-100" />
<ForegroundCard name="accent-secondary-soft" primitiveColor="coral-500" />
<ForegroundCard name="accent-secondary" primitiveColor="coral-400" />
<ForegroundCard name="accent-secondary-strong" primitiveColor="coral-100" />
<ForegroundCard name="accent-tertiary-soft" primitiveColor="purple-500" />
<ForegroundCard name="accent-tertiary" primitiveColor="purple-400" />
<ForegroundCard name="accent-tertiary-strong" primitiveColor="purple-100" />
</div>
</div>
</div>
</div>
---
### Border Colors
Use `tw-border-border-*` for border colors. These tokens automatically adapt to dark mode.
export const BorderCard = ({ name, primitiveColor }) => {
return (
<div className="tw-flex tw-items-center tw-gap-3 tw-rounded-xl tw-p-4 tw-border tw-border-border-base tw-bg-bg-primary">
<div className="tw-flex-1 tw-min-w-0">
<div className="tw-font-mono tw-text-sm tw-font-semibold tw-text-fg-heading">
border-{name}
</div>
<div className="tw-text-xs tw-text-fg-body-subtle tw-mt-0.5">({primitiveColor})</div>
</div>
<Swatch name={`border-${name}`} />
</div>
);
};
<div class="sb-unstyled tw-grid tw-grid-cols-2 tw-gap-8 tw-my-6">
<div class="tw-bg-bg-primary tw-p-6 tw-rounded-lg tw-border tw-border-border-base">
<h3 class="sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Light mode</h3>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Neutral</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<BorderCard name="muted" primitiveColor="gray-100" />
<BorderCard name="light" primitiveColor="gray-200" />
<BorderCard name="base" primitiveColor="gray-200" />
<BorderCard name="strong" primitiveColor="gray-300" />
<BorderCard name="buffer" primitiveColor="gray-100" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Brand</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<BorderCard name="brand-soft" primitiveColor="brand-200" />
<BorderCard name="brand" primitiveColor="brand-700" />
<BorderCard name="brand-strong" primitiveColor="brand-900" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Status</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<BorderCard name="success-soft" primitiveColor="green-200" />
<BorderCard name="success" primitiveColor="green-700" />
<BorderCard name="success-strong" primitiveColor="green-900" />
<BorderCard name="danger-soft" primitiveColor="red-200" />
<BorderCard name="danger" primitiveColor="red-700" />
<BorderCard name="danger-strong" primitiveColor="red-900" />
<BorderCard name="warning-soft" primitiveColor="orange-200" />
<BorderCard name="warning" primitiveColor="orange-600" />
<BorderCard name="warning-strong" primitiveColor="orange-900" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Accent</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<BorderCard name="accent-primary-soft" primitiveColor="teal-200" />
<BorderCard name="accent-primary" primitiveColor="teal-600" />
<BorderCard name="accent-secondary-soft" primitiveColor="coral-200" />
<BorderCard name="accent-secondary" primitiveColor="coral-600" />
<BorderCard name="accent-tertiary-soft" primitiveColor="purple-200" />
<BorderCard name="accent-tertiary" primitiveColor="purple-600" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Focus</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<BorderCard name="focus" primitiveColor="#000000" />
</div>
</div>
</div>
<div class="theme_dark tw-bg-bg-primary tw-p-6 tw-rounded-lg">
<h3 class="sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Dark mode</h3>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Neutral</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<BorderCard name="muted" primitiveColor="gray-800" />
<BorderCard name="light" primitiveColor="gray-700" />
<BorderCard name="base" primitiveColor="gray-700" />
<BorderCard name="strong" primitiveColor="gray-600" />
<BorderCard name="buffer" primitiveColor="gray-900" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Brand</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<BorderCard name="brand-soft" primitiveColor="brand-800" />
<BorderCard name="brand" primitiveColor="brand-700" />
<BorderCard name="brand-strong" primitiveColor="brand-600" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Status</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<BorderCard name="success-soft" primitiveColor="green-800" />
<BorderCard name="success" primitiveColor="green-400" />
<BorderCard name="success-strong" primitiveColor="green-200" />
<BorderCard name="danger-soft" primitiveColor="red-800" />
<BorderCard name="danger" primitiveColor="red-400" />
<BorderCard name="danger-strong" primitiveColor="red-200" />
<BorderCard name="warning-soft" primitiveColor="orange-800" />
<BorderCard name="warning" primitiveColor="orange-400" />
<BorderCard name="warning-strong" primitiveColor="orange-200" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Accent</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<BorderCard name="accent-primary-soft" primitiveColor="teal-800" />
<BorderCard name="accent-primary" primitiveColor="teal-600" />
<BorderCard name="accent-secondary-soft" primitiveColor="coral-800" />
<BorderCard name="accent-secondary" primitiveColor="coral-500" />
<BorderCard name="accent-tertiary-soft" primitiveColor="purple-800" />
<BorderCard name="accent-tertiary" primitiveColor="purple-500" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Focus</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<BorderCard name="focus" primitiveColor="#ffffff" />
</div>
</div>
</div>
</div>
---
## Usage Guidelines
### ✅ DO - Use semantic tokens via Tailwind
```html
<!-- Text colors -->
<h1 class="tw-text-fg-heading">Heading text</h1>
<p class="tw-text-fg-body">Body text</p>
<button class="tw-text-fg-brand">Brand action</button>
<span class="tw-text-fg-danger">Error message</span>
<!-- Background colors -->
<div class="tw-bg-bg-primary">Primary background</div>
<div class="tw-bg-bg-secondary">Secondary background</div>
<button class="tw-bg-bg-brand tw-text-fg-white">Brand button</button>
<div class="tw-bg-bg-danger-soft tw-text-fg-danger">Danger alert</div>
<!-- Border colors -->
<div class="tw-border tw-border-border-base">Base border</div>
<input class="tw-border tw-border-border-light focus:tw-border-border-focus" />
<div class="tw-border-2 tw-border-border-brand">Brand border</div>
<button class="tw-border tw-border-border-danger">Danger border</button>
<!-- Combined examples -->
<div
class="tw-bg-bg-success-soft tw-text-fg-success tw-border tw-border-border-success-soft tw-rounded tw-p-4"
>
Success alert with matching colors
</div>
<!-- Hover states -->
<div class="hover:tw-bg-bg-hover">Hover effect</div>
<!-- Overlays -->
<div class="tw-bg-bg-overlay">Modal overlay</div>
```
### ❌ DON'T - Use primitive colors directly
```html
<!-- Bad: These Tailwind classes don't exist (primitives not exposed) -->
<p class="tw-text-brand-900">Text</p>
<div class="tw-bg-brand-600">Background</div>
<!-- Bad: Using primitives with Tailwind bracket notation -->
<p class="tw-text-[var(--color-brand-600)]">Text</p>
<div class="tw-bg-[var(--color-success-700)]">Background</div>
<!-- Bad: Using primitive CSS variables bypasses the semantic layer -->
<span style="color: var(--color-brand-600)">Text</span>
<div style="background: var(--color-success-700)">Background</div>
```
**Why this is wrong:** Primitives aren't semantic and may change. Always use semantic tokens like
`tw-text-fg-brand`, `tw-bg-success`, etc.
---
## Dark Mode
- Semantic tokens automatically adapt to dark mode via `.theme_dark` class
- No component changes needed when theme switches
- The same semantic token name works in both light and dark themes
- All color values are automatically swapped based on the active theme
---
## Migration Strategy
- **New components:** Use semantic tokens (`fg-*`, `bg-*`, `border-*`) exclusively
- **Existing components:** Keep legacy tokens until refactoring
- **When refactoring:** Replace legacy tokens with semantic equivalents
---
## Legacy Colors
**Legacy colors (RGB format)** still exist for backwards compatibility:
- `primary-*`, `secondary-*`, `success-*`, `danger-*`, `warning-*`, etc.
- Use these only when updating existing components
- Migrate to new semantic tokens when refactoring
The following legacy colors are displayed below with both light and dark mode values:
export const LegacyCard = ({ name }) => {
return (
<div className="tw-flex tw-items-center tw-gap-3 tw-rounded-xl tw-p-4 tw-border tw-border-border-base tw-bg-bg-primary">
<div className="tw-flex-1 tw-min-w-0">
<div className="tw-font-mono tw-text-sm tw-font-semibold tw-text-fg-heading">{name}</div>
<div className="tw-text-xs tw-text-fg-body-subtle tw-mt-0.5">(legacy RGB format)</div>
</div>
<Swatch name={name} />
</div>
);
};
<div class="tw-grid tw-grid-cols-2 tw-gap-8">
<div class="tw-bg-bg-primary tw-p-6 tw-rounded-lg">
<h3 class="sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Light mode</h3>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">General</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<LegacyCard name="background" />
<LegacyCard name="background-alt" />
<LegacyCard name="background-alt2" />
<LegacyCard name="background-alt3" />
<LegacyCard name="background-alt4" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Primary</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<LegacyCard name="primary-100" />
<LegacyCard name="primary-300" />
<LegacyCard name="primary-600" />
<LegacyCard name="primary-700" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">
Secondary
</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<LegacyCard name="secondary-100" />
<LegacyCard name="secondary-300" />
<LegacyCard name="secondary-500" />
<LegacyCard name="secondary-600" />
<LegacyCard name="secondary-700" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Success</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<LegacyCard name="success-100" />
<LegacyCard name="success-600" />
<LegacyCard name="success-700" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Danger</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<LegacyCard name="danger-100" />
<LegacyCard name="danger-600" />
<LegacyCard name="danger-700" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Warning</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<LegacyCard name="warning-100" />
<LegacyCard name="warning-600" />
<LegacyCard name="warning-700" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Info</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<LegacyCard name="info-100" />
<LegacyCard name="info-600" />
<LegacyCard name="info-700" />
</div>
</div>
</div>
<div class="theme_dark tw-bg-bg-primary tw-p-6 tw-rounded-lg">
<h3 class="sb-unstyled tw-mb-6 tw-text-xl tw-font-bold tw-text-fg-heading">Dark mode</h3>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">General</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<LegacyCard name="background" />
<LegacyCard name="background-alt" />
<LegacyCard name="background-alt2" />
<LegacyCard name="background-alt3" />
<LegacyCard name="background-alt4" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Primary</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<LegacyCard name="primary-100" />
<LegacyCard name="primary-300" />
<LegacyCard name="primary-600" />
<LegacyCard name="primary-700" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">
Secondary
</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<LegacyCard name="secondary-100" />
<LegacyCard name="secondary-300" />
<LegacyCard name="secondary-500" />
<LegacyCard name="secondary-600" />
<LegacyCard name="secondary-700" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Success</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<LegacyCard name="success-100" />
<LegacyCard name="success-600" />
<LegacyCard name="success-700" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Danger</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<LegacyCard name="danger-100" />
<LegacyCard name="danger-600" />
<LegacyCard name="danger-700" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Warning</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<LegacyCard name="warning-100" />
<LegacyCard name="warning-600" />
<LegacyCard name="warning-700" />
</div>
</div>
<div class="sb-unstyled tw-mb-6">
<h4 class="sb-unstyled tw-mb-3 tw-text-base tw-font-semibold tw-text-fg-heading">Info</h4>
<div class="tw-grid tw-grid-cols-1 tw-gap-2">
<LegacyCard name="info-100" />
<LegacyCard name="info-600" />
<LegacyCard name="info-700" />
</div>
</div>
</div>
</div>

View File

@@ -13,6 +13,12 @@
@tailwind utilities;
:root {
/* ========================================
* LEGACY COLORS (RGB format)
* These are the original colors used throughout the app.
* Use these for existing components until migration is complete.
* ======================================== */
--color-transparent-hover: rgb(0 0 0 / 0.02);
--color-shadow: 168 179 200;
@@ -74,6 +80,279 @@
--color-illustration-bg-tertiary: 255 255 255;
--color-illustration-tertiary: 255 191 0;
--color-illustration-logo: 23 93 220;
/* ========================================
* NEW COLOR PALETTE (Hex format)
* These colors are from the new Figma design system.
* Use these for new components and features.
* Format: --color-{family}-{shade} where shade ranges from 050 to 950
* ======================================== */
/* Brand Colors */
--color-brand-050: #eef6ff;
--color-brand-100: #dbeafe;
--color-brand-200: #bedbff;
--color-brand-300: #8ec5ff;
--color-brand-400: #6baefa;
--color-brand-500: #418bfb;
--color-brand-600: #2a70f4;
--color-brand-700: #175ddc;
--color-brand-800: #0d43af;
--color-brand-900: #0c3276;
--color-brand-950: #162455;
/* Gray Colors */
--color-gray-050: #f9fafb;
--color-gray-100: #f3f4f6;
--color-gray-200: #e5e7eb;
--color-gray-300: #d1d5dc;
--color-gray-400: #99a1af;
--color-gray-500: #6a7282;
--color-gray-600: #4a5565;
--color-gray-700: #333e4f;
--color-gray-800: #1e2939;
--color-gray-900: #101828;
--color-gray-950: #070b18;
--color-gray-950-rgb: 7, 11, 24;
/* Red Colors */
--color-red-050: #fef2f2;
--color-red-100: #ffe2e2;
--color-red-200: #ffc9c9;
--color-red-300: #ffa2a2;
--color-red-400: #ff6467;
--color-red-500: #fb2c36;
--color-red-600: #e7000b;
--color-red-700: #c10007;
--color-red-800: #9f0712;
--color-red-900: #791112;
--color-red-950: #460809;
/* Orange Colors */
--color-orange-050: #fff8f1;
--color-orange-100: #feecdc;
--color-orange-200: #fcd9bd;
--color-orange-300: #fdba8c;
--color-orange-400: #ff8a4c;
--color-orange-500: #ff5a1f;
--color-orange-600: #d03801;
--color-orange-700: #b43403;
--color-orange-800: #8a2c0d;
--color-orange-900: #70240b;
--color-orange-950: #441306;
/* Yellow Colors */
--color-yellow-050: #fefce8;
--color-yellow-100: #fef9c2;
--color-yellow-200: #fff085;
--color-yellow-300: #ffdf20;
--color-yellow-400: #fdc700;
--color-yellow-500: #f0b100;
--color-yellow-600: #d08700;
--color-yellow-700: #a65f00;
--color-yellow-800: #894b00;
--color-yellow-900: #733e0a;
--color-yellow-950: #432004;
/* Green Colors */
--color-green-050: #f0fdf4;
--color-green-100: #dcfce7;
--color-green-200: #b9f8cf;
--color-green-300: #7bf1a8;
--color-green-400: #18dc7a;
--color-green-500: #0abf52;
--color-green-600: #00a63e;
--color-green-700: #008236;
--color-green-800: #016630;
--color-green-900: #0d542b;
--color-green-950: #032e15;
/* Pink Colors */
--color-pink-050: #fdf2f8;
--color-pink-100: #fce7f3;
--color-pink-200: #fccee8;
--color-pink-300: #fda5d5;
--color-pink-400: #fb64b6;
--color-pink-500: #f6339a;
--color-pink-600: #e60076;
--color-pink-700: #c6005c;
--color-pink-800: #a3004c;
--color-pink-900: #861043;
--color-pink-950: #510424;
/* Coral Colors */
--color-coral-050: #fff2f0;
--color-coral-100: #ffe0dc;
--color-coral-200: #ffc1b9;
--color-coral-300: #ff9585;
--color-coral-400: #ff6550;
--color-coral-500: #ff4026;
--color-coral-600: #e11f05;
--color-coral-700: #c71800;
--color-coral-800: #a81400;
--color-coral-900: #7e0f00;
--color-coral-950: #4d0900;
/* Teal Colors */
--color-teal-050: #ecfeff;
--color-teal-100: #cefafe;
--color-teal-200: #a2f4fd;
--color-teal-300: #70ecf5;
--color-teal-400: #2cdde9;
--color-teal-500: #00c5db;
--color-teal-600: #009cb8;
--color-teal-700: #007c95;
--color-teal-800: #006278;
--color-teal-900: #0f495c;
--color-teal-950: #042e3e;
/* Purple Colors */
--color-purple-050: #faf5ff;
--color-purple-100: #f3e8ff;
--color-purple-200: #e9d4ff;
--color-purple-300: #dab2ff;
--color-purple-400: #c27aff;
--color-purple-500: #ad46ff;
--color-purple-600: #9810fa;
--color-purple-700: #8200db;
--color-purple-800: #6e11b0;
--color-purple-900: #59168b;
--color-purple-950: #3c0366;
/* White and Black */
--color-white: #ffffff;
--color-white-rgb: 255, 255, 255;
--color-black: #000000;
/* ========================================
* SEMANTIC FOREGROUND COLORS (Light Mode)
* These are the tokens that should be exposed to Tailwind
* They reference the primitive colors above
* ======================================== */
/* Neutral Foreground */
--color-fg-white: var(--color-white);
--color-fg-dark: var(--color-gray-900);
--color-fg-contrast: var(--color-white);
--color-fg-heading: var(--color-gray-900);
--color-fg-body: var(--color-gray-600);
--color-fg-body-subtle: var(--color-gray-500);
--color-fg-disabled: var(--color-gray-400);
/* Brand Foreground */
--color-fg-brand-soft: var(--color-brand-200);
--color-fg-brand: var(--color-brand-700);
--color-fg-brand-strong: var(--color-brand-900);
/* Status Foreground */
--color-fg-success: var(--color-green-700);
--color-fg-success-strong: var(--color-green-900);
--color-fg-danger: var(--color-red-700);
--color-fg-danger-strong: var(--color-red-900);
--color-fg-warning: var(--color-orange-600);
--color-fg-warning-strong: var(--color-orange-900);
--color-fg-sensitive: var(--color-pink-600);
/* Accent Foreground */
--color-fg-accent-primary-soft: var(--color-teal-200);
--color-fg-accent-primary: var(--color-teal-400);
--color-fg-accent-primary-strong: var(--color-teal-800);
--color-fg-accent-secondary-soft: var(--color-coral-200);
--color-fg-accent-secondary: var(--color-coral-400);
--color-fg-accent-secondary-strong: var(--color-coral-900);
--color-fg-accent-tertiary-soft: var(--color-purple-200);
--color-fg-accent-tertiary: var(--color-purple-700);
--color-fg-accent-tertiary-strong: var(--color-purple-900);
/* ========================================
* SEMANTIC BACKGROUND COLORS (Light Mode)
* ======================================== */
/* Neutral Background */
--color-bg-white: var(--color-white);
--color-bg-dark: var(--color-gray-800);
--color-bg-contrast: var(--color-gray-800);
--color-bg-contrast-strong: var(--color-gray-950);
--color-bg-primary: var(--color-white);
--color-bg-secondary: var(--color-gray-050);
--color-bg-tertiary: var(--color-gray-050);
--color-bg-quaternary: var(--color-gray-200);
--color-bg-gray: var(--color-gray-300);
--color-bg-disabled: var(--color-gray-100);
/* Brand Background */
--color-bg-brand-softer: var(--color-brand-050);
--color-bg-brand-soft: var(--color-brand-100);
--color-bg-brand-medium: var(--color-brand-200);
--color-bg-brand: var(--color-brand-700);
--color-bg-brand-strong: var(--color-brand-800);
/* Status Background */
--color-bg-success-soft: var(--color-green-050);
--color-bg-success-medium: var(--color-green-100);
--color-bg-success: var(--color-green-700);
--color-bg-success-strong: var(--color-green-800);
--color-bg-danger-soft: var(--color-red-050);
--color-bg-danger-medium: var(--color-red-100);
--color-bg-danger: var(--color-red-700);
--color-bg-danger-strong: var(--color-red-800);
--color-bg-warning-soft: var(--color-orange-050);
--color-bg-warning-medium: var(--color-orange-100);
--color-bg-warning: var(--color-orange-600);
--color-bg-warning-strong: var(--color-orange-700);
/* Accent Background */
--color-bg-accent-primary-soft: var(--color-teal-050);
--color-bg-accent-primary-medium: var(--color-teal-100);
--color-bg-accent-primary: var(--color-teal-400);
--color-bg-accent-secondary-soft: var(--color-coral-050);
--color-bg-accent-secondary-medium: var(--color-coral-100);
--color-bg-accent-secondary: var(--color-coral-400);
--color-bg-accent-tertiary-soft: var(--color-purple-050);
--color-bg-accent-tertiary-medium: var(--color-purple-100);
--color-bg-accent-tertiary: var(--color-purple-600);
/* Hover & Overlay */
--color-bg-hover: rgba(var(--color-gray-950-rgb), 0.05);
--color-bg-overlay: rgba(var(--color-gray-950-rgb), 0.3);
/* ========================================
* SEMANTIC BORDER COLORS (Light Mode)
* ======================================== */
/* Neutral Border */
--color-border-muted: var(--color-gray-050);
--color-border-light: var(--color-gray-100);
--color-border-base: var(--color-gray-200);
--color-border-strong: var(--color-gray-800);
--color-border-buffer: var(--color-white);
/* Brand Border */
--color-border-brand-soft: var(--color-brand-200);
--color-border-brand: var(--color-brand-700);
--color-border-brand-strong: var(--color-brand-900);
/* Status Border */
--color-border-success-soft: var(--color-green-200);
--color-border-success: var(--color-green-700);
--color-border-success-strong: var(--color-green-900);
--color-border-danger-soft: var(--color-red-200);
--color-border-danger: var(--color-red-700);
--color-border-danger-strong: var(--color-red-900);
--color-border-warning-soft: var(--color-orange-200);
--color-border-warning: var(--color-orange-600);
--color-border-warning-strong: var(--color-orange-900);
/* Accent Border */
--color-border-accent-primary-soft: var(--color-teal-200);
--color-border-accent-primary: var(--color-teal-600);
--color-border-accent-secondary-soft: var(--color-coral-200);
--color-border-accent-secondary: var(--color-coral-600);
--color-border-accent-tertiary-soft: var(--color-purple-200);
--color-border-accent-tertiary: var(--color-purple-600);
/* Focus Border */
--color-border-focus: var(--color-black);
}
.theme_light {
@@ -140,6 +419,129 @@
--color-illustration-bg-tertiary: 243 246 249;
--color-illustration-tertiary: 255 191 0;
--color-illustration-logo: 255 255 255;
/* ========================================
* SEMANTIC FOREGROUND COLORS (Dark Mode Overrides)
* ======================================== */
/* Neutral Foreground */
--color-fg-contrast: var(--color-gray-900);
--color-fg-heading: var(--color-gray-050);
--color-fg-body: var(--color-gray-200);
--color-fg-body-subtle: var(--color-gray-400);
--color-fg-disabled: var(--color-gray-600);
/* Brand Foreground */
--color-fg-brand-soft: var(--color-brand-500);
--color-fg-brand: var(--color-brand-400);
--color-fg-brand-strong: var(--color-brand-200);
/* Status Foreground */
--color-fg-success: var(--color-green-400);
--color-fg-success-strong: var(--color-green-100);
--color-fg-danger: var(--color-red-400);
--color-fg-danger-strong: var(--color-red-100);
--color-fg-warning: var(--color-orange-400);
--color-fg-warning-strong: var(--color-orange-100);
--color-fg-sensitive: var(--color-pink-300);
/* Accent Foreground */
--color-fg-accent-primary-soft: var(--color-teal-400);
--color-fg-accent-primary: var(--color-teal-300);
--color-fg-accent-primary-strong: var(--color-teal-100);
--color-fg-accent-secondary-soft: var(--color-coral-500);
--color-fg-accent-secondary: var(--color-coral-400);
--color-fg-accent-secondary-strong: var(--color-coral-100);
--color-fg-accent-tertiary-soft: var(--color-purple-500);
--color-fg-accent-tertiary: var(--color-purple-400);
--color-fg-accent-tertiary-strong: var(--color-purple-100);
/* ========================================
* SEMANTIC BACKGROUND COLORS (Dark Mode Overrides)
* ======================================== */
/* Neutral Background */
--color-bg-contrast: var(--color-gray-050);
--color-bg-contrast-strong: var(--color-gray-050);
--color-bg-primary: var(--color-gray-900);
--color-bg-secondary: var(--color-gray-800);
--color-bg-tertiary: var(--color-gray-950);
--color-bg-quaternary: var(--color-gray-700);
--color-bg-gray: var(--color-gray-600);
--color-bg-disabled: var(--color-gray-950);
/* Brand Background */
--color-bg-brand-softer: var(--color-brand-950);
--color-bg-brand-soft: var(--color-brand-900);
--color-bg-brand-medium: var(--color-brand-800);
--color-bg-brand: var(--color-brand-400);
--color-bg-brand-strong: var(--color-brand-300);
/* Status Background */
--color-bg-success-soft: var(--color-green-950);
--color-bg-success-medium: var(--color-green-900);
--color-bg-success: var(--color-green-400);
--color-bg-success-strong: var(--color-green-300);
--color-bg-danger-soft: var(--color-red-950);
--color-bg-danger-medium: var(--color-red-900);
--color-bg-danger: var(--color-red-400);
--color-bg-danger-strong: var(--color-red-300);
--color-bg-warning-soft: var(--color-orange-950);
--color-bg-warning-medium: var(--color-orange-900);
--color-bg-warning: var(--color-orange-400);
--color-bg-warning-strong: var(--color-orange-300);
/* Accent Background */
--color-bg-accent-primary-soft: var(--color-teal-950);
--color-bg-accent-primary-medium: var(--color-teal-900);
--color-bg-accent-primary: var(--color-teal-400);
--color-bg-accent-secondary-soft: var(--color-coral-950);
--color-bg-accent-secondary-medium: var(--color-coral-900);
--color-bg-accent-secondary: var(--color-coral-400);
--color-bg-accent-tertiary-soft: var(--color-purple-950);
--color-bg-accent-tertiary-medium: var(--color-purple-900);
--color-bg-accent-tertiary: var(--color-purple-600);
/* Hover & Overlay */
--color-bg-hover: rgba(var(--color-white-rgb), 0.05);
--color-bg-overlay: rgba(var(--color-gray-950-rgb), 0.85);
/* ========================================
* SEMANTIC BORDER COLORS (Dark Mode Overrides)
* ======================================== */
/* Neutral Border */
--color-border-muted: var(--color-gray-900);
--color-border-light: var(--color-gray-800);
--color-border-base: var(--color-gray-700);
--color-border-strong: var(--color-gray-400);
--color-border-buffer: var(--color-gray-950);
/* Brand Border */
--color-border-brand-soft: var(--color-brand-800);
--color-border-brand: var(--color-brand-400);
--color-border-brand-strong: var(--color-brand-200);
/* Status Border */
--color-border-success-soft: var(--color-green-800);
--color-border-success: var(--color-green-400);
--color-border-success-strong: var(--color-green-200);
--color-border-danger-soft: var(--color-red-800);
--color-border-danger: var(--color-red-400);
--color-border-danger-strong: var(--color-red-200);
--color-border-warning-soft: var(--color-orange-800);
--color-border-warning: var(--color-orange-400);
--color-border-warning-strong: var(--color-orange-200);
/* Accent Border */
--color-border-accent-primary-soft: var(--color-teal-800);
--color-border-accent-secondary-soft: var(--color-coral-800);
--color-border-accent-secondary: var(--color-coral-500);
--color-border-accent-tertiary-soft: var(--color-purple-800);
--color-border-accent-tertiary: var(--color-purple-500);
/* Focus Border */
--color-border-focus: var(--color-white);
}
@layer components {

View File

@@ -9,9 +9,9 @@ function rgba(color) {
module.exports = {
prefix: "tw-",
content: [
"./src/**/*.{html,ts}",
"./src/**/*.{html,ts,mdx}",
"../../libs/assets/src/**/*.{html,ts}",
"../../libs/components/src/**/*.{html,ts}",
"../../libs/components/src/**/*.{html,ts,mdx}",
"../../libs/key-management-ui/src/**/*.{html,ts}",
"../../libs/auth/src/**/*.{html,ts}",
],
@@ -78,6 +78,46 @@ module.exports = {
alt3: rgba("--color-background-alt3"),
alt4: rgba("--color-background-alt4"),
},
bg: {
white: "var(--color-bg-white)",
dark: "var(--color-bg-dark)",
contrast: "var(--color-bg-contrast)",
"contrast-strong": "var(--color-bg-contrast-strong)",
primary: "var(--color-bg-primary)",
secondary: "var(--color-bg-secondary)",
tertiary: "var(--color-bg-tertiary)",
quaternary: "var(--color-bg-quaternary)",
gray: "var(--color-bg-gray)",
disabled: "var(--color-bg-disabled)",
"brand-softer": "var(--color-bg-brand-softer)",
"brand-soft": "var(--color-bg-brand-soft)",
"brand-medium": "var(--color-bg-brand-medium)",
brand: "var(--color-bg-brand)",
"brand-strong": "var(--color-bg-brand-strong)",
"success-soft": "var(--color-bg-success-soft)",
"success-medium": "var(--color-bg-success-medium)",
success: "var(--color-bg-success)",
"success-strong": "var(--color-bg-success-strong)",
"danger-soft": "var(--color-bg-danger-soft)",
"danger-medium": "var(--color-bg-danger-medium)",
danger: "var(--color-bg-danger)",
"danger-strong": "var(--color-bg-danger-strong)",
"warning-soft": "var(--color-bg-warning-soft)",
"warning-medium": "var(--color-bg-warning-medium)",
warning: "var(--color-bg-warning)",
"warning-strong": "var(--color-bg-warning-strong)",
"accent-primary-soft": "var(--color-bg-accent-primary-soft)",
"accent-primary-medium": "var(--color-bg-accent-primary-medium)",
"accent-primary": "var(--color-bg-accent-primary)",
"accent-secondary-soft": "var(--color-bg-accent-secondary-soft)",
"accent-secondary-medium": "var(--color-bg-accent-secondary-medium)",
"accent-secondary": "var(--color-bg-accent-secondary)",
"accent-tertiary-soft": "var(--color-bg-accent-tertiary-soft)",
"accent-tertiary-medium": "var(--color-bg-accent-tertiary-medium)",
"accent-tertiary": "var(--color-bg-accent-tertiary)",
hover: "var(--color-bg-hover)",
overlay: "var(--color-bg-overlay)",
},
hover: {
default: "var(--color-hover-default)",
contrast: "var(--color-hover-contrast)",
@@ -92,8 +132,62 @@ module.exports = {
tertiary: rgba("--color-illustration-tertiary"),
logo: rgba("--color-illustration-logo"),
},
fg: {
white: "var(--color-fg-white)",
dark: "var(--color-fg-dark)",
contrast: "var(--color-fg-contrast)",
heading: "var(--color-fg-heading)",
body: "var(--color-fg-body)",
"body-subtle": "var(--color-fg-body-subtle)",
disabled: "var(--color-fg-disabled)",
"brand-soft": "var(--color-fg-brand-soft)",
brand: "var(--color-fg-brand)",
"brand-strong": "var(--color-fg-brand-strong)",
success: "var(--color-fg-success)",
"success-strong": "var(--color-fg-success-strong)",
danger: "var(--color-fg-danger)",
"danger-strong": "var(--color-fg-danger-strong)",
warning: "var(--color-fg-warning)",
"warning-strong": "var(--color-fg-warning-strong)",
sensitive: "var(--color-fg-sensitive)",
"accent-primary-soft": "var(--color-fg-accent-primary-soft)",
"accent-primary": "var(--color-fg-accent-primary)",
"accent-primary-strong": "var(--color-fg-accent-primary-strong)",
"accent-secondary-soft": "var(--color-fg-accent-secondary-soft)",
"accent-secondary": "var(--color-fg-accent-secondary)",
"accent-secondary-strong": "var(--color-fg-accent-secondary-strong)",
"accent-tertiary-soft": "var(--color-fg-accent-tertiary-soft)",
"accent-tertiary": "var(--color-fg-accent-tertiary)",
"accent-tertiary-strong": "var(--color-fg-accent-tertiary-strong)",
},
border: {
muted: "var(--color-border-muted)",
light: "var(--color-border-light)",
base: "var(--color-border-base)",
strong: "var(--color-border-strong)",
buffer: "var(--color-border-buffer)",
"brand-soft": "var(--color-border-brand-soft)",
brand: "var(--color-border-brand)",
"brand-strong": "var(--color-border-brand-strong)",
"success-soft": "var(--color-border-success-soft)",
success: "var(--color-border-success)",
"success-strong": "var(--color-border-success-strong)",
"danger-soft": "var(--color-border-danger-soft)",
danger: "var(--color-border-danger)",
"danger-strong": "var(--color-border-danger-strong)",
"warning-soft": "var(--color-border-warning-soft)",
warning: "var(--color-border-warning)",
"warning-strong": "var(--color-border-warning-strong)",
"accent-primary-soft": "var(--color-border-accent-primary-soft)",
"accent-primary": "var(--color-border-accent-primary)",
"accent-secondary-soft": "var(--color-border-accent-secondary-soft)",
"accent-secondary": "var(--color-border-accent-secondary)",
"accent-tertiary-soft": "var(--color-border-accent-tertiary-soft)",
"accent-tertiary": "var(--color-border-accent-tertiary)",
focus: "var(--color-border-focus)",
},
},
textColor: {
textColor: () => ({
main: rgba("--color-text-main"),
muted: rgba("--color-text-muted"),
contrast: rgba("--color-text-contrast"),
@@ -132,7 +226,62 @@ module.exports = {
notification: {
600: rgba("--color-notification-600"),
},
},
// New semantic fg tokens - manually flattened to generate tw-text-fg-* utilities
"fg-white": "var(--color-fg-white)",
"fg-dark": "var(--color-fg-dark)",
"fg-contrast": "var(--color-fg-contrast)",
"fg-heading": "var(--color-fg-heading)",
"fg-body": "var(--color-fg-body)",
"fg-body-subtle": "var(--color-fg-body-subtle)",
"fg-disabled": "var(--color-fg-disabled)",
"fg-brand-soft": "var(--color-fg-brand-soft)",
"fg-brand": "var(--color-fg-brand)",
"fg-brand-strong": "var(--color-fg-brand-strong)",
"fg-success": "var(--color-fg-success)",
"fg-success-strong": "var(--color-fg-success-strong)",
"fg-danger": "var(--color-fg-danger)",
"fg-danger-strong": "var(--color-fg-danger-strong)",
"fg-warning": "var(--color-fg-warning)",
"fg-warning-strong": "var(--color-fg-warning-strong)",
"fg-sensitive": "var(--color-fg-sensitive)",
"fg-accent-primary-soft": "var(--color-fg-accent-primary-soft)",
"fg-accent-primary": "var(--color-fg-accent-primary)",
"fg-accent-primary-strong": "var(--color-fg-accent-primary-strong)",
"fg-accent-secondary-soft": "var(--color-fg-accent-secondary-soft)",
"fg-accent-secondary": "var(--color-fg-accent-secondary)",
"fg-accent-secondary-strong": "var(--color-fg-accent-secondary-strong)",
"fg-accent-tertiary-soft": "var(--color-fg-accent-tertiary-soft)",
"fg-accent-tertiary": "var(--color-fg-accent-tertiary)",
"fg-accent-tertiary-strong": "var(--color-fg-accent-tertiary-strong)",
}),
borderColor: ({ theme }) => ({
...theme("colors"),
// New semantic border tokens - manually flattened to generate tw-border-border-* utilities
"border-muted": "var(--color-border-muted)",
"border-light": "var(--color-border-light)",
"border-base": "var(--color-border-base)",
"border-strong": "var(--color-border-strong)",
"border-buffer": "var(--color-border-buffer)",
"border-brand-soft": "var(--color-border-brand-soft)",
"border-brand": "var(--color-border-brand)",
"border-brand-strong": "var(--color-border-brand-strong)",
"border-success-soft": "var(--color-border-success-soft)",
"border-success": "var(--color-border-success)",
"border-success-strong": "var(--color-border-success-strong)",
"border-danger-soft": "var(--color-border-danger-soft)",
"border-danger": "var(--color-border-danger)",
"border-danger-strong": "var(--color-border-danger-strong)",
"border-warning-soft": "var(--color-border-warning-soft)",
"border-warning": "var(--color-border-warning)",
"border-warning-strong": "var(--color-border-warning-strong)",
"border-accent-primary-soft": "var(--color-border-accent-primary-soft)",
"border-accent-primary": "var(--color-border-accent-primary)",
"border-accent-secondary-soft": "var(--color-border-accent-secondary-soft)",
"border-accent-secondary": "var(--color-border-accent-secondary)",
"border-accent-tertiary-soft": "var(--color-border-accent-tertiary-soft)",
"border-accent-tertiary": "var(--color-border-accent-tertiary)",
"border-focus": "var(--color-border-focus)",
}),
fontFamily: {
sans: "var(--font-sans)",
serif: "var(--font-serif)",

View File

@@ -11,11 +11,16 @@ config.content = [
"bitwarden_license/bit-web/src/**/*.{html,ts,mdx}",
".storybook/preview.tsx",
];
// Safelist is required for dynamic color classes in Storybook color documentation (colors.mdx).
// Tailwind's JIT compiler cannot detect dynamically constructed class names like `tw-bg-${name}`,
// so we must explicitly safelist these patterns to ensure all color utilities are generated.
config.safelist = [
{
pattern: /tw-bg-(.*)/,
},
];
config.corePlugins.preflight = true;
module.exports = config;