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

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).
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

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).