Susegad UI
Register
Theme
Palette

Components

Combobox

An input with suggestions. Without JavaScript it is the browser's own input list and datalist; with it, the ARIA APG editable combobox with a listbox popover, full keyboard support, and matching that finds places however people type them: without accents, by an older name, in another script, or spelled as heard.

quiet warm playful

Open the live demo npx susegad add combobox

Stands on: Core, Core: components, Field (TextField, TextArea), Tokens

The prompt

the prompt

"Bombay", "मुंबई" and "mum" are all asking for Mumbai.

An input with suggestions. Without JavaScript it is the browser's own <input list> and <datalist>. With JavaScript it becomes the ARIA APG editable combobox, and its matching finds places however people type them.

<link rel="stylesheet" href="susegad/components/combobox/combobox.css">
<script type="module" src="susegad/components/combobox/combobox.js"></script>

<sg-combobox>
  <label for="from">Travelling from</label>
  <input id="from" name="from" list="cities" autocomplete="off">
  <datalist id="cities">
    <option value="Mumbai" data-aliases="Bombay, मुंबई">Maharashtra</option>
    <option value="Bengaluru" data-aliases="Bangalore, ಬೆಂಗಳೂರು">Karnataka</option>
  </datalist>
</sg-combobox>

The prompt

Build a combobox as a light-DOM custom element, <sg-combobox>, around a native <input list> with a visible <label> and a <datalist>, so that without JavaScript the browser's own suggestions work and the form submits whatever was typed. With JavaScript, follow the ARIA APG editable combobox with list autocomplete: remove the input's list, give it role="combobox", aria-autocomplete="list", aria-expanded and aria-controls, and build a listbox of the datalist's options as a manual popover anchored under the input with CSS anchor positioning. Keep focus in the input and move through the options with aria-activedescendant. Down Arrow opens the list and moves to the next option, Up Arrow to the previous, both wrapping; Enter chooses the active option and closes the list, or submits the form when none is active; Escape closes the list, and clears the text if it is already closed; Tab closes without choosing. Never choose for the person, and always keep what they typed. Match the way Indian place names are really typed: fold case and punctuation, strip accents from Latin letters only and leave the vowel signs of Devanagari, Kannada and other scripts alone, match the start of any word, and match aliases from data-aliases (older names and other scripts), saying "also Bombay" when an alias matched. Add a looser match for the usual romanisation variants (doubled vowels, aspirates, w for v), ranked below the exact ones. Mark the matched letters, mapping accents back to the original text. Say the number of suggestions in a polite status line once typing settles, only when it changes. Give it three registers. Quiet: a hairline box and list. Warm: write the input on the paper like the field and the select: no box, the field's pencil rule under it, inked from left to right while the input has focus and under the words away from it; a caret of two uneven pencil strokes; the list edged in ink and ruled in pencil between the suggestions, with the active suggestion's name underlined in ink by hand rather than filled. Playful: a stamped box with an off-register ghost, a caret in accent ink with a thick, round nib, and the suggestions as stamped chips. Keep the list's entrance off in quiet and under reduced motion.

Words to code

When you sayTechniqueWhat happens
without JavaScript the browser's own suggestions workprogressive enhancementThe <input list> and <datalist> are real HTML. The element reads the options from the datalist, so the no-JS path and the enhanced path share one list.
follow the ARIA APG editable combobox with list autocompleteaccessibilityThe input is the combobox and keeps focus. The listbox is controlled by it, labelled by the same label, and the active option is announced through aria-activedescendant.
a manual popover anchored under the input with CSS anchor positioningpopover API, anchorsThe list sits in the top layer, so no container clips it. anchor-name on the input and position-anchor on the list place it, with flip-block when there is no room below. Browsers without anchors get coordinates from JavaScript.
Never choose for the person, and always keep what they typedthe person decidesTyping opens the list with nothing selected. Only Enter on an active option or a click chooses. A place not in the list is kept and submitted as typed.
strip accents from Latin letters onlyUnicodeText is decomposed (NFD). A combining mark is dropped only when it follows a Latin letter, so "Balcão" folds to "balcao" while "मुंबई" keeps its anusvara and matras.
match aliases from data-aliasesaliasesEach option can list other names. A match through an alias ranks just below the same kind of match on the name, and the option says which alias matched.
a looser match for the usual romanisation variantsfoldingaa, ee and oo fold to a, i and u; th, dh, bh, kh, gh, ph and jh lose the h; w folds to v. "Tiruvanantapuram" finds Thiruvananthapuram, one rank below exact matches.
mapping accents back to the original texthighlightingEach character is folded on its own and its position remembered, so the match in the folded text maps back and the <mark> wraps "Balcão", not "Balca".
Say the number of suggestions in a polite status linelive regionA visually hidden role="status" says "2 suggestions" 450 ms after typing stops, and nothing if the number has not changed.
the field's pencil rule under it, inked while the input has focusshared drawingruleUnder() from field/rule.js: the field's graphite pencilRule() under the input, with the input's border turned transparent at the same width. On focus the ink runs the whole rule, drawn in over 560 ms; on blur it shrinks to the typed words, measured with canvas.measureText.
ruled in pencil between the suggestions … underlined in ink by handCSS masksOne hand-drawn stroke as an SVG data URI (--sg-pencil-line, vector-effect: non-scaling-stroke) is the mask for both: a ::before on each option but the last, in --sg-text-soft at 55%, and a ::after on the active option's .sg-combobox__value, in --sg-accent-text, so the underline is exactly as long as the name. The active option has no fill.
a caret of two uneven pencil strokesSVGcaret() takes a list of [d, width]; warm passes two crossing strokes of 1.9 and 1.5.

<sg-combobox> is a text input that suggests values as people type, for places, names and anything else with a known list and room for something new. It starts as the browser's own <input list> and <datalist>, and becomes the ARIA APG editable combobox when JavaScript runs.

Usage

<link rel="stylesheet" href="susegad/components/combobox/combobox.css">
<script type="module" src="susegad/components/combobox/combobox.js"></script>

<sg-combobox>
  <label for="from">Travelling from</label>
  <input id="from" name="from" list="cities" autocomplete="off" required>
  <datalist id="cities">
    <option value="Mumbai" data-aliases="Bombay, मुंबई">Maharashtra</option>
    <option value="Panaji" data-aliases="Panjim, पणजी">Goa</option>
  </datalist>
</sg-combobox>
  • The option's value is what goes into the input and the form. Its text (here the state) is shown beside it as a hint, and can be matched too.
  • data-aliases lists other names, comma-separated: an older name, a spelling people use, the name in another script.
  • Add lang to an option whose value is not English, so a screen reader pronounces it well.
  • The value is whatever the person types. The list only suggests. To insist on a listed value, validate it on the server, or with setCustomValidity() and <sg-field-note>.

Attributes, properties and events

NameKindWhat it does
registerattributequiet, warm or playful. Overrides the page's register for this element.
openpropertyWhether the suggestions are showing.
suggestionspropertyThe values on show, best first.
toggle()methodOpen or close the suggestions (the caret does this for a pointer).
sg-chooseeventA suggestion was chosen. detail: { value }. The input also fires input and change, as typing would.
sg-skineventFires when a register's look has loaded.

Matching

  • Case, punctuation and extra spaces are ignored.
  • Accents are ignored on Latin letters: "Balcao" finds "Balcão", "Sao Jacinto" finds "São Jacinto". Devanagari, Kannada and other scripts keep their vowel signs, because there they are letters.
  • A match at the start of the name ranks first, then at the start of any word ("goa" finds "Old Goa"), then an alias ("Bombay", "मुंबई"), then a looser spelling match ("Tiruvanantapuram" finds Thiruvananthapuram), then three or more letters inside a word.
  • Equally good matches keep the order of your datalist.

Keyboard

KeyWhat it does
TypingFilters the suggestions and opens the list. Nothing is chosen for you.
Down ArrowOpens the list, or moves to the next suggestion (from the last, back to the first). Alt+Down opens without moving.
Up ArrowOpens the list at the last suggestion, or moves to the previous one.
EnterChooses the highlighted suggestion. With none highlighted, submits the form as usual.
EscapeCloses the list. With the list closed, clears the text.
TabCloses the list and moves on, keeping what was typed.
Left, Right, Home, EndBack to editing the text.

Registers

  • Quiet: a hairline input with a plain chevron, and a raised list with a bar beside the highlighted suggestion. The list appears without motion. Without JavaScript, the input keeps this look and the suggestions are the browser's own.
  • Warm: written on the paper like the field: no box, a pencil rule under the input (the field's own, from field/rule.js) that inks from left to right while you type in it, and stays inked under the words when you leave. A caret of two uneven pencil strokes. The list is edged in ink and ruled in pencil between suggestions; the highlighted suggestion's name is underlined in ink by hand, not filled. The list unfurls in about a quarter of a second.
  • Playful: a stamped box with an off-register ghost and a thick, round accent caret; suggestions are stamped chips, slightly tilted, and the highlighted one is filled. A choice lands on the input with a small press.

An input that is :user-invalid, or carries aria-invalid="true" for an error a server sends back, shows its edge (or its pencil rule, in warm) in the danger colour. Link the words with aria-describedby. The demo shows a filled-in value and a wrong one.

Accessibility

  • Follows the ARIA APG editable combobox with list autocomplete. Focus stays in the input; the highlighted suggestion is announced through aria-activedescendant, and the listbox is labelled by the input's label.
  • A polite status line says how many suggestions there are ("2 suggestions", "No suggestions") once typing settles, and only when the number changes.
  • The chevron is aria-hidden: the arrow keys do what it does.
  • The input is at least 44 pixels high, the suggestions at least 40.
  • With reduced motion, the list's entrance and the chevron's turn are off.
  • combobox.check.mjs checks the no-JavaScript form, every key above, the screen-reader tree, matching across scripts, and every register under reduced motion. Zero axe violations in every register, light and dark.