diff --git a/litellm/proxy/logo_dark.png b/litellm/proxy/logo_dark.png
new file mode 100644
index 00000000000..f92fbefdd22
Binary files /dev/null and b/litellm/proxy/logo_dark.png differ
diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py
index 4a342174277..e0546ffb070 100644
--- a/litellm/proxy/proxy_server.py
+++ b/litellm/proxy/proxy_server.py
@@ -15256,12 +15256,17 @@ def get_logo_url():
@app.get("/get_image", include_in_schema=False)
-async def get_image():
+async def get_image(theme: Literal["light", "dark"] | None = None):
"""Get logo to show on admin UI"""
# get current_dir
current_dir: Final = os.path.dirname(os.path.abspath(__file__))
- default_site_logo: Final = os.path.join(current_dir, "logo.jpg")
+ bundled_light_logo: Final = os.path.join(current_dir, "logo.jpg")
+ bundled_dark_logo: Final = os.path.join(current_dir, "logo_dark.png")
+ default_site_logo: Final = (
+ bundled_dark_logo if theme == "dark" and os.path.isfile(bundled_dark_logo) else bundled_light_logo
+ )
+ default_logo_filename: Final = os.path.basename(default_site_logo)
is_non_root: Final = os.getenv("LITELLM_NON_ROOT", "").lower() == "true"
@@ -15284,7 +15289,7 @@ async def get_image():
assets_dir = current_dir
# Determine default logo path
- default_logo = os.path.join(assets_dir, "logo.jpg") if assets_dir != current_dir else default_site_logo
+ default_logo = os.path.join(assets_dir, default_logo_filename) if assets_dir != current_dir else default_site_logo
if assets_dir != current_dir and not os.path.exists(default_logo):
default_logo = default_site_logo
@@ -15316,7 +15321,7 @@ async def get_image():
if safe_logo is not None:
safe_logo_path, media_type = safe_logo
return FileResponse(safe_logo_path, media_type=media_type)
- return FileResponse(default_site_logo, media_type="image/jpeg")
+ return FileResponse(bundled_light_logo, media_type="image/jpeg")
@app.get("/get_favicon", include_in_schema=False)
diff --git a/tests/test_litellm/proxy/proxy_server/test_routes_misc.py b/tests/test_litellm/proxy/proxy_server/test_routes_misc.py
index 677ab8765bb..e0e5a81cb2c 100644
--- a/tests/test_litellm/proxy/proxy_server/test_routes_misc.py
+++ b/tests/test_litellm/proxy/proxy_server/test_routes_misc.py
@@ -187,6 +187,54 @@ def test_get_image_returns_default_logo(client, monkeypatch):
assert shape == {"status": 200, "media_type_image": True, "has_body": True}
+PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"
+PNG_IHDR_COLOUR_TYPE_OFFSET = 25
+PNG_COLOUR_TYPE_RGBA = 6
+
+
+def test_get_image_dark_theme_returns_logo_with_an_alpha_channel(client, monkeypatch):
+ """?theme=dark serves the dark logo. It must be an RGBA PNG: the light logo is a
+ JPEG whose baked-in white background renders as a white slab on a dark sidebar."""
+ monkeypatch.delenv("UI_LOGO_PATH", raising=False)
+ response = client.get("/get_image", params={"theme": "dark"})
+ body = response.content
+ shape = {
+ "status": response.status_code,
+ "media_type": response.headers.get("content-type", "").split(";")[0],
+ "is_png": body[:8] == PNG_SIGNATURE,
+ "colour_type": body[PNG_IHDR_COLOUR_TYPE_OFFSET],
+ }
+ assert shape == {
+ "status": 200,
+ "media_type": "image/png",
+ "is_png": True,
+ "colour_type": PNG_COLOUR_TYPE_RGBA,
+ }
+
+
+def test_get_image_without_theme_still_serves_the_light_jpeg(client, monkeypatch):
+ """The default response is unchanged, so light mode keeps the existing logo."""
+ monkeypatch.delenv("UI_LOGO_PATH", raising=False)
+ response = client.get("/get_image")
+ shape = {
+ "status": response.status_code,
+ "media_type": response.headers.get("content-type", "").split(";")[0],
+ "is_jpeg": response.content[:3] == b"\xff\xd8\xff",
+ }
+ assert shape == {"status": 200, "media_type": "image/jpeg", "is_jpeg": True}
+
+
+def test_get_image_dark_theme_keeps_serving_a_custom_ui_logo(client, monkeypatch, tmp_path):
+ """A custom UI_LOGO_PATH has no dark variant yet, so dark mode must fall back to the
+ admin's own logo rather than replacing it with LiteLLM's."""
+ custom_logo = tmp_path / "custom.png"
+ custom_logo.write_bytes(PNG_SIGNATURE + b"custom-logo-marker")
+ monkeypatch.setenv("UI_LOGO_PATH", str(custom_logo))
+ response = client.get("/get_image", params={"theme": "dark"})
+ shape = {"status": response.status_code, "body": response.content}
+ assert shape == {"status": 200, "body": PNG_SIGNATURE + b"custom-logo-marker"}
+
+
def test_get_image_redirects_remote_url(client, monkeypatch):
"""Remote logo URLs are served via redirect — the proxy never fetches them server-side."""
monkeypatch.setenv("UI_LOGO_PATH", "https://example.invalid/logo.png")
diff --git a/ui/litellm-dashboard/src/components/leftnav.test.tsx b/ui/litellm-dashboard/src/components/leftnav.test.tsx
index 61b6bf8e515..f1ebea57ffa 100644
--- a/ui/litellm-dashboard/src/components/leftnav.test.tsx
+++ b/ui/litellm-dashboard/src/components/leftnav.test.tsx
@@ -105,6 +105,21 @@ describe("Sidebar (leftnav)", () => {
expect(screen.getByRole("link", { name: /litellm home/i })).toHaveAttribute("href", "/ui");
});
+ it("pairs the logo with a dark-mode variant that swaps on the dark class", () => {
+ renderWithProviders();
+
+ const [light, dark] = Array.from(screen.getByRole("link", { name: /litellm home/i }).querySelectorAll("img"));
+ const classesOf = (el: Element) => new Set(el.className.split(/\s+/));
+
+ const lightSrc = light.getAttribute("src") ?? "";
+ expect(light).toHaveAttribute("src", expect.stringMatching(/\/get_image$/));
+ expect(dark).toHaveAttribute("src", `${lightSrc}?theme=dark`);
+ expect(classesOf(light).has("dark:hidden")).toBe(true);
+ expect(classesOf(light).has("hidden")).toBe(false);
+ expect(classesOf(dark).has("hidden")).toBe(true);
+ expect(classesOf(dark).has("dark:block")).toBe(true);
+ });
+
it("renders all top-level (non-nested) tabs for admin", () => {
renderWithProviders();
diff --git a/ui/litellm-dashboard/src/components/leftnav.tsx b/ui/litellm-dashboard/src/components/leftnav.tsx
index 2ece64271f8..aa1b97116f5 100644
--- a/ui/litellm-dashboard/src/components/leftnav.tsx
+++ b/ui/litellm-dashboard/src/components/leftnav.tsx
@@ -81,6 +81,8 @@ import { MIGRATED_PAGES, migratedHref, legacyPageHref } from "@/utils/migratedPa
const ICON = { strokeWidth: 1.75 } as const;
+const LOGO_CLASS_NAME = "h-7 w-auto max-w-[150px] object-contain group-data-[collapsed=true]/sidebar:w-7";
+
interface SidebarProps {
setPage: (page: string) => void;
defaultSelectedKey: string;
@@ -603,6 +605,7 @@ const Sidebar_: React.FC = ({
};
const logoSrc = logoUrl || `${baseUrl}/get_image`;
+ const darkLogoSrc = logoUrl || `${baseUrl}/get_image?theme=dark`;
return (
@@ -610,11 +613,8 @@ const Sidebar_: React.FC = ({