Auto complete

Auto complete is a text field that suggests matching options as you type. Unlike a select it does not restrict the input. What the user types is the value, and the menu helps them get there faster.

It accepts every text field attribute, such as variant, label, supportingText and required.

Usage

Pass an array of options. Each one needs a label. A value, a supportingText and leading or trailing content are optional. By default the menu shows the options whose label contains the typed text. Picking one writes its label into the field.

<script lang="ts">
	import { AutoComplete } from 'noph-ui'
</script>

<AutoComplete
	variant="filled"
	label="Fruits"
	name="fruit"
	options={[{ label: 'Apple' }, { label: 'Banana' }]}
/>

Multiple values

To collect more than one value, keep the chosen options in your own state and render them as input chips inside the field. Use two props. onoptionselect replaces the default behaviour of writing the label into the input. optionsFilter replaces the filtering, so you can hide options that are already picked.

<script lang="ts">
	import { AutoComplete, ChipSet, InputChip } from 'noph-ui'
	import type { AutoCompleteOption } from 'noph-ui/types'

	const fruitOptions: AutoCompleteOption[] = [
		{ value: 1, label: 'Apple' },
		{ value: 2, label: 'Banana' },
		{ value: 3, label: 'Orange' },
		{ value: 4, label: 'Grape' },
		{ value: 5, label: 'Pineapple' },
		{ value: 6, label: 'Strawberry' },
		{ value: 7, label: 'Mango' },
		{ value: 8, label: 'Melon' },
	]
	let fruits: AutoCompleteOption[] = $state([{ value: 1, label: 'Apple' }])
	let fruitValue = $state('')
	let chipSet: HTMLDivElement | undefined = $state()
	let input: HTMLElement | undefined
</script>

<AutoComplete
	options={fruitOptions}
	placeholder="Add fruit..."
	style="width:340px"
	label="Fruits"
	name="fruit"
	populated={fruits.length > 0}
	bind:value={fruitValue}
	onfocus={(e) => (input = e.currentTarget)}
	onkeydown={(e) => {
		// Backspace in the empty field moves to the last fruit, and Backspace there removes it.
		if (e.key === 'Backspace' && !e.currentTarget.value && fruits.length > 0) {
			e.preventDefault()
			chipSet
				?.querySelectorAll('button')
				.item(fruits.length - 1)
				?.focus()
		}
	}}
	onoptionselect={(option) => {
		fruits.push(option)
	}}
	optionsFilter={(option) => {
		return (
			(!fruitValue || option.label.toLocaleLowerCase().includes(fruitValue.toLocaleLowerCase())) &&
			!fruits.find((f) => f.value === option.value)
		)
	}}
>
	<ChipSet bind:element={chipSet}>
		{#each fruits as fruit (fruit.value)}
			<InputChip
				name="fruit"
				value={fruit.value}
				label={fruit.label}
				onremove={() => {
					fruits = fruits.filter((f) => f !== fruit)
					// With the last fruit gone there is no chip left to focus, so go back to the field.
					if (!fruits.length) input?.focus()
				}}
			/>
		{/each}
	</ChipSet>
</AutoComplete>

Long option lists

When more than virtualThreshold options are visible at once, the menu switches to a virtual list and only renders what is on screen. A list of thousands of entries stays responsive. The threshold defaults to 300. Lower it if your options are expensive to render. In this mode the menu has the width of the field, as with clampMenuWidth.

<AutoComplete
	label="City"
	options={cities}
	virtualThreshold={100}
	clampMenuWidth
/>

Accessibility

The field is a combobox: it renders role="combobox" with aria-expanded, aria-controls and aria-activedescendant, and the menu renders as a listbox whose options carry role="option". Focus stays in the input while the user moves through the list.

↓ and ↑ move through the suggestions and open the menu if it is closed. Home and End move the caret in the input. Once the arrow keys have moved into the suggestions, they jump to the first and the last one. Enter picks the active option. Escape closes the menu without changing the value. Typing reopens the menu with the filtered list.

Always pass a label. The field must make sense before the menu opens.

Theming

Auto complete has no tokens of its own. The field follows the text field tokens and the suggestion list follows the menu and list tokens.

<AutoComplete
	label="Fruits"
	options={fruitOptions}
	--np-outlined-text-field-focus-outline-color="var(--np-color-tertiary)"
	--np-menu-container-color="var(--np-color-surface-container-highest)"
/>

To style a single option, give it a class or a style. Use the style to set list item tokens for that option. Use a class to reach the parts of the item from your own CSS.

<script lang="ts">
	import { AutoComplete } from 'noph-ui'
</script>

<AutoComplete
	variant="filled"
	label="Fruits"
	name="fruit"
	options={[
		{ label: 'Apple' },
		{
			label: 'Banana',
			supportingText: 'Out of stock',
			style: '--np-item-supporting-text-color: var(--np-color-error)',
		},
		{ label: 'Cherry', supportingText: 'New', class: 'new-option' },
	]}
/>

<style>
	:global(.new-option .np-item-supporting-text) {
		font-style: italic;
	}
</style>

API

Attributes

Auto complete takes every TextField attribute in addition to the ones below.

AttributeTypeDefaultDescription
optionsAutoCompleteOption[][]The suggestions to offer.
optionsFilter(option: AutoCompleteOption) => booleanLabel contains the inputReplaces the built-in filtering. Use it to match on more than the label, or to hide options that have already been picked.
onoptionselect(option, menuElement) => voidWrites the label into the fieldCalled when an option is picked. Overriding it takes over what selecting does, including closing the menu.
openbooleanfalseBindable. Whether the suggestion menu is open. Set it to open or close the menu.
clampMenuWidthbooleanfalseFixes the menu to the width of the field. Without it the menu uses the field width as a minimum and grows for long labels.
virtualThresholdnumber300The menu renders as a virtual list when more than this many options are visible.

AutoCompleteOption

PropertyTypeDescription
labelstringRequired. Text of the option, and what is matched against the input.
valuestring | number | undefinedIdentifier of the option, for telling two equal labels apart in your own state.
supportingTextstring | Snippet | undefinedSecond line below the label. Pass a snippet to render your own markup.
classClassValue | undefinedClasses added to the option element.
stylestring | undefinedInline style of the option element. Use it to set list item tokens such as --np-item-supporting-text-color for a single option.
startSnippet | undefinedLeading content of the option, typically an icon or an avatar.
endSnippet | undefinedTrailing content of the option.