Select
A themed single-choice combobox or multiple-choice list backed by a native select for forms and the no-JavaScript fallback.
stable · light DOM · 4.0.0. Theme-owned combobox/listbox presentation backed by the original native select for FormData, constraints, defaults and reset. The native control is visible only before enhancement or without JavaScript.
Live variants
default
New windows open here.
<j3w1-select id="example-select-default"><div class="select">
<label class="select-label" for="example-select-default-sel-workspace">Workspace <span class="select-required" aria-hidden="true">*</span><span class="select-required sr-only"> required</span></label>
<div class="select-root">
<select class="select-control" id="example-select-default-sel-workspace" aria-describedby="example-select-default-sel-workspace-help example-select-default-sel-workspace-message" name="example-select-default-sel-workspace">
<option value="">Choose a workspace</option>
<option value="1" selected="">1: terminal</option>
<option value="2">2: editor</option>
<option value="3">3: browser</option>
</select>
<svg class="select-chevron" aria-hidden="true" focusable="false" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square"><path d="M4 6.5l4 4 4-4"></path></svg>
</div>
<p class="select-help" id="example-select-default-sel-workspace-help">New windows open here.</p>
<p class="select-message" id="example-select-default-sel-workspace-message">✕ Choose a workspace.</p>
</div></j3w1-select>with-groups
Fallbacks use recorded size-adjust metrics.
<j3w1-select id="example-select-with-groups"><div class="select">
<label class="select-label" for="example-select-with-groups-sel-font">Font <span class="select-required" aria-hidden="true">*</span><span class="select-required sr-only"> required</span></label>
<div class="select-root">
<select class="select-control" id="example-select-with-groups-sel-font" aria-describedby="example-select-with-groups-sel-font-help example-select-with-groups-sel-font-message" name="example-select-with-groups-sel-font">
<optgroup label="Installed">
<option value="scp" selected="">SauceCodePro NFM</option>
<option value="cascadia">Cascadia Mono</option>
</optgroup>
<optgroup label="Fallbacks">
<option value="consolas">Consolas</option>
<option value="dejavu">DejaVu Sans Mono</option>
</optgroup>
</select>
<svg class="select-chevron" aria-hidden="true" focusable="false" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="square"><path d="M4 6.5l4 4 4-4"></path></svg>
</div>
<p class="select-help" id="example-select-with-groups-sel-font-help">Fallbacks use recorded size-adjust metrics.</p>
<p class="select-message" id="example-select-with-groups-sel-font-message">✕ That font is not installed.</p>
</div></j3w1-select>multiple
Click or Space toggles an option; Shift+arrows selects a range. The native fallback uses Ctrl/Command+click.
<j3w1-select id="example-select-multiple"><div class="select">
<label class="select-label" for="example-select-multiple-sel-outputs">Outputs <span class="select-required" aria-hidden="true">*</span><span class="select-required sr-only"> required</span></label>
<div class="select-root">
<select class="select-control select-control-multiple" id="example-select-multiple-sel-outputs" multiple="" size="4" aria-describedby="example-select-multiple-sel-outputs-help example-select-multiple-sel-outputs-message" name="example-select-multiple-sel-outputs">
<option value="dp1" selected="">DP-1 (2560×1440)</option>
<option value="dp2" selected="">DP-2 (2560×1440)</option>
<option value="hdmi">HDMI-1 (1920×1080)</option>
<option value="edp">eDP-1 (laptop)</option>
</select>
</div>
<p class="select-help" id="example-select-multiple-sel-outputs-help">Click or Space toggles an option; Shift+arrows selects a range. The native fallback uses Ctrl/Command+click.</p>
<p class="select-message" id="example-select-multiple-sel-outputs-message">✕ Choose at least one output.</p>
</div></j3w1-select>Install and register
Use the exact version. If npm publication is pending, install the release tarball from the installation guide.
npm install --save-exact @j3w1/ui@4.0.0import '@j3w1/ui/tokens.css';
import '@j3w1/ui/styles/select.css';
import '@j3w1/ui/register/select';Insert the maintained markup above after loading these imports. Its native children provide accessible names and form behavior; the empty custom tag alone does not create a control.
Copy the complete implementation
npx --package=@j3w1/ui@4.0.0 j3w1-ui copy select --out ./vendor/j3w1/selectThe CLI verifies every file before writing and refuses overwrites. Serve the output over HTTP and open index.html. Keep its modules, CSS, tokens and licence notices together.
Public API
attributes
| Name | Type / signature | Meaning |
|---|---|---|
disabled | CustomEvent | Disable the native controls owned by this instance. |
loading | CustomEvent | Mark the primary control busy and prevent duplicate activation. |
value | CustomEvent | Forwards value to the primary native control. |
required | CustomEvent | Forwards required to the primary native control. |
readonly | CustomEvent | Forwards readonly to the primary native control. |
checked | CustomEvent | Forwards checked to the primary native control. |
properties
| Name | Type / signature | Meaning |
|---|---|---|
disabled | boolean | Delegates disabled state to native children. |
name | string | Native control name; application form keys remain explicit. |
value | string | Primary native value; assignment does not fabricate user events. |
checked | boolean | Native checkbox state. |
required | boolean | Native required constraint. |
readOnly | boolean | Native readonly property. |
form | HTMLFormElement | null | Owning native form. Read only. |
values | string[] | Selected values of a multiple native select. |
methods
| Name | Type / signature | Meaning |
|---|---|---|
refresh | (): void | Reconnect behavior after replacing native child structure. |
focus | (options?: FocusOptions): void | Focus the first usable native control. |
checkValidity | (): boolean | Check owned native constraints. |
reportValidity | (): boolean | Report the first invalid owned control. |
clear | (): void | Clear an editable primary input and emit native input/change. |
events
| Name | Type / signature | Meaning |
|---|---|---|
j3w1-change | CustomEvent | Native change from an owned control, alongside the original native event. |
Dependencies and verification
Registered components: none. Style dependencies: none.
Package presence establishes an available implementation. Actual passes depend on the tested artifact and environment. Read specification execution evidence; read packed-consumer evidence; inspect the complete state matrix.
Full implementation manifest, API and token dependency closure
Canonical specification
Select select
A themed single-choice combobox or multiple-choice list backed by a native select for forms and the no-JavaScript fallback.
Report an issue with this component
Tokens
Focus a role to inspect its values; follow its link for persistent usage and source details.
| Part | Role | Value | Use |
|---|---|---|---|
root.bg | color.surface.inputapproveduse | #000000 | Form-control fill. |
root.border | color.border.controlapproveduse | #c81a1a | D-041 owner correction: red control and floating-frame borders, distinct from rose text. |
root.bg-hover | color.interaction.hover.bgapproveduse | #1c1010 | D-041 interaction.hover.bg: color.primitive.modern.hover. |
root.border-focus | color.border.activeobserveduse | #e53935 | Active or focused control border. |
root.ring | color.interaction.focus.ringobserveduse | #e53935 | 1px dashed focus ring on controls and rows. |
root.ring-invalid | color.interaction.focus.ring-containerobserveduse | #ffa2a7 | Focus colour for containers and explicitly mapped filled controls (D-015); component rules determine ring geometry. |
root.border-invalid | color.status.danger.borderobserveduse | #e53935 | Danger border, 2px on invalid inputs. |
root.border-disabled | color.border.disabledapproveduse | #482b2b | D-041 border.disabled: color.primitive.modern.divider. |
root.bg-disabled | color.interaction.disabled.bgapproveduse | #0d0707 | D-041 interaction.disabled.bg: color.primitive.modern.panel. |
control.text | color.text.defaultapproveduse | #e99499 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
control.text-disabled | color.text.disabledapproveduse | #a3676b | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
chevron.stroke | color.icon.defaultapproveduse | #e99499 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
option.bg | color.surface.raisedapproveduse | #140b0b | D-041 surface.raised: color.primitive.modern.raised. |
option.text | color.text.defaultapproveduse | #e99499 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
option.bg-selected | color.interaction.selection.bgapproveduse | #531310 | Selected row, tab, workspace; D-027 recessed the fill, leaving the 2px indicator and the check to carry selection. |
option.text-selected | color.interaction.selection.textobserveduse | #f4eeee | Text on the selection. |
group.text | color.text.mutedapproveduse | #d4868b | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
label.text | color.text.brightapproveduse | #ffa2a7 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
label.required-mark | color.status.danger.textobserveduse | #f07178 | D-041 readable error text on neutral surfaces and native warning tints. |
help.text | color.text.mutedapproveduse | #d4868b | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
message.text | color.status.danger.textobserveduse | #f07178 | D-041 readable error text on neutral surfaces and native warning tints. |
State matrix
Every declared state and combination, statically rendered; the default cell is live. Sizes: compact, comfortable (switch with the density control above).
| State | Single | With option groups | Multiple |
|---|---|---|---|
default | New windows open here. | Fallbacks use recorded size-adjust metrics. | Click or Space toggles an option; Shift+arrows selects a range. The native fallback uses Ctrl/Command+click. |
hover | |||
focus-visible | |||
required | |||
invalid | |||
invalid+focus-visible | |||
disabled |
Purpose
One or several options from a fixed list. D-025 requires theme-owned choice
surfaces in web applications; the native select stays the form-value and
constraint source. A searchable or asynchronous list is combobox.
Anatomy
Each selected option has a check mark and the selection fill. The hidden
native <select> keeps its name, options, current values and defaults.
States
| State | Visual | Non-colour channel |
|---|---|---|
| default | 1px color.border.control on color.surface.input; value color.text.default; chevron color.icon.default | the chevron |
| hover | background → color.interaction.hover.bg; border unchanged | cursor: default; hover-capable pointers only |
| focus-visible | border → color.border.active; 1px dashed color.interaction.focus.ring at −2px | the ring |
| required | * after the label in color.status.danger.text plus visually hidden “required” | required; the mark |
| invalid | 2px color.status.danger.border; message with ✕ | border 1 → 2px; glyph; aria-invalid |
| invalid+focus-visible | 2px danger border and color.interaction.focus.ring-container ring at −4px | double boundary |
| disabled | text and chevron color.text.disabled; color.border.disabled; color.interaction.disabled.bg | disabled; cursor: not-allowed |
In the list, a selected option is color.interaction.selection.bg with
color.interaction.selection.text; group labels color.text.muted on
color.surface.raised. Precedence: disabled > invalid > hover; focus-visible
is always drawn.
Keyboard
As in the key table, plus Home/End to the ends of the single list. Ordinary multiple selection needs no modifier. The native fallback keeps host behaviour.
Accessibility
A programmatic label is required. The first option is a real choice or a
disabled selected placeholder with text (“Choose a workspace”), never
empty. aria-invalid="true" only after interaction or submit. The visible
control carries the label, required and invalid state, and descriptions
(help and message via aria-describedby). A failed native constraint focuses
the visible control and shows a theme-owned error. Disabled options,
optgroups and fieldsets stay unavailable. Values and defaults survive
reconnection, programmatic updates and form reset. Automated evidence records
its real environments and implies no manual screen-reader pass.
Portability
Web implementations use the packaged behaviour and its complete copy closure. The enhancement can apply the same renderer to existing selects without moving them or duplicating form values.
Non-examples
An unthemed browser popup presented as fully themed. A background-image chevron in a literal colour. Rounded corners. A blank first option. Navigation on change. Invalid by colour alone. A glow instead of the ring. Disabling by opacity.
Contrast report
22 declared pairs measured on resolved sRGB values with the WCAG 2.x formula; 0 waived (decorative only), 0 failing. A pass is a design target for the named role, not an application-wide conformance claim.
| Pair | State | Kind | Foreground | Background | Ratio | Min | Result |
|---|---|---|---|---|---|---|---|
| select default text | default | text | color.text.default #e99499 | color.surface.input #000000 | 9.16 | 4.5 | pass |
| select default border | default | ui | color.border.control #c81a1a | color.surface.input #000000 | 3.62 | 3 | pass |
| select hover text | hover | text | color.text.default #e99499 | color.interaction.hover.bg #1c1010 | 8.10 | 4.5 | pass |
| select hover border | hover | ui | color.border.control #c81a1a | color.interaction.hover.bg #1c1010 | 3.20 | 3 | pass |
| select focus-visible text | focus-visible | text | color.text.default #e99499 | color.surface.input #000000 | 9.16 | 4.5 | pass |
| select focus-visible border | focus-visible | ui | color.border.active #e53935 | color.surface.input #000000 | 4.97 | 3 | pass |
| select focus-visible outline | focus-visible | ui | color.interaction.focus.ring #e53935 | color.surface.input #000000 | 4.97 | 3 | pass |
| select required text | required | text | color.text.default #e99499 | color.surface.input #000000 | 9.16 | 4.5 | pass |
| select required border | required | ui | color.border.control #c81a1a | color.surface.input #000000 | 3.62 | 3 | pass |
| select invalid text | invalid | text | color.text.default #e99499 | color.surface.input #000000 | 9.16 | 4.5 | pass |
| select invalid border | invalid | ui | color.status.danger.border #e53935 | color.surface.input #000000 | 4.97 | 3 | pass |
| select invalid+focus-visible text | invalid+focus-visible | text | color.text.default #e99499 | color.surface.input #000000 | 9.16 | 4.5 | pass |
| select invalid+focus-visible border | invalid+focus-visible | ui | color.status.danger.border #e53935 | color.surface.input #000000 | 4.97 | 3 | pass |
| select invalid+focus-visible outline | invalid+focus-visible | ui | color.interaction.focus.ring-container #ffa2a7 | color.surface.input #000000 | 10.98 | 3 | pass |
| chevron on the input surface | — | ui | color.icon.default #e99499 | color.surface.input #000000 | 9.16 | 3 | pass |
| option text in the themed list | — | text | color.text.default #e99499 | color.surface.raised #140b0b | 8.46 | 4.5 | pass |
| group label in the themed list | — | text | color.text.muted #d4868b | color.surface.raised #140b0b | 7.01 | 4.5 | pass |
| selected option | — | text | color.interaction.selection.text #f4eeee | color.interaction.selection.bg #531310 | 12.47 | 4.5 | pass |
| validation message on the panel surface | — | text | color.status.danger.text #f07178 | color.surface.default #0d0707 | 6.98 | 4.5 | pass |
| help text on the panel surface | — | text | color.text.muted #d4868b | color.surface.default #0d0707 | 7.22 | 4.5 | pass |
| disabled text (exempt; house floor 3:1) | disabled | ui | color.text.disabled #a3676b | color.interaction.disabled.bg #0d0707 | 4.48 | 3 | pass |
| disabled border (exempt) | disabled | ui | color.border.disabled #482b2b | color.interaction.disabled.bg #0d0707 | 1.58 | 1 | pass |
Keys
| Key | Action |
|---|---|
| Tab / Shift+Tab | Moves focus in and out. |
| Space / Alt+Down | Opens the themed popup without committing a choice. |
| Up / Down | Opens the single list or moves its active option; Enter or Space commits. In the multiple list, moves the active option. |
| Typing | Moves to an enabled option starting with the typed characters. |
| Space / click; Shift+Up / Shift+Down; Ctrl+Command+A | Toggles a multiple option; extends a range; selects or clears all enabled options. |
| Escape / Tab | Closes the single popup without committing the active option. |
For agents
Component JSON ·Implementation brief ·Build a task kit ·
Related: combobox multiselect text-field field menu
Stress fixtures: FX-LONG FX-320 FX-ZOOM-200 FX-I18N FX-RTL FX-RM FX-HC FX-TOUCH FX-DENSITY FX-STATE-MATRIX