Frontend UX: replace raw <select> elements with a token-styled Select / combobox #51
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: High. From a frontend UX audit.
Problem
Four raw
<select>elements (web/src/fields/field-form.tsx:120,138,158,web/src/objects/object-form.tsx:148) are styledw-full rounded border px-2 py-1 text-sm, while the siblingui/Input(components/ui/input.tsx:12) ish-8 rounded-lg border-input … focus-visible:ring-3 focus-visible:ring-ring/50. So in the same form a select and an Input differ in radius, border color, padding, height — and the select has no focus ring (keyboard users get no focus affordance). Separately, the field-form's vocabulary/authority pickers use a bare native<select>while the object form's equivalent reference pickers use the searchableOptionsCombobox(objects/field-input.tsx:8) — the same "pick a vocabulary/authority" action looks and behaves differently in two places.Suggested fix
ui/select.tsx(Base UI Select) that matchesInput's tokens/height/radius/focus ring, and replace all four raw selects.Source: frontend UX/design audit, 2026-06-06.
Done — merged to
main(f45f1d8).Added
web/src/components/ui/select.tsx(Base UI Select) styled to matchui/Input— sameh-8/rounded-lg/border-input/padding and a realfocus-visible:ring-3 ring-ring/50focus ring, plus disabled +aria-invalidstates. Story-validated by running.Replaced all four raw
<select>elements with it:Controller(Base UI Select isn't a native input); default stays draft.value/onValueChange; disabled-on-edit preserved; the vocabulary picker shows a placeholder until chosen (the required check still blocks submit); the authority "Any" state shows "Any" (placeholder), with an "Any" item to switch back.So in a given form, selects and Inputs now share radius/border/height/padding, and keyboard users get a focus ring on every control.
Implementation notes:
<SelectItem>children (incl..map()arrays), so the trigger shows the chosen label with the plain<SelectItem value="x">Label</SelectItem>API.userEvent.selectOptionsto open-trigger + click-option (Base UI Select isn't a<select>); all payload assertions unchanged.No new dependency (Base UI already present); no new i18n keys; en/sv parity; 204 tests green; typecheck/lint/build/check:size/check:colors clean; no codename.
Follow-ups (out of scope): a searchable vocabulary picker (generalize
OptionsComboboxto{id,label}) if vocab lists grow; the remaining non-target<select>(objects-table page-size picker); a lint guard banning raw<select>outsidecomponents/ui/.