Combobox
A text input that filters a listbox of suggestions; the APG combobox with list autocomplete, rendered with the list open.
stable · light DOM · 4.0.0. Light DOM retains static content, native semantics, label relationships and host composition without duplicating controls.
Live variants
default
- Cascadia Mono
- SauceCodePro NFM
- Source Code Pro
- No fonts match.
Down opens the list; Enter accepts.
<j3w1-combobox id="example-combobox-default"><div class="combobox">
<label class="combobox-label" for="example-combobox-default-cb-font">Font family</label>
<div class="combobox-root">
<input class="combobox-input" id="example-combobox-default-cb-font" type="text" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="example-combobox-default-cb-font-list" autocomplete="off" value="Sauce" placeholder="Type to filter" aria-describedby="example-combobox-default-cb-font-help example-combobox-default-cb-font-message" name="example-combobox-default-cb-font">
<button class="combobox-toggle" type="button" tabindex="-1" aria-label="Show all fonts"><svg class="combobox-chevron" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" focusable="false"><path d="M3.5 6 8 10.5 12.5 6" fill="none" stroke="currentColor" stroke-width="1.5"></path></svg></button>
</div>
<ul class="combobox-listbox" id="example-combobox-default-cb-font-list" role="listbox" aria-label="Fonts">
<li class="combobox-option" role="option" id="example-combobox-default-cb-font-opt-1">Cascadia Mono</li>
<li class="combobox-option" role="option" id="example-combobox-default-cb-font-opt-2" aria-selected="true">SauceCodePro NFM</li>
<li class="combobox-option" role="option" id="example-combobox-default-cb-font-opt-3">Source Code Pro</li>
<li class="combobox-empty" role="option" aria-disabled="true">No fonts match.</li>
</ul>
<p class="combobox-help" id="example-combobox-default-cb-font-help">Down opens the list; Enter accepts.</p>
<p class="combobox-message" id="example-combobox-default-cb-font-message">✕ Choose a font from the list.</p>
</div></j3w1-combobox>with-groups
-
Recent
- j3w1.github.io
- j3w1.local
-
Known
- j3w1-build.internal
- No hosts match.
Recent hosts first.
<j3w1-combobox id="example-combobox-with-groups"><div class="combobox">
<label class="combobox-label" for="example-combobox-with-groups-cb-host">Host</label>
<div class="combobox-root">
<input class="combobox-input" id="example-combobox-with-groups-cb-host" type="text" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="example-combobox-with-groups-cb-host-list" autocomplete="off" value="j3" placeholder="Name or address" aria-describedby="example-combobox-with-groups-cb-host-help example-combobox-with-groups-cb-host-message" name="example-combobox-with-groups-cb-host">
<button class="combobox-toggle" type="button" tabindex="-1" aria-label="Show all hosts"><svg class="combobox-chevron" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" focusable="false"><path d="M3.5 6 8 10.5 12.5 6" fill="none" stroke="currentColor" stroke-width="1.5"></path></svg></button>
</div>
<ul class="combobox-listbox" id="example-combobox-with-groups-cb-host-list" role="listbox" aria-label="Hosts">
<li class="combobox-group" role="group" aria-labelledby="example-combobox-with-groups-cb-host-g1">
<span class="combobox-group-heading" id="example-combobox-with-groups-cb-host-g1" role="presentation">Recent</span>
<ul class="combobox-group-list" role="presentation">
<li class="combobox-option" role="option" id="example-combobox-with-groups-cb-host-opt-1" aria-selected="true">j3w1.github.io</li>
<li class="combobox-option" role="option" id="example-combobox-with-groups-cb-host-opt-2">j3w1.local</li>
</ul>
</li>
<li class="combobox-group" role="group" aria-labelledby="example-combobox-with-groups-cb-host-g2">
<span class="combobox-group-heading" id="example-combobox-with-groups-cb-host-g2" role="presentation">Known</span>
<ul class="combobox-group-list" role="presentation">
<li class="combobox-option" role="option" id="example-combobox-with-groups-cb-host-opt-3">j3w1-build.internal</li>
</ul>
</li>
<li class="combobox-empty" role="option" aria-disabled="true">No hosts match.</li>
</ul>
<p class="combobox-help" id="example-combobox-with-groups-cb-host-help">Recent hosts first.</p>
<p class="combobox-message" id="example-combobox-with-groups-cb-host-message">✕ Choose a known host.</p>
</div></j3w1-combobox>no-results
- No tags match.
Escape clears the filter.
<j3w1-combobox id="example-combobox-no-results"><div class="combobox">
<label class="combobox-label" for="example-combobox-no-results-cb-tag">Tag</label>
<div class="combobox-root">
<input class="combobox-input" id="example-combobox-no-results-cb-tag" type="text" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="example-combobox-no-results-cb-tag-list" autocomplete="off" value="zzq" placeholder="Type to filter" aria-describedby="example-combobox-no-results-cb-tag-help example-combobox-no-results-cb-tag-message" name="example-combobox-no-results-cb-tag">
<button class="combobox-toggle" type="button" tabindex="-1" aria-label="Show all tags"><svg class="combobox-chevron" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" focusable="false"><path d="M3.5 6 8 10.5 12.5 6" fill="none" stroke="currentColor" stroke-width="1.5"></path></svg></button>
</div>
<ul class="combobox-listbox" id="example-combobox-no-results-cb-tag-list" role="listbox" aria-label="Tags">
<li class="combobox-empty combobox-empty-shown" role="option" aria-disabled="true">No tags match.</li>
</ul>
<p class="combobox-help" id="example-combobox-no-results-cb-tag-help">Escape clears the filter.</p>
<p class="combobox-message" id="example-combobox-no-results-cb-tag-message">✕ Choose an existing tag.</p>
</div></j3w1-combobox>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/combobox.css';
import '@j3w1/ui/register/combobox';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 combobox --out ./vendor/j3w1/comboboxThe 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. |
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 | Typed or accepted input value. |
open | boolean | List visibility. |
methods
| Name | Type / signature | Meaning |
|---|---|---|
refresh | (): void | Reconnect behavior after replacing native child structure. |
focus | (options?: FocusOptions): void | Focus the first usable native control. |
show | (allOptions?: boolean): void | Open the list, optionally showing every option. |
hide | (): void | Close without discarding typed text. |
events
| Name | Type / signature | Meaning |
|---|---|---|
j3w1-change | CustomEvent | An option was accepted or the value cleared. |
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
Combobox combobox
A text input that filters a listbox of suggestions; the APG combobox with list autocomplete, rendered with the list open.
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. |
input.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. |
input.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. |
input.placeholder | color.text.placeholderapproveduse | #bd787d | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
input.caret | color.code.caretapproveduse | #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. |
toggle.text | color.action.tertiary.textapproveduse | #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. |
toggle.bg-hover | color.action.tertiary.hover-bgobserveduse | #1c0a09 | Ghost and icon button hover. |
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. |
listbox.bg | color.surface.raisedapproveduse | #140b0b | D-041 surface.raised: color.primitive.modern.raised. |
listbox.border | color.border.overlayapproveduse | #c81a1a | D-041 owner correction: red control and floating-frame borders, distinct from rose text. |
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-hover | color.interaction.hover.bg-strongapproveduse | #1c1010 | D-041 interaction.hover.bg-strong: color.primitive.modern.hover. |
option.text-hover | color.text.link-hoverobserveduse | #f4eeee | Link text on hover, over interaction.hover.bg-strong. |
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. |
option.ring-selected | color.interaction.focus.ringobserveduse | #e53935 | 1px dashed focus ring on controls and rows. |
group.text | color.text.subtleapproveduse | #bd787d | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
group.border | color.border.dividerapproveduse | #482b2b | D-041 border.divider: color.primitive.modern.divider. |
empty.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. |
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 | Flat list | Grouped options | No results |
|---|---|---|---|
default |
Down opens the list; Enter accepts. |
Recent hosts first. |
Escape clears the filter. |
hover | |||
focus-visible | |||
open | |||
closed | |||
selected | |||
invalid | |||
invalid+focus-visible | |||
disabled | |||
no-results |
Purpose
A text input completed from a list of suggestions, for large or open-ended
sets (fonts, hosts, tags); a closed set of a dozen values is select. The
demonstration renders the list open so every option state shows without a
script.
Anatomy
Parts as listed in the anatomy; help and validation message behave as in
text-field.
States
| State | Visual | Non-colour channel |
|---|---|---|
| default | 1px color.border.control on color.surface.input; list on color.surface.raised, 1px color.border.overlay | — |
| hover | box → color.interaction.hover.bg; hovered option → color.interaction.hover.bg-strong with color.text.link-hover | cursor: text on the box, default on the list |
| focus-visible | box border → color.border.active; 1px dashed color.interaction.focus.ring at −2px | the ring |
| open | list below the box; toggle glyph rotated 180° | aria-expanded="true"; toggle glyph |
| closed | no list; box unchanged | aria-expanded="false"; nothing below the box |
| selected | active option filled color.interaction.selection.bg with color.interaction.selection.text, 1px dashed color.interaction.focus.ring ring at −2px | aria-selected="true"; aria-activedescendant; the ring |
| 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 | color.text.disabled, color.border.disabled, color.interaction.disabled.bg; list never opens | disabled; cursor: not-allowed |
| no-results | only the empty row, color.text.muted italic | aria-disabled row; italic; polite zero-match announcement |
Precedence when several apply: disabled > invalid > selected > hover; focus-visible is always drawn on the box, and the selection ring on the option.
Keyboard
As in the key table. Typing filters on every keystroke; the match count is announced through a polite live region, not by re-reading the list.
Accessibility
The listbox is named (aria-label, or aria-labelledby the field label);
options are role="option", aria-selected on the active one only; groups
are role="group" labelled by their heading. The empty row is a disabled
option so the list is never empty. Text meets 4.5:1; list border and
selection ring 3:1.
Portability
Background, border and outline express everything; the toggle rotation is a
transform inside the motion budget. A toolkit with a native completion popup
uses it, mapping the option fill to the selection roles and the list border
to the popover frame. Without aria-activedescendant, real focus moves to the
option and the deviation is recorded.
Non-examples
A select restyled as a combobox. DOM focus moving into the list on Down. A list floating with a shadow or rounded frame. The active option marked by text colour alone. A no-results state that silently shows the full list. Opening on hover. A list that slides or fades in for longer than 150 ms.
Contrast report
23 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 |
|---|---|---|---|---|---|---|---|
| combobox default text | default | text | color.text.default #e99499 | color.surface.input #000000 | 9.16 | 4.5 | pass |
| combobox default border | default | ui | color.border.control #c81a1a | color.surface.input #000000 | 3.62 | 3 | pass |
| combobox hover text | hover | text | color.text.link-hover #f4eeee | color.interaction.hover.bg-strong #1c1010 | 16.19 | 4.5 | pass |
| combobox focus-visible text | focus-visible | text | color.text.default #e99499 | color.surface.input #000000 | 9.16 | 4.5 | pass |
| combobox focus-visible border | focus-visible | ui | color.border.active #e53935 | color.surface.input #000000 | 4.97 | 3 | pass |
| combobox focus-visible outline | focus-visible | ui | color.interaction.focus.ring #e53935 | color.surface.input #000000 | 4.97 | 3 | pass |
| combobox open text | open | text | color.text.default #e99499 | color.surface.raised #140b0b | 8.46 | 4.5 | pass |
| combobox open border | open | ui | color.border.overlay #c81a1a | color.surface.raised #140b0b | 3.34 | 3 | pass |
| combobox selected text | selected | text | color.interaction.selection.text #f4eeee | color.interaction.selection.bg #531310 | 12.47 | 4.5 | pass |
| combobox selected outline | selected | ui | color.interaction.focus.ring #e53935 | color.interaction.selection.bg #531310 | 3.38 | 3 | pass |
| combobox invalid text | invalid | text | color.text.default #e99499 | color.surface.input #000000 | 9.16 | 4.5 | pass |
| combobox invalid border | invalid | ui | color.status.danger.border #e53935 | color.surface.input #000000 | 4.97 | 3 | pass |
| combobox invalid+focus-visible text | invalid+focus-visible | text | color.text.default #e99499 | color.surface.input #000000 | 9.16 | 4.5 | pass |
| combobox invalid+focus-visible border | invalid+focus-visible | ui | color.status.danger.border #e53935 | color.surface.input #000000 | 4.97 | 3 | pass |
| combobox invalid+focus-visible outline | invalid+focus-visible | ui | color.interaction.focus.ring-container #ffa2a7 | color.surface.input #000000 | 10.98 | 3 | pass |
| combobox no-results text | no-results | text | color.text.muted #d4868b | color.surface.raised #140b0b | 7.01 | 4.5 | pass |
| combobox no-results border | no-results | ui | color.border.overlay #c81a1a | color.surface.raised #140b0b | 3.34 | 3 | pass |
| group heading on the listbox | — | text | color.text.subtle #bd787d | color.surface.raised #140b0b | 5.68 | 4.5 | pass |
| validation message on the canvas | — | text | color.status.danger.text #f07178 | color.surface.canvas #000000 | 7.34 | 4.5 | pass |
| help text on the canvas | — | text | color.text.muted #d4868b | color.surface.canvas #000000 | 7.58 | 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 |
| group separator (decorative) | — | ui | color.border.divider #482b2b | color.surface.raised #140b0b | 1.53 | 1 | pass |
Keys
| Key | Action |
|---|---|
| Down / Up | Opens the list if closed; moves the active option, wrapping at either end; the input keeps DOM focus. |
| Enter | Accepts the active option into the input and closes the list. |
| Escape | Closes the list; a second Escape clears the input. |
| Alt+Down / Alt+Up | Opens or closes the list without moving the active option. |
| Home / End | Moves the caret inside the input, never the active option. |
| Tab | Leaves the control; an open list closes and the typed text stays. |
For agents
Component JSON ·Implementation brief ·Build a task kit ·
Related: text-field select multiselect search-field menu command-palette
Stress fixtures: FX-LONG FX-320 FX-ZOOM-200 FX-I18N FX-RTL FX-RM FX-HC FX-OVERFLOW FX-STATE-MATRIX