Skip to content

Copilot logo ignores widget theme/localStorage — Logo reads an unprovided theme context and follows OS prefers-color-scheme #2976

Description

@mcoslet-cloudx

Describe the bug
In the Copilot widget, the logo (/logo?theme=...) does not follow the applied theme. It always follows the OS/browser prefers-color-scheme instead of the theme passed to mountChainlitWidget (or the persisted vite-ui-theme in localStorage). As a result, a copilot mounted with theme: 'light' on a machine set to dark mode renders a light UI but a dark logo (GET /logo?theme=dark).

Root cause (traced in copilot/dist/index.js, v2.11.1): there are two theme contexts.

The shadcn ThemeProvider (storageKey: "vite-ui-theme", defaultTheme = widgetConfig.theme || config.default_theme) provides context cIr. This themes the whole UI correctly and toggles the light/dark class on window.cl_shadowRootElement.
The Logo component's useTheme hook reads a different context, wNt (createContext({ theme: "system", setTheme: () => null })). There are zero wNt.Provider render sites in the bundle, so it always resolves to its default "system".
The logo variant is computed as:

variant = t.theme === "system"
? (matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light")
: t.theme;
Because wNt is stuck at "system", variant collapses to the OS color scheme, and the widget theme prop / localStorage are ignored for the logo only.

To Reproduce
Steps to reproduce the behavior:

Set your OS/browser to dark mode (prefers-color-scheme: dark).
Embed the Copilot with an explicit light theme:
window.mountChainlitWidget({
chainlitServer: CHAINLIT_SERVER_URL,
accessToken: chainlitToken,
theme: 'light',
});
Provide per-theme logos in public/ (logo_light.svg, logo_dark.svg) and leave [UI] logo_file_url empty.
Open the copilot and inspect the logo element / Network tab.
See the UI rendered in light (correct) but the logo requested as GET /logo?theme=dark (logo_dark.svg) — and localStorage.getItem('vite-ui-theme') is "light".
Confirmation: DevTools → Rendering → "Emulate prefers-color-scheme: light" flips the logo to light while the rest of the UI is unchanged, proving the logo is OS-driven rather than theme-driven.

Expected behavior
The logo should follow the same resolved theme as the rest of the copilot — i.e. the theme passed to mountChainlitWidget (and/or the persisted vite-ui-theme). Mounting with theme: 'light' should request GET /logo?theme=light and render logo_light.svg, regardless of the OS color scheme. The Logo hook should read the same context that ThemeProvider provides (cIr), or wNt should be given a provider.

Screenshots
Image

Storage panel showing vite-ui-theme = light.
Rendered logo element:<img src="http://localhost:8000/logo?theme=dark&" alt="logo" class="logo w-[100px]">.

Desktop (please complete the following information):
OS: macOS (system appearance: Dark)
Browser: Firefox (DevTools shown); reproducible on Chromium as well
Version: Chainlit 2.11.1 (copilot widget)

Smartphone (please complete the following information):
N/A (not device-specific; depends on prefers-color-scheme, so any device in dark mode reproduces it)
Additional context

Workaround: setting [UI] logo_file_url to a single URL bypasses the ?theme= path entirely (getLogoEndpoint(theme, url) => url || /logo?theme=${theme}), so the logo no longer depends on the broken context. Trade-off: one logo for both themes.
The affected hook is Pje (the Logo's useTheme) reading context wNt; the working provider is uIr providing context cIr. Aligning the two (or adding a wNt.Provider) should fix it.
Likely also affects any other component consuming the same wNt-based hook, not just the logo.

Additional context
Context for screenshots:
localStorage.setItem('vite-ui-theme', "light"); // beat the provider's stored value
window.mountChainlitWidget({
chainlitServer: server,
accessToken: access_token,
theme: "light",
});

This bug report — including the root-cause trace through the copilot bundle — was investigated and written with Claude Opus 4.8 (High reasoning).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingfrontendPertains to the frontend.needs-triagestaleIssue has not had recent activity or appears to be solved. Stale issues will be automatically closed

    Type

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions