Problem
Two related polish items surfaced while reviewing the Phase 1 design system primitives (#149, PR #150):
1. Native <select> dropdown ignores dark theme on Windows
The Select primitive I shipped in Phase 1 uses native <select> with our chrome on the trigger button. But the open dropdown menu is rendered by the OS, which on Windows always uses the system theme — white background, dark text — completely breaking the dark-theme aesthetic. Screenshot in the PR review showed the issue clearly: clean dark trigger, then a glaring light dropdown overlay.
This is also true on macOS to a lesser extent — the native menu adopts system theme, not app theme.
2. Interactive elements should show pointer cursor
Tailwind doesn't apply cursor: pointer to <button> by default (browser default is cursor: default for buttons in most engines). Currently buttons across QueryDen show the I-beam or default arrow cursor instead of the pointer, making interactivity ambiguous.
Fix
Custom Select dropdown
Replace the native <select> with a portal-rendered, theme-aware dropdown. Should:
- Render in a portal so it isn't clipped by
overflow:hidden parents
- Match the existing Input chrome on the trigger
- Keyboard navigation (Arrow Up/Down, Home/End, type-ahead, Enter, Esc)
- Proper ARIA (combobox / listbox roles)
- Open animation (fade + scale, ~120ms)
- Selected state visible
Implementation: add @radix-ui/react-select and wrap it. Radix handles portal, keyboard nav, focus, animations, and a11y — building from scratch is ~200 lines that we'd get subtly wrong. ~12KB gzipped, tree-shakeable.
Pointer cursor on interactables
Add cursor-pointer to Button, IconButton, and any other clickable primitive in src/components/ui/. Disabled state already uses cursor-not-allowed.
For ad-hoc <button> elements elsewhere in the app, they'll get the cursor as a side effect of being migrated to the primitives in Phase 2 sweeps. Not adding a global button { cursor: pointer; } rule because that would also affect dragging handles, grid headers, and other clickable-but-not-clickable elements.
Scope
Both are landing in the existing Phase 1 PR (#150) since they're foundational primitives, not Phase 2 migration work.
Acceptance
Problem
Two related polish items surfaced while reviewing the Phase 1 design system primitives (#149, PR #150):
1. Native
<select>dropdown ignores dark theme on WindowsThe
Selectprimitive I shipped in Phase 1 uses native<select>with our chrome on the trigger button. But the open dropdown menu is rendered by the OS, which on Windows always uses the system theme — white background, dark text — completely breaking the dark-theme aesthetic. Screenshot in the PR review showed the issue clearly: clean dark trigger, then a glaring light dropdown overlay.This is also true on macOS to a lesser extent — the native menu adopts system theme, not app theme.
2. Interactive elements should show pointer cursor
Tailwind doesn't apply
cursor: pointerto<button>by default (browser default iscursor: defaultfor buttons in most engines). Currently buttons across QueryDen show the I-beam or default arrow cursor instead of the pointer, making interactivity ambiguous.Fix
Custom Select dropdown
Replace the native
<select>with a portal-rendered, theme-aware dropdown. Should:overflow:hiddenparentsImplementation: add
@radix-ui/react-selectand wrap it. Radix handles portal, keyboard nav, focus, animations, and a11y — building from scratch is ~200 lines that we'd get subtly wrong. ~12KB gzipped, tree-shakeable.Pointer cursor on interactables
Add
cursor-pointertoButton,IconButton, and any other clickable primitive insrc/components/ui/. Disabled state already usescursor-not-allowed.For ad-hoc
<button>elements elsewhere in the app, they'll get the cursor as a side effect of being migrated to the primitives in Phase 2 sweeps. Not adding a globalbutton { cursor: pointer; }rule because that would also affect dragging handles, grid headers, and other clickable-but-not-clickable elements.Scope
Both are landing in the existing Phase 1 PR (#150) since they're foundational primitives, not Phase 2 migration work.
Acceptance
<Button>or<IconButton>shows the pointer cursor