Skip to content

Combobox Component

The Combobox component provides a powerful searchable select input with autocomplete functionality built with Vue 3 and Headless UI. It supports local options, asynchronous data loading, single/multiple selection, custom rendering, and various customization options.

Installation

To use the Combobox component you need to install the @vueuse/core and @headlessui/vue packages.

bash
yarn add @robuust-digital/vue-components @vueuse/core @headlessui/vue

Import the component from the combobox package

js
import { Combobox } from '@robuust-digital/vue-components/combobox';

Import CSS

css
@import "@robuust-digital/vue-components/combobox/css";

Basic Usage

vue
<template>
  <Combobox
    v-model="single"
    id="basic-example"
    :on-search="mockSearch"
    placeholder="Search fruits..."
  />
</template>

Local Options

Pass an options array to enable local mode. The full list is shown when the combobox receives focus, and typing filters the options using their optionText. No endpoint or onSearch callback is required.

vue
<template>
  <Combobox
    v-model="local"
    id="local-example"
    :options="mockItems"
    placeholder="Search fruits..."
  />
</template>

When both options and onSearch are supplied, local options take precedence. The minLength, debounce, minLoadingTime, endpoint, and cancellation props only affect remote searches.

API Integration Example

Real-world example using JSONPlaceholder API to search users:

vue
<template>
  <Combobox
    v-model="apiUsers"
    id="api-example"
    endpoint="users"
    :on-search="comboboxSearch"
    :display-value="customDisplayValue"
    :option-text="customOptionText"
    placeholder="Search users..."
    clearable
  />
</template>

<script setup>
/**
 * API search example
 * 
 * @param {String} searchValue - Search query
 * @param {Object} params - Additional request params
 * @param {String} endpoint - API endpoint
 */
const comboboxSearch = async (searchValue, params, endpoint) => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/${endpoint}${searchValue ? `?username=${searchValue}` : ''}`
  );

  if (response.ok) {
    const data = await response.json();
    return { data };
  }

  return { data: [], error: 'An error occurred' };
};
</script>

Props

PropTypeDefaultDescription
idStringRequiredUnique identifier for the combobox
modelValueAnynullv-model binding value
optionsArrayundefinedOptions to display and filter locally
endpointStringundefinedAPI endpoint for search requests
requestParamsObject{}Additional params for API requests
onSearchFunction() => Promise.resolve({ data: [] })Callback used for remote searches
manualInputBooleanfalseAllow manual input without selection
responseDataFunctiondata => dataTransform API response data
displayValueFunctionitem => item?.nameFormat selected value display
optionTextFunctionitem => item?.nameFormat option text in dropdown
disabledBooleanfalseDisable the combobox
minLengthNumber2Minimum characters before search
itemKeyString'id'Unique key for options
clearableBooleanfalseShow clear button
multipleBooleanfalseEnable multiple selection
searchOnlyBooleanfalseSearch-only mode without selection
iconObject, FunctionnullCustom icon component
prefixIconObject, FunctionnullCustom left icon component
sizeString'base'Size variant ('sm' or 'base')
debounceNumber150Debounce time for search in milliseconds
minLoadingTimeNumber0Minimum loading indicator display time
onCancelFunctionnullCallback to cancel pending requests

Events

EventPayloadDescription
update:modelValueAnyEmitted when selection changes
update:requestParamsObjectEmitted when request params change
combobox:noResultsStringEmitted when search returns no results
combobox:errorObject, StringEmitted when search encounters an error

Advanced Examples

Multiple Selection with Custom Display

vue
<template>
  <Combobox
    v-model="multiple"
    id="multiple-custom"
    :on-search="comboboxSearch"
    endpoint="users"
    multiple
    clearable
    :display-value="customDisplayValue"
    :option-text="customOptionText"
    placeholder="Select multiple users..."
  >
    <template #chip="{ optionText, option, removeOption }">
      <Badge color="blue" size="sm">
        {{ optionText }}
        <button @click="removeOption(option)">×</button>
      </Badge>
    </template>
  </Combobox>
</template>

Custom Option Rendering

vue
<template>
  <Combobox
    v-model="single"
    id="custom-option"
    :on-search="comboboxSearch"
    endpoint="users"
    clearable
  >
    <template #option="{ option }">
      <div class="flex flex-col">
        <strong>{{ option.username }}</strong>
        <small class="text-slate-500">{{ option.email }}</small>
      </div>
    </template>
  </Combobox>
</template>

Request Cancellation

Example showing how to cancel pending requests when a new search is initiated:

vue
<template>
  <Combobox
    v-model="single"
    id="cancel-example"
    :on-search="searchWithCancel"
    :on-cancel="(controller) => controller.abort()"
    placeholder="Search with cancellation..."
  />
</template>

<script setup>
/**
 * API search example with request cancellation
 * 
 * @param {String} searchValue - Search query
 * @param {Object} params - Additional request params
 * @param {String} endpoint - API endpoint
 */
const searchWithCancel = async (searchValue, params, endpoint) => {
  const controller = new AbortController();
  const { signal } = controller;

  const response = await fetch(`https://jsonplaceholder.typicode.com/${endpoint}${searchValue ? `?username=${searchValue}` : ''}`, { signal });
  const data = await response.json();

  return {
    data,
    cancel: controller,
  };
};
</script>

This example demonstrates how to implement request cancellation when a new search is initiated before the previous one completes.

Slots

SlotPropsDescription
prefixIcon{ icon }Custom left icon
icon{ icon }Custom right icon
spinner{ spinning }Custom loading indicator
clear-Custom clear button
chip{ optionText, option, removeOption }Custom chip for multiple selection
option{ option, isActive }Custom option rendering
optionSuffix{ option, isActive }Additional option metadata
optionPrefix{ option, isActive }Prefix metadata for options

Improved Documentation

We are planning to provide more examples and advanced use cases for the Combobox component. Stay tuned!

CSS Customization ⚡️

To customize the combobox styles global

css
:root {
  /* Available variables */
  --rvc-combobox-border-radius: var(--rvc-base-border-radius);
  --rvc-combobox-border-width: var(--rvc-base-border-width);
  --rvc-combobox-border-color: var(--rvc-base-border-color);
  --rvc-combobox-font-size: var(--rvc-base-input-font-size);
  --rvc-combobox-font-weight: var(--rvc-base-input-font-weight);
  --rvc-combobox-box-shadow: var(--rvc-base-box-shadow);
  --rvc-combobox-color: var(--rvc-base-input-color);
  --rvc-combobox-bg-color: var(--rvc-base-input-bg-color);
  --rvc-combobox-bg-color-disabled: var(--rvc-base-input-bg-color-disabled);
  --rvc-combobox-disabled-opacity: var(--rvc-base-input-disabled-opacity);
  --rvc-combobox-padding-x: var(--rvc-base-input-padding-x);
  --rvc-combobox-height: var(--rvc-base-input-height);
  --rvc-combobox-icon-size: var(--rvc-base-input-icon-size);
  --rvc-combobox-icon-color: var(--rvc-base-input-icon-color);
  --rvc-combobox-icon-loading-animation: var(--rvc-base-loading-animation);
  --rvc-combobox-placeholder-color: var(--rvc-base-input-placeholder-color);
  --rvc-combobox-clear-color: var(--rvc-base-input-icon-color);
  --rvc-combobox-clear-color-hover: var(--color-slate-700);
  --rvc-combobox-chip-icon-size: calc(var(--spacing) * 4);
  --rvc-combobox-chip-color: var(--rvc-base-input-color);
  --rvc-combobox-chip-color-hover: var(--color-slate-700);
  --rvc-combobox-chips-spacing: calc(var(--spacing) * 2);
  --rvc-combobox-options-offset: calc(var(--spacing) * 1);
  --rvc-combobox-options-z-index: 10;
  --rvc-combobox-options-max-height: calc(var(--spacing) * 60);
  --rvc-combobox-options-padding-x: 0.1875rem;
  --rvc-combobox-options-padding-y: 0.1875rem;
  --rvc-combobox-option-padding-x: calc(var(--spacing) * 2);
  --rvc-combobox-option-padding-y: calc(var(--spacing) * 1.5);
  --rvc-combobox-option-bg-color-hover: var(--color-slate-100);
  --rvc-combobox-option-bg-color-active: transparent;
  --rvc-combobox-option-color-active: var(--rvc-base-input-color);
  --rvc-combobox-option-font-weight-active: var(--font-weight-semibold);

  /* Small variant */
  --rvc-combobox-height-sm: 1.875rem;
  --rvc-combobox-font-size-sm: calc(var(--rvc-base-input-font-size) * 0.9);
  --rvc-combobox-padding-x-sm: calc(var(--rvc-base-input-padding-x) * 0.85);
  --rvc-combobox-icon-size-sm: calc(var(--rvc-base-input-icon-size) * 0.85);
}

Roadmap

  • Add more examples and advanced use cases
  • Improve Docs with more detailed explanations
  • Implement infinite loading for large datasets