Component

<dom-combobox>

custom element

Select-like combobox with a text input, optional toggle button, managed activedescendant, keyboard navigation, and floating list positioning.

Demo

Select-like search

Select an option value while showing its label in the input. The list still supports filtering, arrow keys, Enter, Esc, and floating positioning.

basic.htmlhtml
<dom-combobox class="relative block w-full max-w-sm">
	<input
		slot="input"
		type="text"
		placeholder="Search fruits…"
		class="h-10 w-full rounded-full border border-border bg-background px-4 pr-10 text-sm text-fg outline-none focus:ring-2 focus:ring-ring/40"
	/>
	<button
		slot="toggle"
		type="button"
		aria-label="Show options"
		class="absolute right-1 top-1 inline-flex size-8 items-center justify-center rounded-full text-muted-fg hover:bg-secondary"
	>
		<svg viewBox="0 0 20 20" class="size-4" fill="none">
			<path d="M6 8l4 4 4-4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" />
		</svg>
	</button>
	<ul slot="list" class="z-50 max-h-[min(12rem,var(--dom-floating-available-height))] overflow-auto rounded-2xl border border-border bg-background p-1 shadow-lg">
		<li data-value="1" data-label="Apple" class="cursor-pointer rounded-xl px-3 py-2 text-sm text-fg hover:bg-secondary">Apple</li>
		<li data-value="2" data-label="Banana" class="cursor-pointer rounded-xl px-3 py-2 text-sm text-fg hover:bg-secondary">Banana</li>
		<li data-value="3" data-label="Cherry" class="cursor-pointer rounded-xl px-3 py-2 text-sm text-fg hover:bg-secondary">Cherry</li>
	</ul>
</dom-combobox>

<script type="module">
	import '@getdom/studio/headless/combobox.js';
</script>

Demo

Rich option markup

List items can be authored directly in the DOM with images and nested labels. data-label controls the text shown in the input, and data-value is the selected value.

people.htmlhtml
<dom-combobox class="relative block w-full max-w-md">
	<input
		slot="input"
		type="text"
		placeholder="Assign a person…"
		class="h-10 w-full rounded-full border border-border bg-background px-4 pr-10 text-sm text-fg outline-none focus:ring-2 focus:ring-ring/40"
	/>
	<button
		slot="toggle"
		type="button"
		aria-label="Show people"
		class="absolute right-1 top-1 inline-flex size-8 items-center justify-center rounded-full text-muted-fg hover:bg-secondary"
	>
		<svg viewBox="0 0 20 20" class="size-4" fill="none">
			<path d="M6 8l4 4 4-4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" />
		</svg>
	</button>
	<ul slot="list" class="z-50 max-h-[min(14rem,var(--dom-floating-available-height))] overflow-auto rounded-2xl border border-border bg-background p-1 shadow-lg">
		<li data-value="ada" data-label="Ada Lovelace" class="cursor-pointer rounded-xl px-3 py-2 text-sm text-fg transition hover:bg-secondary data-[active]:bg-secondary aria-selected:bg-secondary">
			<div class="flex items-center gap-3">
				<img src="https://unavatar.io/github/adafruit" alt="" class="size-8 rounded-full bg-secondary object-cover" />
				<span class="min-w-0">
					<span class="block truncate font-medium">Ada Lovelace</span>
					<span class="block truncate text-xs text-muted-fg">Mathematician</span>
				</span>
			</div>
		</li>
		<li data-value="grace" data-label="Grace Hopper" class="cursor-pointer rounded-xl px-3 py-2 text-sm text-fg transition hover:bg-secondary data-[active]:bg-secondary aria-selected:bg-secondary">
			<div class="flex items-center gap-3">
				<img src="https://unavatar.io/github/ghopper" alt="" class="size-8 rounded-full bg-secondary object-cover" />
				<span class="min-w-0">
					<span class="block truncate font-medium">Grace Hopper</span>
					<span class="block truncate text-xs text-muted-fg">Computer scientist</span>
				</span>
			</div>
		</li>
		<li data-value="katherine" data-label="Katherine Johnson" class="cursor-pointer rounded-xl px-3 py-2 text-sm text-fg transition hover:bg-secondary data-[active]:bg-secondary aria-selected:bg-secondary">
			<div class="flex items-center gap-3">
				<img src="https://unavatar.io/github/nasa" alt="" class="size-8 rounded-full bg-secondary object-cover" />
				<span class="min-w-0">
					<span class="block truncate font-medium">Katherine Johnson</span>
					<span class="block truncate text-xs text-muted-fg">NASA mathematician</span>
				</span>
			</div>
		</li>
	</ul>
</dom-combobox>

<script type="module">
	import '@getdom/studio/headless/combobox.js';
</script>

Usage

Plain HTML

Register every <dom-*> in one import: import '@getdom/studio/headless'.

Slots

NameScopeDescription
#inputThe text input. Gets role="combobox" and aria-controls wired automatically.
#toggleOptional button that opens the list like a select.
#listThe listbox container. Each option must carry data-value.

Attributes

NameTypeDescription
valuestringCurrently-selected value.
openbooleanReflects whether the list is visible.
placementstringPreferred floating placement before collision handling.
floating-mode'viewport' | 'anchor'viewport keeps the list inside the browser; anchor keeps it attached to the input while scrolling.
data-menu-idstringId of an external or teleported list element.

From dom-combobox.__doc.attributes.

Events

NamePayloadDescription
dom:input{ value }Fired whenever the text value changes.
dom:query{ query }Fired whenever the user types. Useful for server lookups.
dom:select{ value, label, option }Fired when an option is selected.
dom:change{ value, option, custom }Fired when an option is committed.

Names auto-detected from defineEmits and source emit() calls; payload and description from __doc.events when present.

Keyboard

  • ↑ / ↓Move active option.
  • EnterCommit active option.
  • EscClose the list.
  • TypeFilter the list and emit dom:query.

Related

See also

← Headless overview