Skip to content

fix(react): keep native select options readable in dark mode#1240

Open
jackulau wants to merge 2 commits into
RhysSullivan:mainfrom
jackulau:fix/1227-native-select-dark
Open

fix(react): keep native select options readable in dark mode#1240
jackulau wants to merge 2 commits into
RhysSullivan:mainfrom
jackulau:fix/1227-native-select-dark

Conversation

@jackulau

@jackulau jackulau commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Problem

Native <select> dropdowns render unreadable option popups in dark mode (#1227). The Resume-approvals dropdown on the MCP connect card is the visible surface, but every NativeSelect in the console is affected.

Root cause

The console themes through @media (prefers-color-scheme: dark) and never sets a .dark class, so Tailwind's dark: utilities (gated on .dark via @custom-variant) never match and are effectively dead. NativeSelect relied on dark:bg-input/30 for its dark surface and set no color-scheme, leaving it bg-transparent. A native option popup follows the control's opaque background-color and color-scheme, not <option> styles, so in dark mode the browser drew a light popup and the inherited light text was unreadable.

Fix

  • Give the control a solid themed surface: bg-popover text-popover-foreground (opaque, flips with the theme tokens) plus hover:bg-accent.
  • Pin color-scheme to the active theme via the existing useIsDark() hook, so the browser renders a matching popup.
  • Drop the dead dark: background utilities.

NativeSelect is a shared component, so all of its usages are fixed at once.

Verification

  • format:check, lint (0 warnings / 0 errors), typecheck (42/42), and the @executor-js/react test suite (189/189) pass.
  • Verified live in host-selfhost on the Resume-approvals dropdown in both dark and light: the select's computed color-scheme and background/text flip correctly with the OS theme (dark #141414 / #ededed, light #ffffff / #111111), which is what makes the native popup readable.

Fixes #1227

The native <select> option popup follows the control's opaque background
and color-scheme, not option-level styles. bg-transparent left the popup
white, so dark-mode options rendered near-white text on a white surface.
The dark:bg-input/30 fallback never applied: the dark: variant is gated on
a .dark ancestor class this app never sets (theming is prefers-color-scheme
driven).

Give the select a solid themed surface (bg-popover/text-popover-foreground),
add a hover:bg-accent state, and pin color-scheme via useIsDark so the native
popup follows the active theme. Fixes the Resume approvals dropdown in
setup-mcp and mcp-install-card, and every other NativeSelect.

Closes RhysSullivan#1227
@greptile-apps

greptile-apps Bot commented Jul 1, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes unreadable native <select> option popups in dark mode by giving NativeSelect a solid themed surface (bg-popover text-popover-foreground) and dynamically pinning color-scheme to the active OS theme via useIsDark(). Dead Tailwind dark: utilities are also removed since the app themes via prefers-color-scheme and never sets a .dark class.

  • Replaces bg-transparent + dead dark:bg-input/30 with opaque bg-popover text-popover-foreground, ensuring the native option popup always gets a visible background.
  • Injects style={{ colorScheme: isDark ? "dark" : "light", ...style }} on the <select> element so the browser renders the popup in the correct OS color scheme.
  • Drops all dead dark: utilities from the className strings (both background and the invalid-state ring).

Confidence Score: 5/5

Safe to merge — the change is narrowly scoped to NativeSelect's className and inline style, with no logic changes elsewhere.

The fix correctly replaces dead Tailwind dark: utilities (which never matched due to the app using prefers-color-scheme rather than a .dark class) with an opaque themed surface and a dynamically pinned color-scheme. The useIsDark() hook is already established in the codebase and handles SSR safely. The style spread order (colorScheme first, then ...style) is an intentional API affordance allowing callers to override if needed. No regressions are introduced.

No files require special attention.

Important Files Changed

Filename Overview
packages/react/src/components/native-select.tsx Replaces dead dark-mode Tailwind utilities with opaque themed classes and pins color-scheme via useIsDark(); logic is correct and the hook already handles SSR safely.
.changeset/native-select-dark-mode.md Patch-level changeset entry accurately describing the dark-mode fix for @executor-js/react.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["NativeSelect renders"] --> B["useIsDark()"]
    B --> C{"prefers-color-scheme: dark?"}
    C -->|yes| D["colorScheme: 'dark'"]
    C -->|no| E["colorScheme: 'light'"]
    D --> F["<select style={{colorScheme}} className='bg-popover text-popover-foreground'>"]
    E --> F
    F --> G["Browser native option popup\ninherits opaque bg + matching color-scheme"]
    G --> H["Readable in both light & dark mode ✓"]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["NativeSelect renders"] --> B["useIsDark()"]
    B --> C{"prefers-color-scheme: dark?"}
    C -->|yes| D["colorScheme: 'dark'"]
    C -->|no| E["colorScheme: 'light'"]
    D --> F["<select style={{colorScheme}} className='bg-popover text-popover-foreground'>"]
    E --> F
    F --> G["Browser native option popup\ninherits opaque bg + matching color-scheme"]
    G --> H["Readable in both light & dark mode ✓"]
Loading

Reviews (2): Last reviewed commit: "fix(react): drop remaining dead dark: ar..." | Re-trigger Greptile

Comment thread packages/react/src/components/native-select.tsx Outdated
The dark: variant is gated on a .dark class the app never sets, so dark:aria-invalid:ring-destructive/40 never applied. Removing it is a no-op at runtime and keeps the component free of dead dark: utilities.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NativeSelect options unreadable in dark mode

1 participant