j3w1 / ComboboxVue · HTML · React · Astro

J3W1 / DESIGN SYSTEM

Combobox

A text input that filters a listbox of suggestions; the APG combobox with list autocomplete, rendered with the list open.

stable · LIGHT DOM · 1.1.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.

✕ Choose a font from the list.

<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
  • Known
  • No hosts match.

Recent hosts first.

✕ Choose a known host.

<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.

✕ Choose an existing tag.

<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@1.1.0
import '@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.

Vue SFC, React, Astro and plain HTML integration →

Copy the complete implementation

npx --package=@j3w1/ui@1.1.0 j3w1-ui copy combobox --out ./vendor/j3w1/combobox

The 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

NameType / signatureMeaning
disabledCustomEventDisable the native controls owned by this instance.
loadingCustomEventMark the primary control busy and prevent duplicate activation.

properties

NameType / signatureMeaning
disabledbooleanDelegates disabled state to native children.
namestringNative control name; application form keys remain explicit.
valuestringTyped or accepted input value.
openbooleanList visibility.

methods

NameType / signatureMeaning
refresh(): voidReconnect behavior after replacing native child structure.
focus(options?: FocusOptions): voidFocus the first usable native control.
show(allOptions?: boolean): voidOpen the list, optionally showing every option.
hide(): voidClose without discarding typed text.

events

NameType / signatureMeaning
j3w1-changeCustomEventAn 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

Purpose

A text input whose value is completed from a list of suggestions. It is the right control when the set is large or open-ended (fonts, hosts, tags); a closed set of a dozen values is the select component. The demonstration renders the list open so every option state is visible without a script.

Anatomy

Label above the box; the box with the input and an optional toggle; the listbox directly below, on color.surface.raised behind a 1px color.border.overlay; options in color.text.default, an optional group heading in color.text.subtle above a color.border.divider rule; the empty message in color.text.muted; help and validation message below, as in the text field.

States

StateVisualNon-colour channel
default1px color.border.control on color.surface.input; list on color.surface.raised with a 1px color.border.overlay
hoverbox background → color.interaction.hover.bg; the hovered option → color.interaction.hover.bg-strong with color.text.link-hovercursor: text on the box, default on the list
focus-visiblebox border → color.border.active; ring 1px dashed color.interaction.focus.ring at −2pxthe ring
openlist visible below the box; toggle glyph rotated 180°aria-expanded="true"; the toggle glyph
closedlist absent; the box unchangedaria-expanded="false"; nothing below the box
selectedthe active option filled color.interaction.selection.bg with color.interaction.selection.text and a 1px dashed ring in color.interaction.focus.ring-containeraria-selected="true"; aria-activedescendant; the ring
invalidbox border 2px color.status.danger.border; message with the ✕ glyphborder width 1 → 2px; glyph; aria-invalid
invalid+focus-visiblethe 2px danger border and the ring in color.interaction.focus.ring-container at −4pxdouble boundary
disabledtext color.text.disabled; border color.border.disabled; background color.interaction.disabled.bg; list never opensdisabled; cursor: not-allowed
no-resultsthe list holds only the empty row in color.text.muted, italicaria-disabled row; italic; a polite announcement of zero matches

Precedence when several apply: disabled > invalid > selected > hover; focus-visible is always drawn on the box, and the selection ring on the option.

Keyboard

DOM focus never leaves the input. Down and Up open the list and move the active option, which the input reports through aria-activedescendant; Enter accepts it; Escape closes the list and, when the list is already closed, clears the value. Alt+Down and Alt+Up open and close without changing the active option. Home and End move the caret. Tab closes the list and keeps whatever is typed. Typing filters the list on every keystroke; the number of matches is announced through a polite live region, not by re-reading the list.

Accessibility

The input carries role="combobox", aria-autocomplete="list", aria-expanded, aria-controls pointing at the listbox and, while an option is active, aria-activedescendant. The listbox has an accessible name (aria-label or aria-labelledby the field label); options are role="option" with aria-selected on the active one only; groups are role="group" with aria-labelledby their heading. The empty row is a disabled option so the list is never empty; the zero-match count is announced through the same polite live region as every other count. Contrast: option text 8.43:1 on the raised surface, selection text 12.47:1 on the selection fill, the selection ring 7.48:1 on the fill, the list border 4.57:1 on the raised surface, group headings 4.96:1.

Portability

Everything is expressible with background, border and outline; the toggle rotation is a transform inside the motion budget. Toolkits with a native completion popup use it and map the option fill to the selection roles; the list border is the popover frame. Toolkits without aria-activedescendant move real focus to the option and record the deviation.

Non-examples

A select restyled as a combobox. Moving DOM focus into the list on Down. A list that floats over the page with a shadow or a rounded frame. Highlighting the active option with a colour change of the text 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.

j3w1 / command

  • SPEC Overview
  • SPEC Foundations
  • SPEC Components
  • SPEC Patterns
  • SPEC Tokens
  • SPEC Tools
  • SPEC Implement
  • SPEC For agents
  • SPEC Ports
  • SPEC Releases
  • DEMO Vue application demo
  • SPEC Complete UI theme specification