From c794dcb91d449a825106b0f8f3c33ffd93b2a4d5 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 20 Aug 2026 10:58:32 -0700 Subject: [PATCH] fix(ui): give status colours a readable foreground and drop the muted 70% step (#37649) * 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- utility rather than placeholder:text-, 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 --- .../_components/CacheLeakageCard.tsx | 4 +- .../_components/TierTurnsChart.tsx | 2 +- .../_components/cost_tracking_settings.tsx | 4 +- .../pricing_calculator/multi_cost_results.tsx | 12 +-- .../_components/provider_margin_table.tsx | 2 +- .../_components/GuardrailsOverview.tsx | 2 +- .../_components/TeamGuardrailsTab.tsx | 12 +-- .../_components/add_guardrail_form.tsx | 2 +- .../_components/EnvVarsSection.tsx | 2 +- .../_components/MCPSubmissionsTab.tsx | 44 ++++++----- .../mcp-servers/_components/mcp_connect.tsx | 2 +- .../components/chat_ui/A2AMetrics.tsx | 8 +- .../chat_ui/AdditionalModelSettings.tsx | 10 +-- .../components/chat_ui/AgentBuilderView.tsx | 2 +- .../components/chat_ui/ChatImageUpload.tsx | 2 +- .../playground/components/chat_ui/ChatUI.tsx | 16 ++-- .../chat_ui/CodeInterpreterOutput.tsx | 4 +- .../chat_ui/CodeInterpreterTool.tsx | 2 +- .../components/chat_ui/FilePreviewCard.tsx | 4 +- .../components/chat_ui/RealtimePlayground.tsx | 10 ++- .../chat_ui/ResponsesImageUpload.tsx | 2 +- .../chat_ui/SearchResultsDisplay.tsx | 6 +- .../components/chat_ui/SessionManagement.tsx | 2 +- .../components/compareUI/CompareUI.tsx | 4 +- .../compareUI/components/ComparisonPanel.tsx | 2 +- .../components/complianceUI/ComplianceUI.tsx | 76 +++++++++---------- .../_components/ai_suggestion_modal.tsx | 12 +-- .../prompts/_components/prompt_info.tsx | 2 +- .../_components/general_settings.tsx | 2 +- .../components/EndpointUsageTable.tsx | 2 +- .../components/EntityUsage/EntityUsage.tsx | 6 +- .../EntityUsage/SpendByProvider.tsx | 4 +- .../components/UsageAIChatPanel.tsx | 10 +-- .../_components/components/UsagePageView.tsx | 14 ++-- ui/litellm-dashboard/src/app/globals.css | 14 +++- .../AIHub/UsefulLinksManagement.tsx | 4 +- .../GuardrailsMonitor/LogViewer.tsx | 6 +- .../GuardrailsMonitor/MetricCard.tsx | 2 +- .../src/components/HelpLink.tsx | 4 +- .../Navbar/UserDropdown/UserDropdown.tsx | 2 +- .../SSOSettings/RoleMappings.tsx | 4 +- .../Fallbacks/FallbackGroupConfig.tsx | 6 +- .../RouterSettings/Fallbacks/Fallbacks.tsx | 2 +- .../components/EntityUsage/TopKeyView.tsx | 10 +-- .../VirtualKeysPage/keyTableColumns.tsx | 4 +- .../src/components/activity_metrics.tsx | 2 +- .../add_model/ClassificationMethodConfig.tsx | 6 +- .../add_model/ComplexityRouterConfig.tsx | 6 +- .../add_model/EscalationKeywords.tsx | 2 +- .../components/add_model/KeywordTierRules.tsx | 2 +- .../add_model/SemanticKeywordMatching.tsx | 2 +- .../components/bulk_create_users_button.tsx | 14 +++- .../src/components/chat/ConversationList.tsx | 2 +- .../components/chat_ui/MCPEventsDisplay.tsx | 4 +- .../common_components/AutoRotationView.tsx | 4 +- .../RouterSettingsSummary.tsx | 4 +- .../BudgetFallbacksEditor.tsx | 4 +- .../src/components/logging_settings_view.tsx | 4 +- .../mcp_tools/ByokCredentialModal.tsx | 8 +- .../HealthChecksTableColumns.tsx | 2 +- .../components/model_group_alias_settings.tsx | 2 +- .../permissions/AgentPermissions.tsx | 2 +- .../permissions/MCPServerPermissions.tsx | 10 +-- .../permissions/VectorStorePermissions.tsx | 2 +- .../src/components/public_model_hub.tsx | 16 ++-- .../shared/advanced_date_picker.tsx | 2 +- .../src/components/shared/chart_loader.tsx | 2 +- .../src/components/team/TeamInfo.tsx | 8 +- .../components/templates/KeyInfoHeader.tsx | 2 +- .../components/templates/key_info_view.tsx | 4 +- .../GuardrailViewer/CompliancePanel.tsx | 4 +- .../GuardrailViewer/GuardrailViewer.tsx | 4 +- .../LogDetailsDrawer/LogDetailsDrawer.tsx | 2 +- .../view_logs/RequestLogsTableColumns.tsx | 4 +- .../src/components/view_logs/TypeBadges.tsx | 2 +- 75 files changed, 248 insertions(+), 232 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CacheLeakageCard.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CacheLeakageCard.tsx index c0b4150b4f4..ca47b71725d 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CacheLeakageCard.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CacheLeakageCard.tsx @@ -40,7 +40,7 @@ const compareRows = (a: CacheLeakageRow, b: CacheLeakageRow, sort: SortState): n const InfoTooltip = ({ info }: { info: string }) => ( }> - + {info} @@ -72,7 +72,7 @@ const SortableHead = ({ className="inline-flex items-center gap-1 font-medium hover:text-foreground" > {label} - + diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/TierTurnsChart.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/TierTurnsChart.tsx index 5b9b8563baa..e55ebc07656 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/TierTurnsChart.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/TierTurnsChart.tsx @@ -133,7 +133,7 @@ const TierTurnsChart: React.FC = ({ view, autoRouters }) => {slice.tier} {Math.round((100 * slice.turns) / total).toLocaleString()}%

{slice.models.length > 0 && ( -

{slice.models.join(", ")}

+

{slice.models.join(", ")}

)} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/_components/cost_tracking_settings.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/_components/cost_tracking_settings.tsx index fa4e8daf9d0..3fce928ed67 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/_components/cost_tracking_settings.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/_components/cost_tracking_settings.tsx @@ -223,7 +223,7 @@ const CostTrackingSettings: React.FC = ({ userID, use ) : (
= ({ userID, use ) : (
+
Token Pricing:{" "} {result.input_cost_per_token && ( Input ${formatNumberWithCommas(result.input_cost_per_token * 1_000_000, 2)}/1M @@ -294,17 +294,17 @@ const MultiCostResults: React.FC = ({ multiResult, timePe {record.error ? ( - - + - ) : ( {formatCost(record.cost_per_request)} )} {record.error ? ( - - + - ) : ( 0 ? "text-warning" : "text-muted-foreground/70"}`} + className={`font-mono text-sm ${(record.margin_cost_per_request ?? 0) > 0 ? "text-warning" : "text-muted-foreground"}`} > {formatCost(record.margin_cost_per_request)} @@ -312,7 +312,7 @@ const MultiCostResults: React.FC = ({ multiResult, timePe {record.error ? ( - - + - ) : ( {formatCost(periodCost)} )} @@ -325,7 +325,7 @@ const MultiCostResults: React.FC = ({ multiResult, timePe aria-expanded={isExpanded} aria-label={`${isExpanded ? "Hide" : "Show"} cost breakdown for ${record.model}`} onClick={() => toggleExpanded(record.id)} - className="text-muted-foreground/70 hover:text-muted-foreground" + className="text-muted-foreground hover:text-foreground" > {isExpanded ? : } diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/_components/provider_margin_table.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/_components/provider_margin_table.tsx index fa322d1b4d1..04823ac4aa0 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/_components/provider_margin_table.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/_components/provider_margin_table.tsx @@ -138,7 +138,7 @@ const ProviderMarginTable: React.FC = ({ autoFocus /> % - + + + $ 150 ? "text-destructive" : row.original.avgLatency > 50 diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/TeamGuardrailsTab.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/TeamGuardrailsTab.tsx index cc2d2051a24..c9de758f50d 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/TeamGuardrailsTab.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/TeamGuardrailsTab.tsx @@ -358,7 +358,7 @@ function GuardrailCard({ @@ -727,7 +727,7 @@ function DetailPanel({
@@ -218,7 +220,7 @@ function SubmissionRulesPanel({ requiredFields, onChange, onSave, isSaving }: Su
{field.label}
-
{field.description}
+
{field.description}
); @@ -235,7 +237,7 @@ function SubmissionRulesPanel({ requiredFields, onChange, onSave, isSaving }: Su await onSave(); setExpanded(false); }} - className="px-4 py-1.5 text-sm font-medium text-white bg-info hover:bg-info/80 disabled:opacity-50 rounded-md transition-colors" + className="px-4 py-1.5 text-sm font-medium text-info-foreground bg-info hover:bg-info/80 disabled:opacity-50 rounded-md transition-colors" > {isSaving ? "Saving…" : "Save Rules"} @@ -296,11 +298,11 @@ function MCPServerCard({ server, onApprove, onReject, requiredFields }: MCPServe )} {server.url && (
- + {server.url}
)} -
+
Transport: {server.transport ?? "sse"} @@ -322,7 +324,7 @@ function MCPServerCard({ server, onApprove, onReject, requiredFields }: MCPServe @@ -341,7 +343,7 @@ function MCPServerCard({ server, onApprove, onReject, requiredFields }: MCPServe @@ -367,7 +369,11 @@ function MCPServerCard({ server, onApprove, onReject, requiredFields }: MCPServe allPassed ? "bg-success" : "bg-destructive" }`} > - {allPassed ? : } + {allPassed ? ( + + ) : ( + + )}
@@ -383,7 +389,7 @@ function MCPServerCard({ server, onApprove, onReject, requiredFields }: MCPServe @@ -392,7 +398,7 @@ function MCPServerCard({ server, onApprove, onReject, requiredFields }: MCPServe @@ -567,13 +573,13 @@ export function MCPSubmissionsTab({ accessToken }: MCPSubmissionsTabProps) {
- + setSearch(e.target.value)} - className="w-full pl-9 pr-4 py-2 border border-border rounded-md text-sm text-foreground placeholder-muted-foreground/70 focus:outline-hidden focus:ring-1 focus:ring-ring focus:border-info" + className="w-full pl-9 pr-4 py-2 border border-border rounded-md text-sm text-foreground placeholder:text-muted-foreground focus:outline-hidden focus:ring-1 focus:ring-ring focus:border-info" />
setSearchPrompt(e.target.value)} placeholder="Search prompts..." - className="w-full border border-border rounded-lg pl-8 pr-3 py-1.5 text-xs placeholder:text-muted-foreground/70 focus:outline-hidden focus:ring-2 focus:ring-blue-500/20 focus:border-info" + className="w-full border border-border rounded-lg pl-8 pr-3 py-1.5 text-xs placeholder:text-muted-foreground focus:outline-hidden focus:ring-2 focus:ring-blue-500/20 focus:border-info" />
@@ -883,7 +883,7 @@ export default function ComplianceUI({ > Select All - · + · @@ -985,7 +985,7 @@ export default function ComplianceUI({ Required columns:{" "} prompt,{" "} expected_result{" "} - (fail or pass) + (fail or pass)

Optional columns:{" "} @@ -1049,14 +1049,14 @@ export default function ComplianceUI({ className="w-full flex items-center gap-2 px-3 py-2.5 text-left bg-muted hover:bg-accent transition-colors rounded-lg border border-border" > {isExpanded ? ( - + ) : ( - + )}

{fw.name} - {fwPromptCount} prompts + {fwPromptCount} prompts
{fwSelectedCount > 0 && ( @@ -1092,9 +1092,9 @@ export default function ComplianceUI({ className="w-full flex items-center gap-1.5 px-2.5 py-2 text-left hover:bg-accent transition-colors" > {isCatExpanded ? ( - + ) : ( - + )} {category.name} - + {category.prompts.length} {selectedInCat > 0 && ( @@ -1118,7 +1118,7 @@ export default function ComplianceUI({ {isCatExpanded && (
-

+

{category.description}

) : ( -

+

No policies or guardrails selected — select above to test against specific rules.

)} @@ -1245,7 +1245,7 @@ export default function ComplianceUI({
- +

Type a prompt below to quickly test it.

@@ -1254,10 +1254,10 @@ export default function ComplianceUI({ {quickTestMessages.map((msg) => (

{msg.type === "system" && ( @@ -1284,7 +1284,7 @@ export default function ComplianceUI({ {isQuickTesting && (

- +
)} @@ -1300,23 +1300,23 @@ export default function ComplianceUI({ onKeyDown={handleQuickTestKeyDown} placeholder="Enter text to test..." rows={3} - className="w-full px-3 pt-3 pb-1 text-sm text-foreground placeholder:text-muted-foreground/70 focus:outline-hidden resize-none" + className="w-full px-3 pt-3 pb-1 text-sm text-foreground placeholder:text-muted-foreground focus:outline-hidden resize-none" />
- + Press Enter to submit ·{" "} Shift+Enter for new line - {quickTestInput.length} + {quickTestInput.length}