Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .claude/rules/frontend-styling.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ Light mode is frozen: reach dark with tokens that adapt, never by changing light
[`docs/colors.md`](../../openmetadata-ui/src/main/resources/ui/docs/colors.md); dark values live under
`.dark-mode` in `globals.css`.

- **Scope:** dark mode is AI mode on core/Tailwind surfaces only. Classic mode gets no dark theme
and antd-only surfaces (`.ant-*`) are not themed for dark — migrate them to core instead
(ADR:2026-10-12-dark-mode-targets-ai-mode-core-surfaces-only).
- **Surfaces:** page → `tw:bg-page`; card/panel → core `<Card>` or `tw:bg-surface`; popover, dropdown,
select, modal, drawer → `tw:bg-overlay-surface`. `tw:bg-primary` is the page colour in dark
(invisible card); `Box` is layout only — give it no background. Wrappers/grid frames stay transparent
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Dark mode targets AI mode and core/Tailwind surfaces only

- **Status:** Accepted
- **Revisions:** v1 2026-10-12 (initial)
- **Deciders:** Harshit Shah
- **Guard:** reviewer
- **Related:** ADR:2026-10-11-migrated-antd-components-adopt-core-styling, open-metadata/openmetadata-collate#7317, open-metadata/OpenMetadata#35271

## Context
Dark-mode sweeps keep finding light edges and fills on antd components (`.ant-modal-header`,
`.ant-dropdown-menu`, `.ant-select-selector`, `.ant-collapse-item`) and on classic-mode chrome
such as the classic navbar search. Antd is being removed from the codebase component by component,
and classic (non-AI) mode is not going to ship a dark theme. Dark-mode work spent on either is
deleted by the migration that follows it.

## Decision
Classic (non-AI) mode does not support dark mode, and antd-only surfaces are not themed for dark.
Dark-mode fixes target AI mode, rendered through `@openmetadata/ui-core-components` and Tailwind
(`tw:`) surfaces, plus non-antd legacy markup that AI mode renders.

- Do not add `.dark-mode` overrides, dark token swaps or `tw:dark:*` variants whose only effect is
on an `.ant-*` selector or an antd component.
- Do not fix classic-only chrome for dark mode.
- A shared legacy LESS variable (for example `@grey-15`) may be bridged for dark when non-antd
surfaces in AI mode use it, even though antd consumers pick the change up too.
- An antd surface that looks wrong in dark is fixed by migrating it to its core equivalent.

## Consequences
Antd screens and classic mode can look unfinished in dark until they migrate; that is accepted.
Dark-mode PRs stay small and their fixes survive the antd removal. Revisit if classic mode is
kept and given a dark theme, or if the antd removal stops before the remaining surfaces are migrated.
Original file line number Diff line number Diff line change
Expand Up @@ -1226,12 +1226,12 @@
--color-text-disabled: theme(--color-gray-500);
/* Brand text = blue-300 link tone in dark (palette guideline + Collate #6491
--ai-link); regular text stays on the gray ramp. */
--color-text-brand-primary: theme(--color-brand-300);
--color-text-brand-secondary: theme(--color-brand-300);
--color-text-brand-primary: theme(--color-brand-400);
--color-text-brand-secondary: theme(--color-brand-400);
--color-text-brand-tertiary: theme(--color-gray-400);
--color-text-placeholder: theme(--color-gray-400);
--color-text-placeholder_subtle: theme(--color-gray-700);
--color-text-link: theme(--color-brand-300);
--color-text-link: theme(--color-brand-400);
--color-text-link-hover: theme(--color-brand-200);
--color-text-primary_on-brand: theme(--color-gray-50);
--color-text-secondary_on-brand: theme(--color-gray-300);
Expand Down Expand Up @@ -1282,10 +1282,10 @@
--color-fg-disabled_subtle: theme(--color-gray-600);
/* Brand foreground (icons, tab underline, active accents) = blue-300 link
tone in dark (palette guideline + Collate #6491 --ai-link). */
--color-fg-brand-primary: theme(--color-brand-300);
--color-fg-brand-secondary: theme(--color-brand-300);
--color-fg-brand-primary_alt: theme(--color-brand-300);
--color-fg-brand-secondary_alt: theme(--color-brand-300);
--color-fg-brand-primary: theme(--color-brand-400);
--color-fg-brand-secondary: theme(--color-brand-400);
--color-fg-brand-primary_alt: theme(--color-brand-400);
--color-fg-brand-secondary_alt: theme(--color-brand-400);
--color-fg-brand-secondary_hover: theme(--color-brand-200);

/* BACKGROUND COLORS */
Expand Down Expand Up @@ -1350,7 +1350,7 @@
--color-avatar-styles-bg-neutral: rgb(224 224 224 / 1);
--color-button-destructive-primary-icon: theme(--color-error-300);
--color-button-destructive-primary-icon_hover: theme(--color-error-200);
--color-button-primary-icon: theme(--color-brand-300);
--color-button-primary-icon: theme(--color-brand-400);
--color-button-primary-icon_hover: theme(--color-brand-200);
--color-featured-icon-light-fg-brand: theme(--color-brand-200);
--color-featured-icon-light-fg-error: theme(--color-error-200);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -999,7 +999,7 @@
margin: var(--om-space-6);
}
.ql-toolbar.ql-snow {
border-color: var(--om-legacy-color-dde3ea);
border-color: var(--tw-border-color-secondary);
Comment thread
gitar-bot[bot] marked this conversation as resolved.
border-top-left-radius: var(--om-radius-md);
border-top-right-radius: var(--om-radius-md);
display: flex;
Expand All @@ -1009,7 +1009,7 @@
width: 100%;
}
.ql-container.ql-snow {
border-color: var(--om-legacy-color-dde3ea);
border-color: var(--tw-border-color-secondary);
border-bottom-left-radius: var(--om-radius-md);
border-bottom-right-radius: var(--om-radius-md);
width: 100%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 95%;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ export const KeyProfileMetrics = ({ profile }: KeyProfileMetricsProps) => {
);

const sectionClassName =
'tw:border-b-[0.6px] tw:border-tertiary tw:-mt-2 tw:pb-4 tw:px-4';
const titleClassName = 'tw:text-sm tw:font-semibold tw:mb-1.5';
'tw:border-b-[0.6px] tw:border-secondary tw:pb-4 tw:px-3.5';
const titleClassName = 'tw:text-sm tw:font-semibold tw:mb-3';

return (
<div className={sectionClassName}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ const RetentionPeriod = ({
width={520}>
<Dialog.Header
className={classNames(
'tw:border-b tw:border-[var(--om-legacy-color-dde3ea)] tw:px-6 tw:py-4',
'tw:border-b tw:border-secondary tw:px-6 tw:py-4',
'tw:sm:px-6 tw:sm:pt-4 tw:*:font-medium! tw:*:leading-[22px]!',
'tw:*:text-black/85! tw:dark:border-subtle tw:dark:*:text-primary!'
)}
Expand Down Expand Up @@ -175,7 +175,7 @@ const RetentionPeriod = ({
onChange={setValue}
/>
</Dialog.Content>
<Dialog.Footer className="tw:mt-0 tw:border-[var(--om-legacy-color-dde3ea)] tw:sm:mt-0 tw:dark:border-subtle tw:*:gap-[13px]! tw:*:px-[23px]! tw:*:py-4!">
<Dialog.Footer className="tw:mt-0 tw:border-secondary tw:sm:mt-0 tw:*:gap-[13px]! tw:*:px-[23px]! tw:*:py-4!">
<Button
className="tw:h-10 tw:rounded-lg tw:px-[15px] tw:py-0 tw:font-normal tw:text-[var(--ant-primary-color)] tw:hover:bg-transparent tw:hover:text-[var(--ant-primary-color-hover)]"
color="tertiary"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,9 @@
gap: var(--om-space-8);
align-items: baseline;
}

// Dark: selected card is a 1px #1570ef edge (devrel dark palette); brand-300
// stays for light, which is frozen.
.dark-mode .explore-search-card.highlight-card {
border-color: var(--tw-color-brand-600);
}
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ const ExploreExportScopeModal = ({
onClose={onCancel}>
<Dialog.Header
className={classNames(
'tw:border-b tw:border-[var(--om-legacy-color-dde3ea)] tw:px-6 tw:py-4',
'tw:border-b tw:border-secondary tw:px-6 tw:py-4',
'tw:sm:px-6 tw:sm:pt-4 tw:*:font-medium! tw:*:leading-[22px]!',
'tw:*:text-black/85! tw:dark:border-subtle tw:dark:*:text-primary!'
)}>
Expand Down Expand Up @@ -331,7 +331,7 @@ const ExploreExportScopeModal = ({
</CoreCard>
</RadioGroup>
</Dialog.Content>
<Dialog.Footer className="tw:mt-0 tw:border-[var(--om-legacy-color-dde3ea)] tw:sm:mt-0 tw:dark:border-subtle tw:*:gap-[13px]! tw:*:px-[23px]! tw:*:py-4!">
<Dialog.Footer className="tw:mt-0 tw:border-secondary tw:sm:mt-0 tw:*:gap-[13px]! tw:*:px-[23px]! tw:*:py-4!">
<Button
className="tw:h-10 tw:rounded-lg tw:px-[15px] tw:py-0 tw:font-normal tw:text-[var(--ant-primary-color)] tw:hover:bg-transparent tw:hover:text-[var(--ant-primary-color-hover)]"
color="tertiary"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*/

@import '../../../../styles/variables.less';
@border-color: var(--om-legacy-color-dde3ea);
@border-color: var(--tw-border-color-secondary);
@awaiting-bg: var(--om-legacy-color-221-227-234-0-2);
@success-bg: var(--om-legacy-color-40-167-69-0-059);
@failure-bg: var(--om-legacy-color-203-36-49-0-059);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,19 +109,22 @@

// Dark-only overrides that match the design recording exactly. Light leaves both
// unset, falling back to the frozen light values (gray-blue-100 pill, brand-800 fg).
// - selected surface -> interactive-selected (brand-500 @16%), a blue-tinted pill;
// a solid gray-blue-900 was invisible against the gray-blue-950 panel.
// - selected label/icon -> brand-300 (#84caff); the utility-brand-800 scale lands
// on brand-200 in dark, one step too pale versus the recording.
// - selected surface -> brand-600 (#1570ef) @20%, a blue-tinted pill (devrel
// dark palette); a solid gray-blue-900 was invisible against the panel.
// - selected label/icon -> brand-400 (#53b1fd); the utility-brand-800 scale lands
// on brand-200 in dark, one step too pale.
// - rest nav/list icons -> gray-600; quaternary (gray-400) is a step too bright.
// - hover fill -> gray-700; nav-item hover reads a step up from the gray-800 nav
// backing, matching the PageShell (PR #33581) hover token.
// - hover fill -> white @6% (secondary_hover), per the devrel dark palette.
// - border/divider -> gray-700; the PageShell nav/card edges sit on gray-700.
.dark-mode {
--ask-selected-surface: var(--color-bg-brand-primary);
--ask-selected-fg: var(--color-brand-300);
--ask-selected-surface: color-mix(
in srgb,
var(--tw-color-brand-600) 20%,
transparent
);
--ask-selected-fg: var(--tw-color-brand-400);
--ask-rest-icon: var(--color-gray-600);
--ask-hover: var(--om-color-gray-700);
--ask-hover: var(--tw-background-color-secondary_hover);
--ask-border: var(--om-color-gray-700);
}

Expand Down
58 changes: 31 additions & 27 deletions openmetadata-ui/src/main/resources/ui/src/styles/app.less
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,10 @@ a[href].link-text-grey,
// .dark-mode, so referencing them here yields the dark value. Categorical ramps
// (severity, heatmap gradients) are intentionally excluded — they belong to the
// token foundation.
.dark-mode {
// `:root.dark-mode` (not `.dark-mode`): tokens.css declares the light values on
// `:root` at equal specificity and is imported after this file, so a bare
// `.dark-mode` lost and every bridged var stayed light in dark mode.
:root.dark-mode {
// Neutral surfaces / borders / muted text
--om-grey-26: var(--color-bg-secondary_subtle);
--om-grey-25: var(--color-bg-secondary_subtle);
Expand Down Expand Up @@ -873,40 +876,41 @@ a[href].link-text-grey,
--om-warning-400: var(--tw-color-utility-warning-400);
}

// AI dark-mode brand ACCENTS -> blue-300 (#84caff).
// AI dark-mode brand ACCENTS -> blue-400 (#53b1fd), the devrel dark palette:
// #1570ef carries fills/borders, #53b1fd labels/icons/links (~5.4:1 on dark).
// UntitledUIThemeProvider + AntDConfigProvider inject the configured brand
// primary (#1570ef) as theme-agnostic inline custom properties on <html> — the
// same element that carries `.dark-mode` — so the token layer's dark flip can't
// override them (inline wins on that element). Re-point the brand *accent* vars
// (text / fg / icon / border / outline / ring) to brand-300 on `body`, a
// (text / fg / icon / border / outline / ring) to brand-400 on `body`, a
// descendant of <html>, which wins over the html-inline value for the whole app
// subtree (incl. antd portals). Solid-fill vars (*-solid, bg-brand-*, the raw
// ramp) are intentionally left so buttons/badges keep the brand-600 fill. Light
// is untouched, so a white-label custom brand still tints accents in light.
.dark-mode body {
--tw-color-text-brand-secondary: var(--om-color-brand-300);
--tw-text-color-brand-secondary: var(--om-color-brand-300);
--tw-color-text-brand-tertiary: var(--om-color-brand-300);
--tw-text-color-brand-tertiary: var(--om-color-brand-300);
--tw-color-text-brand-tertiary_alt: var(--om-color-brand-300);
--tw-text-color-brand-tertiary_alt: var(--om-color-brand-300);
--tw-color-fg-brand-primary: var(--om-color-brand-300);
--tw-color-fg-brand-primary_alt: var(--om-color-brand-300);
--tw-color-fg-brand-secondary: var(--om-color-brand-300);
--tw-color-fg-brand-secondary_alt: var(--om-color-brand-300);
--tw-color-fg-brand-secondary_hover: var(--om-color-brand-300);
--tw-color-icon-fg-brand: var(--om-color-brand-300);
--tw-color-featured-icon-light-fg-brand: var(--om-color-brand-300);
--tw-color-border-brand: var(--om-color-brand-300);
--tw-color-border-brand_alt: var(--om-color-brand-300);
--tw-border-color-brand: var(--om-color-brand-300);
--tw-border-color-brand_alt: var(--om-color-brand-300);
--tw-background-color-border-brand: var(--om-color-brand-300);
--tw-background-color-border-brand_alt: var(--om-color-brand-300);
--tw-outline-color-brand: var(--om-color-brand-300);
--tw-outline-color-brand_alt: var(--om-color-brand-300);
--tw-ring-color-brand: var(--om-color-brand-300);
--tw-ring-color-brand_alt: var(--om-color-brand-300);
--tw-color-text-brand-secondary: var(--tw-color-brand-400);
--tw-text-color-brand-secondary: var(--tw-color-brand-400);
--tw-color-text-brand-tertiary: var(--tw-color-brand-400);
--tw-text-color-brand-tertiary: var(--tw-color-brand-400);
--tw-color-text-brand-tertiary_alt: var(--tw-color-brand-400);
--tw-text-color-brand-tertiary_alt: var(--tw-color-brand-400);
--tw-color-fg-brand-primary: var(--tw-color-brand-400);
--tw-color-fg-brand-primary_alt: var(--tw-color-brand-400);
--tw-color-fg-brand-secondary: var(--tw-color-brand-400);
--tw-color-fg-brand-secondary_alt: var(--tw-color-brand-400);
--tw-color-fg-brand-secondary_hover: var(--tw-color-brand-400);
--tw-color-icon-fg-brand: var(--tw-color-brand-400);
--tw-color-featured-icon-light-fg-brand: var(--tw-color-brand-400);
--tw-color-border-brand: var(--tw-color-brand-400);
--tw-color-border-brand_alt: var(--tw-color-brand-400);
--tw-border-color-brand: var(--tw-color-brand-400);
--tw-border-color-brand_alt: var(--tw-color-brand-400);
--tw-background-color-border-brand: var(--tw-color-brand-400);
--tw-background-color-border-brand_alt: var(--tw-color-brand-400);
--tw-outline-color-brand: var(--tw-color-brand-400);
--tw-outline-color-brand_alt: var(--tw-color-brand-400);
--tw-ring-color-brand: var(--tw-color-brand-400);
--tw-ring-color-brand_alt: var(--tw-color-brand-400);
// Status text: the configured error / warning / success colours are injected
// the same way, as light -600 shades, and stayed below AA on dark surfaces.
// Text only; the solid fills keep the configured colour.
Expand Down Expand Up @@ -954,7 +958,7 @@ a[href].link-text-grey,
// Carousel active dot draws with --ant-primary-color (#1570ef) via a `!important`
// rule in temp.css; match !important to win and re-tint in dark.
.dark-mode .slick-dots li.slick-active button:before {
color: var(--om-color-brand-300) !important;
color: var(--tw-color-brand-400) !important;
}

// Dark-mode canvas chrome for every reactflow surface (lineage, workflow,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ a {
:where(.dark-mode) .tw\:text-brand-600,
:where(.dark-mode) .tw\:text-brand-700,
:where(.dark-mode) .tw\:text-blue-700 {
color: var(--om-color-brand-300);
color: var(--tw-color-brand-400);
}

/* Dark: brand-tint surfaces darken so the flipped brand-300 text stays readable
/* Dark: brand-tint surfaces darken so the flipped brand-400 text stays readable
* on them. Dark-scoped, so the utility token (which flips to brand-950/900 under
* .dark-mode) is safe to reference directly here. */
:where(.dark-mode) .tw\:bg-brand-50 {
Expand Down
Loading