From fcef2fc522cdf70daf1208c34a3287695d5ede12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20St=C3=A9ffano?= Date: Mon, 21 Sep 2026 18:16:45 -0300 Subject: [PATCH 1/2] fix(ui): make dropdown choices readable in dark JupyterLab themes The chat mode select reset its background to initial while keeping the theme's text color, so in a dark theme the native option list showed light text on a light popup. Give it the themed input background and set explicit theme colors on the option lists of the NBI selects (chat mode, form fields, settings dialog, perf panel), which browsers that draw options from their own colors would otherwise leave on the light palette. Co-Authored-By: Claude Sonnet 5 --- style/base.css | 14 +++++++++- tests/ts/select-theme-css.test.ts | 46 +++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 tests/ts/select-theme-css.test.ts diff --git a/style/base.css b/style/base.css index b3c192fc..98b83760 100644 --- a/style/base.css +++ b/style/base.css @@ -364,13 +364,25 @@ .chat-mode-select { height: 24px; - background-color: initial; + background-color: var(--jp-input-background, var(--jp-layout-color1)); color: var(--jp-ui-font-color1); padding: 0 5px; border-color: var(--jp-border-color0); outline: none; } +/* The native option list is drawn from the option's own colors, not the + select's, in some browsers (Firefox) and falls back to the light UI palette + otherwise. Without these, light theme text lands on a light popup in a dark + theme and the choices are unreadable. */ +.chat-mode-select option, +.nbi-form-field select option, +.config-dialog-body select option, +.nbi-perf-control select option { + background-color: var(--jp-layout-color2); + color: var(--jp-ui-font-color0); +} + .permission-mode-container { position: relative; display: flex; diff --git a/tests/ts/select-theme-css.test.ts b/tests/ts/select-theme-css.test.ts new file mode 100644 index 00000000..a839fa0f --- /dev/null +++ b/tests/ts/select-theme-css.test.ts @@ -0,0 +1,46 @@ +// Copyright (c) Mehmet Bektas + +// The chat mode dropdown reset its background to `initial` while keeping a +// theme-derived text color, so in a dark JupyterLab theme the native option +// list rendered light text on a light popup. jsdom cannot render a native +// popup, so pin the stylesheet declarations that prevent it. +import * as fs from 'fs'; +import * as path from 'path'; + +const css = fs + .readFileSync(path.join(__dirname, '..', '..', 'style', 'base.css'), 'utf8') + .replace(/\/\*[\s\S]*?\*\//g, ''); + +function declarations(selector: string): string { + const rules = css + .split('}') + .map(chunk => chunk.split('{')) + .filter( + ([sel, body]) => + body !== undefined && + sel + .split(',') + .map(part => part.trim()) + .includes(selector) + ); + return rules.map(([, body]) => body).join('\n'); +} + +describe('select colors follow the JupyterLab theme', () => { + it('gives the chat mode select a themed background', () => { + const body = declarations('.chat-mode-select'); + expect(body).toMatch(/background-color:\s*var\(--jp-/); + expect(body).not.toMatch(/background-color:\s*initial/); + }); + + it.each([ + '.chat-mode-select option', + '.nbi-form-field select option', + '.config-dialog-body select option', + '.nbi-perf-control select option' + ])('themes the option list for %s', selector => { + const body = declarations(selector); + expect(body).toMatch(/background-color:\s*var\(--jp-/); + expect(body).toMatch(/\bcolor:\s*var\(--jp-/); + }); +}); From 04047715a8c44881c55df54b510d93be3b5cc795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20St=C3=A9ffano?= Date: Mon, 21 Sep 2026 18:54:24 -0300 Subject: [PATCH 2/2] fix(ui): keep the chat mode select's look and harden the option colors Follow-ups from review of the dark-theme fix: - Give the chat mode select the footer's own background instead of the input background, so it looks as it did when it was transparent (the input background made it a lighter chip in dark themes). An explicit color is what keeps the native popup from falling back to the light palette. - Match the chat mode popup to that select so the closed and open states are the same tone. - Add fallbacks to the option colors so a theme that omits a variable does not leave the options transparent and bring the original bug back. Checked against the default light and dark themes and jupyterlab-night 0.5.2 in headless Chrome, plus a theme that defines almost nothing. Co-Authored-By: Claude Sonnet 5 --- style/base.css | 25 +++++++++++++++++++----- tests/ts/select-theme-css.test.ts | 32 +++++++++++++++++++++++++------ 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/style/base.css b/style/base.css index 98b83760..7134da09 100644 --- a/style/base.css +++ b/style/base.css @@ -364,7 +364,11 @@ .chat-mode-select { height: 24px; - background-color: var(--jp-input-background, var(--jp-layout-color1)); + + /* The footer's own background, so the select blends in as it did when this + was transparent; an explicit color (rather than transparent) is what + keeps the native popup from falling back to the light palette. */ + background-color: var(--jp-cell-editor-background, var(--jp-layout-color1)); color: var(--jp-ui-font-color1); padding: 0 5px; border-color: var(--jp-border-color0); @@ -374,13 +378,24 @@ /* The native option list is drawn from the option's own colors, not the select's, in some browsers (Firefox) and falls back to the light UI palette otherwise. Without these, light theme text lands on a light popup in a dark - theme and the choices are unreadable. */ -.chat-mode-select option, + theme and the choices are unreadable. The fallbacks keep a theme that leaves + a variable out from bringing that back. */ + +/* The chat mode popup matches the select it belongs to, so the closed and open + states are the same tone. */ +.chat-mode-select option { + background-color: var( + --jp-cell-editor-background, + var(--jp-layout-color1, Canvas) + ); + color: var(--jp-ui-font-color0, CanvasText); +} + .nbi-form-field select option, .config-dialog-body select option, .nbi-perf-control select option { - background-color: var(--jp-layout-color2); - color: var(--jp-ui-font-color0); + background-color: var(--jp-layout-color2, var(--jp-layout-color1, Canvas)); + color: var(--jp-ui-font-color0, CanvasText); } .permission-mode-container { diff --git a/tests/ts/select-theme-css.test.ts b/tests/ts/select-theme-css.test.ts index a839fa0f..bef627b7 100644 --- a/tests/ts/select-theme-css.test.ts +++ b/tests/ts/select-theme-css.test.ts @@ -26,21 +26,41 @@ function declarations(selector: string): string { return rules.map(([, body]) => body).join('\n'); } +// What the chat mode select sits on, so it looks as it did when transparent. +const FOOTER_BACKGROUND = /var\(\s*--jp-cell-editor-background/; + describe('select colors follow the JupyterLab theme', () => { - it('gives the chat mode select a themed background', () => { + it('gives the chat mode select the footer background, not transparent', () => { const body = declarations('.chat-mode-select'); - expect(body).toMatch(/background-color:\s*var\(--jp-/); + expect(body).toMatch( + new RegExp(`background-color:\\s*${FOOTER_BACKGROUND.source}`) + ); expect(body).not.toMatch(/background-color:\s*initial/); }); + it('matches the chat mode popup to the select it belongs to, with fallbacks', () => { + const body = declarations('.chat-mode-select option'); + expect(body).toMatch( + /background-color:\s*var\(\s*--jp-cell-editor-background,\s*var\(\s*--jp-layout-color1,\s*Canvas\s*\)\s*\)/ + ); + expect(body).toMatch( + /\bcolor:\s*var\(\s*--jp-ui-font-color0,\s*CanvasText\s*\)/ + ); + }); + it.each([ - '.chat-mode-select option', '.nbi-form-field select option', '.config-dialog-body select option', '.nbi-perf-control select option' - ])('themes the option list for %s', selector => { + ])('themes the option list for %s, with fallbacks', selector => { const body = declarations(selector); - expect(body).toMatch(/background-color:\s*var\(--jp-/); - expect(body).toMatch(/\bcolor:\s*var\(--jp-/); + // A theme that omits --jp-layout-color2 must not leave the option + // transparent, which is the original bug. + expect(body).toMatch( + /background-color:\s*var\(\s*--jp-layout-color2,\s*var\(\s*--jp-layout-color1,\s*Canvas\s*\)\s*\)/ + ); + expect(body).toMatch( + /\bcolor:\s*var\(\s*--jp-ui-font-color0,\s*CanvasText\s*\)/ + ); }); });