Skip to content

refactor(ui): map hardcoded Tailwind palette classes onto semantic tokens - #37576

Merged
yuneng-berri merged 2 commits into
litellm_internal_stagingfrom
litellm_ui_dark_mode_color_tokens
Aug 20, 2026
Merged

refactor(ui): map hardcoded Tailwind palette classes onto semantic tokens#37576
yuneng-berri merged 2 commits into
litellm_internal_stagingfrom
litellm_ui_dark_mode_color_tokens

Conversation

@ryan-crabbe-berri

@ryan-crabbe-berri ryan-crabbe-berri commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Dashboard colors are hardcoded palette classes, not tokens
  • The .dark block exists but can never take effect
  • Sidebar reads as a card floating on near-black

How it solves it:

  • Codemod rewrites 3,232 palette utilities onto semantic tokens
  • Neutrals, red, green, amber, blue collapse into token families
  • Retunes the dark ramp neutral, with the sidebar recessed

User Flow

Before: a proxy admin who prefers dark interfaces has no way to get one, and would see a broken page even if they forced it

  1. They open https://litellm-domain/ui/?page=api-keys and get the light dashboard
  2. There is no theme control anywhere on the page
  3. If they force the page into dark from browser devtools, most of the screen stays light: key names, table borders, filter chips and status badges keep their fixed light-mode colors

After: the same forced-dark page renders as a coherent dark interface, so a theme control can be added on top of it

  1. They open https://litellm-domain/ui/?page=api-keys and get the same light dashboard, pixel for pixel
  2. There is still no theme control, so nothing changes for anyone who does not go looking
  3. If they force the page into dark from devtools, the whole page follows: canvas, sidebar, table rows, dividers, badges and muted text all shift together

Relevant issues

Linear ticket

Pre-Submission checklist

  • I have added meaningful tests
  • The handful of test files covering my change pass locally
  • My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review

Screenshots / Proof of Fix

Before (7b574b9)

Light mode, unchanged baseline

  1. Open http://localhost:3000/?page=api-keys and screenshot the Virtual Keys table

Dark mode forced from devtools

  1. In devtools, select the <html> element and add class="dark"
  2. Screenshot: the sidebar goes dark but the table body, key names, filter chips and status badges stay light

After (922f065)

Light mode, unchanged baseline

  1. Open http://localhost:3000/?page=api-keys and screenshot the same table
  2. It should be indistinguishable from the Before shot

Dark mode forced from devtools

  1. In devtools, select the <html> element and add class="dark"
  2. Screenshot: canvas #212121, sidebar #131313, row dividers #303030, all text and badges legible

Type

🧹 Refactoring

Caveats (if any)

  • No new tests: mechanical rewrite, 12 touched suites pass
  • An eslint rule banning new palette classes is worth adding
  • No theme toggle yet; dark mode stays inert until one lands
  • 210 palette utilities left by hand: purple, indigo, gradients
  • ~250 hardcoded hex values in inline styles untouched
  • Playground code blocks still use a light-only Prism theme
  • @tailwindcss/forms still paints bare inputs white
  • Semantic colors keep shadcn's saturation, brighter than the neutrals

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

…kens

The dashboard painted itself with literal palette utilities (text-gray-500,
bg-blue-50, border-red-200) that resolve to one fixed color regardless of
theme, so the shadcn token layer and its .dark block could never take effect.

A codemod (scripts/codemod-color-tokens.mjs) rewrites 3,232 of those across
254 files onto the existing token scale: neutrals become foreground /
muted-foreground / muted / border / card, and red, green, amber and blue
collapse into destructive, success, warning and info, with the pale -50 to
-300 tints expressed as opacity modifiers on the same token. Hover and focus
variants map to accent so they lift rather than recess. 210 now-redundant
dark: variants are dropped since the tokens carry both modes.

The .dark palette is retuned to a neutral gray ramp with the sidebar recessed
below the content canvas, replacing the blue-tinted shadcn default where the
sidebar read as a full-height card floating on a near-black page.

Nothing sets the .dark class yet, so light mode is unchanged and dark mode
stays inert until a theme toggle lands.
@greptile-apps

greptile-apps Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Too many files changed for review (257 files, 100 file limit).

Bypass the limit by tagging @greptile-apps to review.

@yuneng-berri
yuneng-berri merged commit 57b328f into litellm_internal_staging Aug 20, 2026
66 of 67 checks passed
@yuneng-berri
yuneng-berri deleted the litellm_ui_dark_mode_color_tokens branch August 20, 2026 05:11
yuneng-berri added a commit that referenced this pull request Aug 20, 2026
…ken migration (#37579)

* fix(ui): restore hover feedback and dark-mode variants lost in the token migration

PR #37576 mapped hardcoded Tailwind palette classes onto semantic tokens. Two-tone
hover pairs collapsed onto a single token, so 116 hover utilities across 49 files
became identical to their base class and produced no visible feedback, and in seven
files a dark: variant was dropped while its hardcoded light partner survived, leaving
those elements stuck light in dark mode.

Hover states now follow the alpha-step idiom the shadcn primitives already use
(hover:bg-primary/80, hover:bg-success/20): a duplicated hover:text-X or hover:bg-X
becomes /80, hover:border-border becomes hover:border-ring, and a duplicate is
dropped where another hover utility on the element already carries the change. One
transition-colors that no longer animated anything is removed.

For the dark-mode gaps, indigo maps onto info and amber onto warning. There is no
purple token in globals.css, so the purple sites keep their palette classes and get
their dark: partner back.

* fix(ui): add an eslint rule that fails a hover: utility identical to its base

The token migration collapsed two-tone hover pairs by hand, so nothing catches
the next one. `local/no-noop-hover-variant` reads every string literal and
template chunk and errors when a `hover:X` sits alongside a bare `X`, which is
exactly the shape that renders no hover feedback. It ships at error with no
suppression baseline, so the eleven sites that already carried a dead hover
before the migration are fixed here too.

The rule reads one class string at a time, so a base class supplied by a
different ternary branch than its hover partner is left alone: a selected row
whose resting colour already matches its hover colour is deliberate, not a bug.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants