Compare
Component
Compare · 11 components with a popup

Combobox, five ways

Open one. Five follow. Each tile runs Combobox in its own framework and reports the change in its own syntax.

Combobox docs: demo, examples and API
Frameworks
5
Parts
26
Open now
0 of 5
Events
0

Live

Sync on · one tile drives all five

React

LoadingonOpenChange

Loading React…

Vue

Loading@open-change

Loading Vue…

Svelte

LoadingonOpenChange

Loading Svelte…

Angular

Loading(openChange)

Loading Angular…

Solid

LoadingonOpenChange

Loading Solid…

EventsNo changes yet

No changes yet. Use any tile.

The same example, in each framework

The showcase example for Combobox, as written in each framework. Lines that only React has are marked.

React · Combobox.tsx37 lines only in React
import { Combobox } from '@▢▢▢▢/react/combobox'

const fruits = ['Apple', 'Banana', 'Cherry', 'Grape', 'Lemon', 'Mango', 'Orange', 'Peach', 'Pear']

export default function ComboboxExample() {
  return (
    <Combobox.Root items={fruits}>
      <label className="field">
        <span className="select-label">Choose a fruit</span>
        <Combobox.InputGroup className="combobox">
          <Combobox.Input className="combobox-input" placeholder="e.g. Apple" />
          <Combobox.Clear
            className="combobox-button"
            aria-label="Clear selection"
          >
            ×
          </Combobox.Clear>
          <Combobox.Trigger
            className="combobox-button"
            aria-label="Open popup"
          >
            <Combobox.Icon className="select-icon" />
          </Combobox.Trigger>
        </Combobox.InputGroup>
      </label>
      <Combobox.Portal>
        <Combobox.Positioner className="select-positioner" sideOffset={6}>
          <Combobox.Popup className="select-popup combobox-popup">
            <Combobox.Empty className="combobox-empty">
              No fruits found.
            </Combobox.Empty>
            <Combobox.List className="select-list">
              <Combobox.Collection>
                {(fruit: string) => (
                  <Combobox.Item
                    key={fruit}
                    value={fruit}
                    className="select-item"
                  >
                    <Combobox.ItemIndicator
                      className="select-item-indicator"
                    >
                      ✓
                    </Combobox.ItemIndicator>
                    <span className="select-item-text">{fruit}</span>
                  </Combobox.Item>
                )}
              </Combobox.Collection>
            </Combobox.List>
          </Combobox.Popup>
        </Combobox.Positioner>
      </Combobox.Portal>
    </Combobox.Root>
  )
}
Combobox.vue
<script setup lang="ts">
import { Combobox } from '@▢▢▢▢/vue/combobox'

const fruits = ['Apple', 'Banana', 'Cherry', 'Grape', 'Lemon', 'Mango', 'Orange', 'Peach', 'Pear']
</script>

<template>
  <Combobox.Root :items="fruits">
    <label class="field">
      <span class="select-label">Choose a fruit</span>
      <Combobox.InputGroup class="combobox">
        <Combobox.Input class="combobox-input" placeholder="e.g. Apple" />
        <Combobox.Clear class="combobox-button" aria-label="Clear selection"
          >×</Combobox.Clear
        >
        <Combobox.Trigger class="combobox-button" aria-label="Open popup">
          <Combobox.Icon class="select-icon" />
        </Combobox.Trigger>
      </Combobox.InputGroup>
    </label>
    <Combobox.Portal>
      <Combobox.Positioner class="select-positioner" :side-offset="6">
        <Combobox.Popup class="select-popup combobox-popup">
          <Combobox.Empty class="combobox-empty"
            >No fruits found.</Combobox.Empty
          >
          <Combobox.List class="select-list">
            <Combobox.Collection v-slot="fruit">
              <Combobox.Item :key="fruit" :value="fruit" class="select-item">
                <Combobox.ItemIndicator class="select-item-indicator"
                  >✓</Combobox.ItemIndicator
                >
                <span class="select-item-text">{{ fruit }}</span>
              </Combobox.Item>
            </Combobox.Collection>
          </Combobox.List>
        </Combobox.Popup>
      </Combobox.Positioner>
    </Combobox.Portal>
  </Combobox.Root>
</template>

Measured, per framework

Combobox in each framework, side by side. Lower is better for size, time and memory. No outside baseline: the frameworks are compared with each other only.

MetricReactVueSvelteAngularSolid
SizeKiB gzip, with core and dom
Openms, median
Closems, median
MemoryMiB after one open and close
Testspassing
Measured
Nov 4 on Apple M3 Max, Chromium 156, 35 runs per framework over 5 page loads. The range under each time is the spread of the per-load medians.
Tests
Combobox's tests in each framework's suites, jsdom and Chromium together, from the latest run. React runs the React tests on the port; the other four also run the shared behaviour tests.
Sources
reports/size-baseline.json (size), reports/perf.json (shared code, time, memory) and reports/tests-by-component.json (tests).

Same props, five spellings

The Combobox.Root props as each framework writes them. A name that differs from React is marked.

React
Props and callbacks have upstream's names and signatures: onCheckedChange(checked, details), onOpenChange(open, details), onValueChange(value, details).
Vue
Props keep upstream's names and can be written in kebab case in a template (:default-checked, :side-offset). Upstream's callbacks are Vue events with the same arguments: checkedChange(checked, details), openChange(open, details), valueChange(value, details).
Svelte
Callbacks are props with upstream's names and arguments: onCheckedChange(checked, details), onOpenChange(open, details), onValueChange(value, details), with details.cancel(), reason and event. Native event props use Svelte's names (onclick, onkeydown).
Angular
Inputs are signal inputs with upstream's names (checked, disabled, nativeButton); boolean inputs also work as bare attributes. Each callback with details is an output named without on: checkedChange, openChange, valueChange.
Solid
Callbacks keep upstream's names and arguments: onCheckedChange(checked, details), with details.cancel() and details.event. Native handlers are camelCase (onClick, onKeyDown), as Solid 2 requires.
Prop · 38ReactVueSvelteAngularSolid
namepropnamenamename[name]name
defaultValuepropdefaultValuedefault-valuedefaultValue[defaultValue]defaultValue
valuepropvaluevaluevalue[value]value
onValueChangeeventonValueChange@value-changeonValueChange(valueChange)onValueChange
defaultInputValuepropdefaultInputValuedefault-input-valuedefaultInputValue[defaultInputValue]defaultInputValue
inputValuepropinputValueinput-valueinputValue[inputValue]inputValue
onInputValueChangeeventonInputValueChange@input-value-changeonInputValueChange(inputValueChange)onInputValueChange
defaultOpenpropdefaultOpendefault-opendefaultOpen[defaultOpen]defaultOpen
openpropopenopenopen[open]open
onOpenChangeeventonOpenChange@open-changeonOpenChange(openChange)onOpenChange
autoHighlightpropautoHighlightauto-highlightautoHighlight[autoHighlight]autoHighlight
highlightItemOnHoverprophighlightItemOnHoverhighlight-item-on-hoverhighlightItemOnHover[highlightItemOnHover]highlightItemOnHover
actionsRefpropactionsRefactions-refactionsRef[actionsRef]actionsRef
autoCompletepropautoCompleteauto-completeautoComplete[autoComplete]autoComplete
filterpropfilterfilterfilter[filter]filter
filteredItemspropfilteredItemsfiltered-itemsfilteredItems[filteredItems]filteredItems
formpropformformform[form]form
gridpropgridgridgrid[grid]grid
inlinepropinlineinlineinline[inline]inline
isItemEqualToValuepropisItemEqualToValueis-item-equal-to-valueisItemEqualToValue[isItemEqualToValue]isItemEqualToValue
itemToStringLabelpropitemToStringLabelitem-to-string-labelitemToStringLabel[itemToStringLabel]itemToStringLabel
itemToStringValuepropitemToStringValueitem-to-string-valueitemToStringValue[itemToStringValue]itemToStringValue
itemspropitemsitemsitems[items]items
limitproplimitlimitlimit[limit]limit
localeproplocalelocalelocale[locale]locale
loopFocusproploopFocusloop-focusloopFocus[loopFocus]loopFocus
modalpropmodalmodalmodal[modal]modal
multiplepropmultiplemultiplemultiple[multiple]multiple
onItemHighlightedeventonItemHighlighted@item-highlightedonItemHighlighted(itemHighlighted)onItemHighlighted
onOpenChangeCompleteeventonOpenChangeComplete@open-change-completeonOpenChangeComplete(openChangeComplete)onOpenChangeComplete
openOnInputClickpropopenOnInputClickopen-on-input-clickopenOnInputClick[openOnInputClick]openOnInputClick
virtualizedpropvirtualizedvirtualizedvirtualized[virtualized]virtualized
disabledpropdisableddisableddisabled[disabled]disabled
readOnlypropreadOnlyread-onlyreadOnly[readOnly]readOnly
requiredproprequiredrequiredrequired[required]required
inputRefpropinputRefinput-refinputRef[inputRef]inputRef
idpropididid[id]id
childrenpropchildren#defaultchildrenng-contentchildren