The dashboard's dark theme left a chunk of the bundled provider logos
unreadable: 25 of them are pure black marks on a transparent background,
so on a near-black surface they disappeared entirely, and another 11 are
dark multicolor marks drawn for a white page.
This adds the seam the rest of the work hangs off: a per-asset treatment
manifest in logoTreatments.ts, and a Logo component that applies the
treatment it names. Two treatments exist today. "invert" flattens a mark
to solid white with brightness(0) invert(1), which is what the vendor's
own white mark looks like for a pure-black transparent glyph. "plate"
puts a white surface behind the mark so it reads exactly as it does on a
light page.
Both are dark-only, and only assets named in the manifest are touched, so
light mode is unchanged and the other 96 bundled logos keep rendering
byte for byte as they do today. The className an untreated logo receives
is passed through verbatim rather than routed through cn(), so even the
class string is unchanged.
The split between invert and plate was measured per asset, not guessed:
luminance, saturation and alpha coverage sampled off a canvas render. Two
assets that look monochrome, aiml_api and repelloai, carry a light
knockout inside dark artwork, so inversion would flatten the knockout
into the mark and erase it. They get a plate instead, and a test pins
that.
Six assets whose artwork is an opaque dark box (aim_logo, aim_security,
deepgram, jina, lakeraai, openmeter) are deliberately left untreated. A
plate cannot show through an opaque image, so the only honest fix for
them is a replacement asset.
Regression LIT-6143 (the policy Flow Builder painting its guardrail dropdown
underneath a position: fixed shell at z-index 1000) was one instance of a
class of bug: pages picking their own z-index numbers above the portalled
popup layer. This removes the class.
- globals.css defines the only z-index values in the dashboard as Tailwind
utilities: z-raised, z-chrome, z-sticky, z-sticky-pinned, z-floating,
z-overlay, z-popup; every numeric, arbitrary and inline z-index across
src is migrated onto them and tailwind-merge learns the tokens
- new local/no-ad-hoc-z-index ESLint rule bans z-<n>, z-[...], z-(...) and
inline zIndex everywhere, and reserves z-popup for the portalled
primitives in components/ui (and the DataTable menus)
- the Flow Builder renders in the dashboard content area instead of as a
fixed full-screen overlay, so it has no stacking level at all
- the guardrail content-filter Add keyword / Add pattern / Custom pattern
dialogs drop the leftover z-[1100] (renamed from ABOVE_ANTD_MODAL when
antd was removed) that hid their own Action select and pattern combobox
behind the dialog, the same bug as LIT-6143
* fix(ui): give status colours a readable foreground and drop the muted 70% step
The four status tokens are lightened for dark mode, which is correct when they are used as text
and wrong for the 27 places that use them as a background under `text-white`. Every one of those
passes in light and fails in dark: success 1.78:1, warning 1.72:1, info 2.64:1, destructive
2.89:1. The cause is not 27 authoring mistakes, it is that no `--success-foreground` and no
sibling ever existed, so `text-white` was the only thing available to write. Adding the four
companions and registering them in `@theme` makes the correct pairing expressible, and the call
sites then read `text-success-foreground` instead of a hardcoded colour. Dark lands at 9.98, 10.31,
6.72 and 6.15.
Light is deliberately pure white rather than the near-white the other `-foreground` tokens use, so
the four ratios stay at exactly the 4.95, 5.03, 5.25 and 4.77 they are today instead of drifting
down to 4.73, 4.81, 5.02 and 4.56.
Separately `text-muted-foreground/70` measures 2.75:1 on a light page and 4.31:1 on a dark one,
so the same 183 occurrences fail AA in light and sit under it in dark. Dropping the opacity step
takes them to 4.84:1 and 7.34:1. The identical step on the placeholder base rule goes with them,
which is what put every input's placeholder at 2.75:1 in light.
Residual, not addressed here: `text-muted-foreground` over `bg-muted` reaches 4.39:1 in light,
still short of 4.5. Closing that needs `--muted-foreground` itself to move, which changes every
secondary label in the product and is a design call rather than a defect fix.
* fix(ui): finish the status-foreground swap and repoint no-op muted hovers
Four sites still forced text-white on a status fill because the class sat on
a child element rather than on the filled container, so the earlier sweep did
not reach them. The compliance quick-test bubble was worse: it paired bg-info
with text-success-foreground and its paragraph kept text-white on top, so the
dark-theme contrast the PR set out to fix was still reachable there
Dropping the /70 step also turned 21 existing "text-muted-foreground/70
hover:text-muted-foreground" pairs into hovers that change nothing, which
local/no-noop-hover-variant flags as an error. The affordance was "brighten on
hover", so these now hover to text-foreground, matching the 74 places that
already spell it that way
The remaining churn is prettier reflowing the handful of lines whose length
changed, since the token names are longer than text-white
* fix(ui): let the approve/reject confirm button pick the token its fill uses
Both submission review dialogs put text-success-foreground on the shared
button class while the fill below it swings between bg-success for Approve and
bg-destructive for Reject, so Reject drew a success token over a destructive
fill. The two tokens resolve to the same value today, so nothing looks wrong,
but the pairing only holds by coincidence and would break the moment either
token moves. Moving the token into the branch makes it track the fill
* fix(ui): drop the last 70% placeholders, still live on the legacy utility
Four inputs spell their placeholder colour with Tailwind's older
placeholder-<colour> utility rather than placeholder:text-<colour>, so the
sweep that dropped the 70% step passed over them. Tailwind 4.3 still emits
that utility, and utilities sit after base in the layer order, so those four
kept overriding the new input::placeholder rule and kept rendering at 70% in
dark mode, which is the contrast failure this PR set out to close
They now spell it the same way as the three placeholders the PR already
converted, which both removes the step and settles on one spelling
* fix(ui): make dark-mode form controls visible
Two dark-mode defects left form controls without any visual boundary or fill.
`--input` and `--border` share one value in `.dark`, oklch(0.309), which resolves to
rgb(48,48,48). Against `--background` (33) that is a 15-step stroke, and against `--popover` (42)
it collapses to 6 steps out of 255, so a control inside any dialog is effectively undrawn. The
controls also use `bg-transparent`, so there is no fill cue either and only the placeholder text
renders. Measured 1.09:1 against the dialog surface where WCAG 1.4.11 asks for 3.0:1 on the
boundary of a user interface component. Splitting `--input` off at oklch(0.56) restores 3.07:1
without touching `--border`, which stays where it is because it draws decorative separators rather
than control boundaries. 91 controls across 19 routes were measured at the collapsed value, every
one with an identical stroke and surface, so a single token covers all of them.
Separately, `@tailwindcss/forms` paints a white fill on every bare control. The block above
already neutralises that for `combobox-chip-input`, but its audit covered `components/ui` only,
and hand-rolled controls elsewhere still render white on a dark page: typed text lands at 1.11:1
and native selects at 2.19:1 on `/model-hub-table`, `/playground`, `/guardrails`, `/mcp-servers`
and `/models-and-endpoints`. Tracking `--background` fixes those at 14.51:1 and 7.34:1.
Light mode is unchanged by both. The token edit is scoped to `.dark`, and `--background` in
`:root` is the same white the plugin was already painting, verified control-by-control on a dev
server: backgrounds stay rgb(255,255,255) and ratios stay 20.13:1 and 4.84:1.
* fix(ui): keep the combobox chip input transparent under the bare-control fill
The new base rule matched at (0,2,1) while the combobox chip-input override
sits at (0,1,0), so ComboboxChipsInput lost its transparent background and
painted an opaque page-colored rectangle inside the chips container, which
carries its own bg-transparent / dark:bg-input/30 fill.
Folding the exclusions into one :not() list adds the chip input and drops the
selector to (0,1,1). Every @tailwindcss/forms base selector is wrapped in
:where(), so it lands at (0,0,1); (0,1,1) still outweighs it and bare inputs,
textareas and selects keep the fill this PR gives them.
* refactor(ui): map hardcoded Tailwind palette classes onto semantic tokens
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.
* chore(ui): drop the one-shot color-token codemod script
Nothing in the dashboard renders antd any more, so the package and the
scaffolding around it can go. This removes `antd` and
`@ant-design/cssinjs` from package.json, deletes the global StyleProvider
the root layout wrapped every page in, drops the `antd` cascade layer and
the z-index override that lifted Base UI popups over an antd Modal, and
retires the lint rules that policed antd imports and antd class selectors
in tests.
Fifteen test files still carried `vi.mock("antd", ...)` factories for
components that stopped importing antd during the migration. They were
inert, and they resolve the real module, so they would have broken the
moment the package left node_modules.
The compatibility shims keep their behaviour and lose the antd name:
`antdRules`/`antdRequired` become `validatorRules`/`requiredRule`,
`isAntdUrl` becomes `isValidUrl`, and `ABOVE_ANTD_MODAL` becomes
`NESTED_DIALOG_LAYER`. Comments that explain why a contract looks the way
it does still name antd, because that history is the reason.
The last tremor component import left the dashboard when the primitive
sweep merged, so the package, its v3 compatibility shim, its @theme token
block and the palette safelist it needed at runtime all have no consumer.
Removing the safelist is what shrinks the shipped stylesheet: tremor built
class names at runtime, so Tailwind had to emit every bg/text/border/ring/
stroke/fill utility across 22 palettes and 11 shades in case one was used.
Nothing in the app constructs a class name that way any more, so the
scanner finds every utility on its own.
The date-fns overrides pin also goes. It only existed because tremor and
react-day-picker@8 peered on date-fns 3 while Base UI wanted 4, and the
lockfile still resolves a single hoisted 4.4.0 without it.
The dashboard had no shared tokens for non-destructive status colours, so
components reached for raw Tailwind shades instead. Add --success, --warning
and --info alongside the existing --destructive, in both :root and .dark, and
register them in @theme inline so the usual utilities resolve.
Light values are picked for legibility as foreground text rather than by
copying a fixed shade number. Tailwind's ramps are not perceptually aligned
across hues, so amber-600 and green-600 sit at 66.6% and 62.7% lightness and
fail WCAG AA on white (3.19:1 and 3.22:1). green-700, amber-700 and blue-600
land at 52.7%, 55.5% and 54.6%, the same band as --destructive at 57.7%, and
clear AA. Dark mode uses the -400 shades, matching --destructive.
The .dark values are populated even though nothing can apply that class yet.
They are the artifact the later theme switch work will turn on.
Alert moves its info and warning variants onto the tokens. The tint is /5
rather than /10 because /10 drops both below AA. The error variant keeps its
existing shades: it involves no new token, and its current 9.21:1 is better
than anything the token form would give it.
Create Key offered two options labelled "Never resets" in the Reset Budget
dropdown. BudgetDurationDropdown renders its unset item using the caller's
placeholder, and create_key_button passed placeholder="Never resets" alongside
showNeverResets, so the omit option and the explicit-null option looked
identical while behaving differently. An omitted budget_duration picks up
default_key_generate_params and the linked budget tier's schedule, whereas the
"none" sentinel is converted to an explicit null and truly never resets. The
unset item now reads "Not set", matching getBudgetDurationLabel, and the
create-key test mock passes the placeholder through so a future collision fails
the suite
The rest is migration cleanup found during manual QA. The models and endpoints
tab strip hides its scrollbar and fades at the right edge using a vendored copy
of the shadcn scroll-fade utility, keeping the CLI package out of the build.
globals.css neutralises the @tailwindcss/forms resting-state rules for
combobox-chip-input, which lets twelve call sites drop the same copy-pasted
className workaround. Guardrails moves to the line tab variant and stops
clipping its textarea focus ring, the log drawer JSON tree takes the app
background, the audit log empty state centres, the caching page selects no
longer stretch to the row height, the usage page team filter shares its row
with the Export button through a new filterSlot prop, and the cost optimization
and vector store tab strips drop their leftover full-width divider
Two behaviour differences the port introduced, both found in browser QA
The antd Select matched a prebuilt pattern on its display_name and its
internal name; a Base UI Combobox only searches itemToStringLabel, so
queries like "amex" and "sg" stopped matching. Restore the second field
with a filter predicate on the Root, covered by a regression test that
searches on a token the visible label does not contain
Base UI portals a popup into a positioner whose "isolate z-50" is fixed
in the primitive, so inside an antd Modal at z-index 1000 the options
were visible but not clickable. antd hid this because its own dropdowns
and tooltips already sat above its Modal. The positioner is not reachable
from the call site, so this needs one app-wide rule keyed on an antd
modal being present, and it becomes deletable when the last one goes
The prompt editor is a base-ui Dialog at z-index 50. The create form houses it in
the same base-ui Dialog, so it stacks on top, but the edit form was an antd Modal
whose portal computes to z-index 1000, so the editor opened underneath it and was
neither readable nor clickable.
Move the edit form onto the Dialog the create form already uses, which puts the
whole nesting chain in one overlay layer. A dialog opened from inside another
dialog now reads as a drill-down rather than a stack: base-ui stamps
data-nested-dialog-open on the parent while a child is open, so the parent steps
aside instead of showing its own edges around a differently sized child.
* fix(ui): render the sidebar scrollbar with shadcn ScrollArea
The sidebar navigation scrolled through a native overflow-y-auto container, so the browser drew its default scrollbar. It now scrolls through the shadcn ScrollArea primitive so the thumb matches the rest of the dashboard
Switching to ScrollArea surfaced a latent styling gap. The Base UI scroll-area, tabs, and separator primitives rely on data-horizontal and data-vertical Tailwind variants that resolve to [data-orientation="horizontal"] and [data-orientation="vertical"], and those variants ship in shadcn's shared stylesheet. The project never imported it, so the classes matched nothing and the scrollbar collapsed to zero width. This adds shadcn as a devDependency and imports shadcn/tailwind.css, which also repairs the vertical tabs and separator styling. See shadcn-ui/ui#9196 for the upstream tracking issue
* refactor(ui): inline the Base UI data-* variants, drop the shadcn dep
The earlier fix imported shadcn/tailwind.css through the shadcn devDependency, which pulled 219 packages and tied the CSS build to shadcn's package exports (an open Turbopack-breaking bug, shadcn-ui/ui#10931). shadcn's model is that we own the components, so the custom variants those components depend on belong in our own stylesheet rather than a runtime dependency. This inlines the nine data-* custom variants and the no-scrollbar utility that the Base UI primitives reference into globals.css, and removes the shadcn package.
Follow-up to the full-height sidebar shell. Replaces hand-rolled markup in the sidebar, top bar, and account/usage docks with shadcn primitives so the surface reuses shared components instead of one-off elements
Adds three Base-UI-native primitives that follow the existing conventions (cva + data-slot, no Radix): Meter, Avatar, and Breadcrumb. Badge gains a render prop via Base UI useRender so it can render as an anchor, which activates the variant's existing [a&] styles
Wires the redesigned surface onto them: the version tag is an outline Badge linking to the release notes, the breadcrumb uses the Breadcrumb primitive, the Docs link is a ghost Button, the account initials use Avatar, and the Enterprise usage card uses Collapsible for its open state and Meter for the seat and team gauges. Meter is the correct element for a used-of-total measurement and Base UI ships it natively, so it replaces the hand-rolled bars
Sets the sidebar background to pure white by pointing the --sidebar token at oklch(1 0 0) in light mode, which keeps SidebarUsageCard consistent since it shares the token; the dark block is left untouched
Also repairs layout.test.tsx, which the shell refactor had left red: the dashboard shell now renders DashboardHeader in place of the old navbar, so the test mocks DashboardHeader and asserts on it instead of the navbar the layout no longer mounts
* feat(ui): shadcn migration foundation: Tailwind v4, shadcn init, antd cascade fix
Upgrade the dashboard from Tailwind v3 to v4 with CSS-first config: the
official upgrade codemod renamed utilities across 151 files, and
tailwind.config.js (plus the dead tailwind.config.ts) is replaced by
@theme tokens, @source globs, and @plugin directives in globals.css. The
Tremor safelist becomes @source inline patterns and the legacy tremor
theme tokens carry over verbatim. ui_colors.json was build-time only and
fed the dying Tremor palette, so its brand values are inlined and the
file removed; runtime theming replaces that path next.
shadcn is initialized with a hand-authored components.json (rsc,
cssVariables, baseColor gray) pointing utils at the existing
lib/cva.config.ts, which now exports cn (cva beta cx + twMerge) instead
of adding class-variance-authority as a second variant library. The two
ad-hoc cn helpers fold into it. Button lands as the canary primitive,
adapted to cva beta and React 18 forwardRef, with tests covering the
variant, twMerge, asChild, and ref seams. --radius is 0.5rem so the
shadcn radius scale reproduces Tailwind defaults and legacy rounded-*
classes render unchanged.
antd v5 emits unlayered CSS-in-JS that would beat every layered v4
utility, so AntdGlobalProvider now wraps the app in StyleProvider layer
and ConfigProvider cssVar, and globals.css declares
@layer theme, base, antd, components, utilities. antd wins over
preflight but yields to utilities, which is what lets migrated shadcn
pages coexist with legacy antd pages. Preflight stays global with the
three v3 behaviors pinned (default border color, button cursor,
placeholder color).
* fix(ui): restore tremor opacity tints removed by tailwind v4
Tailwind v4 removed the *-opacity-* utilities, but the precompiled
@tremor/react dist still composes them with shade-500 palette classes
(bg-opacity-10 over bg-<color>-500 etc.), so Badge, BadgeDelta, Callout,
light Icon and Button, BarList, and ProgressBar lost their tints and
rendered solid 500-shade fills. Adversarial review caught it; the
original smoke pages only exercised antd Tags.
tremor-v3-compat.css restores exactly the pairs tremor emits: for each
of the 22 safelisted colors, bg-opacity-{10,20,40}, hover/group-hover
bg-opacity-{20,30}, and ring-opacity-{20,40} against the -500 shade,
via color-mix into the utilities layer. Tremor's colorPalette maps both
background and iconRing to 500, so the -500 pairing covers every
composition in the dist; dark: variants are inert until dark mode ships.
The shim dies with @tremor/react at the end of the migration.
The upgrade codemod also missed two hand-rolled modal scrims using
bg-black bg-opacity-{30,50} (solid black under v4); now bg-black/30 and
bg-black/50. Removed the docker/build_admin_ui.sh copy of
enterprise_colors.json into the deleted ui_colors.json; that build-time
rebrand path is retired and its runtime replacement lands with the
theming phase.
* fix(ui): pair ring-opacity-40 with shade 300 in tremor compat shim
Tremor's colorPalette maps ring to shade 300, and the only consumer of
ring-opacity-40 (Icon variant outlined) composes it with that shade,
so the shade-500 rows were dead and outlined icon rings would render
at full opacity. Latent today (no dashboard usage of the outlined
variant); caught by adversarial review. ring-opacity-20 stays at 500
(iconRing), matching Badge and BadgeDelta.
- Add premium/standard badge with crown icon next to user ID
- Remove colons and improve visual hierarchy with icons
- Add tooltips for user status (Premium User / Upgrade message)
- Enhance spacing and typography for better readability
- Update dropdown styling with proper hover states
* fix(.globals.css): revert .md hard set
caused regression in invitation link display (and possibly other places)
* Fix keys not showing on refresh for internal users (#8312)
* [Bug] UI: Newly created key does not display on the View Key Page (#8039)
- Fixed issue where all keys appeared blank for admin users.
- Implemented filtering of data via team settings to ensure all keys are displayed correctly.
* Fix:
- Updated the validator to allow model editing when `keyTeam.team_alias === "Default Team"`.
- Ensured other teams still follow the original validation rules.
* - added some classes in global.css
- added text wrap in output of request,response and metadata in index.tsx
- fixed styles of table in table.tsx
* - added full payload when we open single log entry
- added Combined Info Card in index.tsx
* fix: keys not showing on refresh for internal user
* fixed user id passed as null when keyuser is you (#8271)
* fix(user_dashboard.tsx): ensure non admin can't view other keys
---------
Co-authored-by: Taha Ali <123803932+tahaali-dev@users.noreply.github.com>
Co-authored-by: Jaswanth Karani <karani.jaswanth@gmail.com>
* Litellm UI fixes 8123 v2 (#8208)
* [Bug] UI: Newly created key does not display on the View Key Page (#8039)
- Fixed issue where all keys appeared blank for admin users.
- Implemented filtering of data via team settings to ensure all keys are displayed correctly.
* Fix:
- Updated the validator to allow model editing when `keyTeam.team_alias === "Default Team"`.
- Ensured other teams still follow the original validation rules.
* - added some classes in global.css
- added text wrap in output of request,response and metadata in index.tsx
- fixed styles of table in table.tsx
* Enhance Request Viewer UI with Detailed Log Information
- Added a comprehensive "Request Details" card displaying key log.
- Introduced a new "Request Tags" section
- Removed commented-out buttons for expanded/JSON views
- Improved overall layout and readability of log information
* only include Request Details
---------
Co-authored-by: Taha Ali <123803932+tahaali-dev@users.noreply.github.com>