v1.0.0

<bmx-combobox>

A text field that filters a list as you type. The other half of roster item 5, and a separate element from bmx-select rather than a flag on it.

33 properties · 6 events · 9 methods · 13 parts

Example

Show markup
<div class="row">
  <bmx-combobox id="ex-combo-country" label="Country" placeholder="Start typing…" clearable="true" description="Type to filter. Only something from the list can be left in the field."></bmx-combobox>
</div>
<div class="row">
  <bmx-combobox id="ex-combo-tags" label="Labels" multiple="true" allow-custom="true" placeholder="Choose or invent…" description="allow-custom turns the same component into a tag input — press Enter on text that matches nothing."></bmx-combobox>
</div>
<script type="module">
  await customElements.whenDefined('bmx-combobox');

  document.getElementById('ex-combo-country').options = [
    { value: 'gb', label: 'United Kingdom', group: 'Europe' },
    { value: 'ie', label: 'Ireland', group: 'Europe' },
    { value: 'de', label: 'Germany', group: 'Europe' },
    { value: 'es', label: 'Spain', group: 'Europe' },
    { value: 'us', label: 'United States', group: 'Americas' },
    { value: 'ca', label: 'Canada', group: 'Americas' },
  ];

  document.getElementById('ex-combo-tags').options = [
    { value: 'bug', label: 'Bug' },
    { value: 'chore', label: 'Chore' },
    { value: 'docs', label: 'Documentation' },
    { value: 'urgent', label: 'Urgent' },
  ];
</script>

WHY IT IS A SEPARATE ELEMENT

The two look like one component with a switch and are not. A select's trigger is a button, so every printable key is typeahead; a combobox's is a text field, so every printable key is text and Home and End belong to the caret rather than to the list. A select announces aria-haspopup="listbox"; a combobox announces aria-autocomplete. A searchable flag would silently change an element's role and its keyboard between two renders, which is the thing this library keeps declining to do - see src/core/choice.ts for the same argument about two keyboard modules.

What they genuinely share is the list, and they share it as code: ../listbox-view.tsx, ../listbox-geometry.ts and ../listbox.css, so a grouped, windowed list behaves identically in both.

THE SEARCHABLE SELECT

allow-custom is off by default, which makes this the control people usually mean when they ask for a searchable select: the user may type to find, but may only leave with something from the list. Turn it on and the typed text becomes the value - a tag input, a free-text-with-suggestions field. The distinction matters at exactly one moment, blur, and that is where it is implemented.

FILTERING SOMEWHERE ELSE

bmxFilter fires on every edit, and server-filter turns the local filter off. Without that flag a consumer answering bmxFilter from an API would have their results filtered again by the same query on the way in - which works for a substring search and quietly discards everything a fuzzy or synonym search was for.

ACCESSIBILITY

The APG editable-combobox pattern: role="combobox" on the input itself with aria-autocomplete="list", aria-expanded and aria-controls, and aria-activedescendant moving the visual focus while the caret stays where the user put it. Home and End are deliberately not bound to the list, because in a text field they belong to the caret and taking them is the quickest way to make a combobox feel broken to anyone who touch-types.

The result count is announced through a live region. A list that silently shrinks as you type tells a sighted user everything and a screen reader user nothing at all.

Properties

PropertyAttributeTypeDefaultDescription
allowCustom allow-custom boolean false Whether a value the user typed but did not choose is allowed. Off by default, which is the "searchable select": type to find, leave with something from the list. On, the typed text becomes the value - which is what makes this a tag input when combined with multiple.
appearance appearance BmxComboboxAppearance 'outline' Visual treatment.
autoFocus auto-focus boolean false Focus the field once it has rendered.
clearable clearable boolean false Show a clear button once something is chosen.
description description string Help text below the field.
disabled disabled boolean false Disable the field.
emptyText empty-text string 'No matches' What the list says when nothing matches.
errorText error-text string An error supplied by the consumer - a server response, typically.
fullWidth full-width boolean false Stretch to the width of the container.
hideLabel hide-label boolean false Hide the label visually while keeping it as the accessible name.
label label string The field's label. Required unless the label slot is used.
loading loading boolean false Show a busy state while a consumer fetches results.
loadingText loading-text string 'Searching…' What the list says while loading is set.
matcher property only BmxOptionMatcher A consumer's own matcher, in place of the default.
maxTags max-tags number 3 How many tags to show before the rest collapse into a count.
maxVisibleRows max-visible-rows number 8 How many rows the open list shows before it scrolls.
messages property only BmxFieldMessages Replacements for the default wording, by reason. Accepts the JSON spelling of the object as well, for templates that can only write attributes. See src/core/markup.ts.
multiple multiple boolean false Allow more than one choice, shown as tags.
name name string The field's name in the form it belongs to.
options property only BmxListboxOption[] [] The options. A property, because it is an array. Accepts the JSON spelling of the list as well, because an attribute is the only channel some templates have. See src/core/markup.ts.
placeholder placeholder string 'Type to search…' Placeholder text. Never a substitute for a label.
placement placement BmxPlacement 'bottom-start' Which side the list opens on when there is room.
readonly readonly boolean false Make the field read-only. It still submits and is still focusable.
required required boolean false Require a choice before the form will submit.
serverFilter server-filter boolean false Filter elsewhere. See the note on the class.
shape shape BmxShape 'rounded' Corner treatment. circle is not meaningful here and behaves as pill.
showChevron show-chevron boolean true Show the disclosure arrow, which opens the unfiltered list.
size size BmxSize 'md' Size step.
tone tone BmxTone 'primary' Semantic colour role, used for the focus ring and the chosen option.
validateOn validate-on BmxValidateOn 'blur' When the field is willing to reveal a problem.
value value string | null null The chosen value in single mode. Two-way.
values property only string[] [] The chosen values in multiple mode. Two-way. Accepts a comma-separated or JSON string as well, because an attribute is the only channel some templates have. See src/core/markup.ts.
virtualThreshold virtual-threshold number 100 The row count above which the list is windowed.

Events

EventDetailDescription
bmxBlur void Fired when the field loses focus.
bmxChange BmxComboboxChangeDetail Fired when the selection changes.
bmxFilter BmxComboboxFilterDetail Fired on every edit, with what the user has typed.
bmxFocus void Fired when the field gains focus.
bmxOpenChange boolean Fired when the list opens or closes.
bmxValidityChange BmxComboboxValidityDetail Fired whenever the resolved validity changes.

Methods

MethodSignatureDescription
checkValidity checkValidity() => Promise<boolean> Validate now and return whether the field passed, without revealing anything.
clear clear() => Promise<void> Empty the selection and the text.
closeList closeList() => Promise<void> Close the list without committing.
getQuery getQuery() => Promise<string> What the user has typed.
getSelectedOptions getSelectedOptions() => Promise<BmxListboxOption[]> The options behind the current selection.
openList openList() => Promise<void> Open the list, unfiltered.
removeFocus removeFocus() => Promise<void> Remove focus from the field.
reportValidity reportValidity() => Promise<boolean> Validate, reveal any problem, and focus the field if it has one.
setFocus setFocus(options?: FocusOptions) => Promise<void> Focus the field.

Slots

SlotDescription
(default) bmx-option elements, when options are written rather than passed.
description Rich help text, in place of the description property.
label Rich label content, in place of the label property.

CSS shadow parts

PartDescription
chevron The disclosure arrow.
clear The clear button.
control The text input carrying role="combobox".
description The help text.
empty The message shown when nothing matches.
error The error message.
field The bordered box.
group A group heading.
label The label element.
listbox The floating surface.
option One option row.
tag One chosen value in multiple mode.
tag-remove A tag's remove button.

CSS custom properties

PropertyDescription
--bmx-combobox-background The field's background. Set by appearance.
--bmx-combobox-border-color The field's border colour at rest.
--bmx-combobox-border-width Border width of the field.
--bmx-combobox-font-size The text's font size.
--bmx-combobox-gap Space between the tags, the input and the chevron.
--bmx-combobox-group-height A group heading's height.
--bmx-combobox-height The field's height.
--bmx-combobox-label-font-size The label's font size.
--bmx-combobox-list-background The floating list's background.
--bmx-combobox-list-shadow The list's shadow.
--bmx-combobox-option-active-background The background of the option the keyboard is on.
--bmx-combobox-option-height A row's height in the open list.
--bmx-combobox-option-selected-background The chosen option's background.
--bmx-combobox-padding-inline Horizontal padding inside the field.
--bmx-combobox-placeholder-color Placeholder colour. Dimmer than a value, and still AA against the field.
--bmx-combobox-radius Corner radius of the field and the list.
--bmx-combobox-stack-gap Space between the label, the field and the supporting text.
--bmx-combobox-support-font-size Font size of the description and error message.