Searchable Dropdown Select With Search Box


A searchable dropdown improves the user experience when working with long option lists. Instead of scrolling through dozens or hundreds of options, users can simply type and instantly filter results.

hcg-searchable-select is a lightweight JavaScript library that transforms a standard HTML <select> element into a searchable dropdown while preserving native form behavior. It works without frameworks or dependencies and keeps the original select element synchronized for form submissions and validation.

Normal HTML select compared with hcg-searchable-select searchable dropdown with search box

Why Use a Searchable Select Dropdown?

Traditional HTML select elements become difficult to use when they contain a large number of options. A searchable select allows users to:

  • Find options instantly by typing
  • Navigate efficiently with the keyboard
  • Reduce scrolling on desktop and mobile devices
  • Improve usability for country, city, category, and product lists
  • Maintain clean and accessible forms

The library progressively enhances a normal HTML select, ensuring compatibility with existing forms and workflows.

Demo

Live examples of each type. Click a select to open it, then type to filter the options.

Basic searchable select

A standard dropdown with a search box. Start typing to filter a long list of options.


Grouped options (optgroup)

Options organized under non-selectable group headers using native <optgroup> elements.


Clearable selection

A clear button appears once a value is chosen, resetting the selection back to the placeholder.


Pre-selected value

A select that already has a value chosen on load, shown in the control.


Disabled select

A non-interactive control that cannot be opened, mirroring a disabled native select.

Features

The library includes many advanced features commonly found in premium select components:

  • Type-to-search filtering with match highlighting (<mark>)
  • Full keyboard support:
    • Closed + focused: ArrowUp / ArrowDown step the value like a native <select> (no popup)
    • Open: ArrowUp / ArrowDown move the highlight, Enter selects, Escape closes
  • Enter / Space / Alt+ArrowDown open the search panel
  • Native change event and onChange / onOpen / onClose callbacks
  • Works with any number of selects on one page
  • Accessible - ARIA combobox / listbox pattern with live aria-activedescendant
  • Progressive enhancement - the real <select> is kept for form submission
  • Respects disabled options and disabled selects
  • Mobile-aware - skips search auto-focus on touch devices so the keyboard does not cover the list
  • Handles duplicate values, placeholder vs clearable empty options, and form reset
  • Programmatic API: open(), close(), refresh(), destroy()
  • React-ready (bundled component + destroy() for clean unmount)
  • Accepts a CSS selector string or a DOM element
  • Namespaced hcg-select-* classes (no style collisions), themeable via CSS variables
  • Pure vanilla JS, zero dependencies, modern ES2015+

Installation

Install it with npm, load it from a CDN, or download the files directly and host them yourself. Once included, simply apply the library to a standard HTML <select> element to instantly add search functionality, keyboard navigation, and an improved user experience without changing your existing form structure.

npm

Command Line
npm install hcg-searchable-select

Package page: npmjs.com/package/hcg-searchable-select

Usage

JavaScript
import hcgSelect from "hcg-searchable-select";
import "hcg-searchable-select/hcg-select.css";

hcgSelect("#country");

CommonJS also works: const hcgSelect = require("hcg-select");

CDN (no build step)

Use jsDelivr or unpkg - both serve the package straight from npm. Drop these into your HTML and you're done (the script auto-initializes any select[data-hcg-select]):

HTML
<!-- jsDelivr -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/hcg-searchable-select@1/hcg-select.css">
<script src="https://cdn.jsdelivr.net/npm/hcg-searchable-select@1/hcg-select.js"></script>
HTML
<!-- unpkg -->
<link rel="stylesheet" href="https://unpkg.com/hcg-searchable-select@1/hcg-select.css">
<script src="https://unpkg.com/hcg-searchable-select@1/hcg-select.js"></script>

Pin a version for production (e.g. hcg-searchable-select@1.0.0) instead of @1, which floats to the latest 1.x. The global hcgSelect() is then available on window.

Direct download

Grab hcg-select.js and hcg-select.css from the repo and host them yourself. GitHub Repository

Browse the source code on GitHub or package from npm.

Usage

1. Include the files

HTML
<link rel="stylesheet" href="hcg-select.css">
<script src="hcg-select.js"></script>

2. Add a select

Add the data-hcg-select attribute. It is auto-initialized on page load.

HTML
<select id="country" data-placeholder="Select a country" data-hcg-select>
  <option value="">- Select a country -</option>
  <option value="in">India</option>
  <option value="us">United States</option>
  <option value="uk">United Kingdom</option>
</select>

That's it. The widget reads the options, hides the native select, and renders a searchable dropdown.

Manual initialization

For selects added dynamically, or to pass options:

JavaScript
hcgSelect("#country", {
  placeholder: "Select a country",
  searchPlaceholder: "Type to search...",
  noResultsText: "Nothing found"
});

hcgSelect accepts a CSS selector string or a DOM element, and returns an API:

JavaScript
const api = hcgSelect(document.getElementById("country"));
api.open();           // open the panel
api.close();          // close the panel
api.refresh();        // rebuild the list from the current <option>s, then re-sync label + disabled state
api.disable();        // disable the widget (sets the native select.disabled = true)
api.enable();         // enable the widget
api.setDisabled(b);   // set disabled state from a boolean
api.destroy();        // tear down the widget and restore the original native <select>
api.element;          // the wrapper element
api.select;           // the underlying <select>

disable() / enable() / setDisabled() set the native select.disabled (the single source of truth), so the widget, the native element, and your forms never disagree. Setting select.disabled directly works too - a MutationObserver keeps the widget in sync either way.

Call api.refresh() after you add, remove, rename, disable, enable, or reorder the native <option>s (or toggle the select own disabled) so the widget stays in sync.

Options

hcg-searchable-select includes a flexible set of configuration options that allow you to customize placeholders, search behavior, focus handling, messages, and event callbacks. These settings make it easy to adapt the dropdown to different user experiences while keeping the API simple and developer-friendly.

Option Default Description
placeholder "Select..." Text shown when nothing is selected. Falls back to data-placeholder.
searchPlaceholder "Search..." Placeholder text for the search input.
noResultsText "No results" Text shown when no option matches.
typeAhead true When the control is focused and closed, typing a letter jumps to the first matching option (native-select behavior). Set false to disable.
onChange - function(value, option, select) called when the user selects an option (after the native change event).
onOpen - function(select, api) called when the panel opens.
onClose - function(select, api) called when the panel closes.

Events and callbacks

You have two ways to react when the user picks an option - use whichever fits.

1. Native change event (works with any framework / form code)

The underlying native <select> always holds the current value and fires a bubbling change event when the user picks an option:

JavaScript
document.getElementById("country").addEventListener("change", function () {
  console.log(this.value);
});

2. Callback options

Pass callbacks when you initialize manually:

JavaScript
hcgSelect("#country", {
  onChange: function (value, option, select) {
    console.log("picked", value, option.text);
  },
  onOpen:  function (select, api) { /* panel opened */ },
  onClose: function (select, api) { /* panel closed */ }
});

onChange receives the new value, the selected <option> element, and the <select>. It fires after the native change event, so both mechanisms stay in sync.

Grouped options (optgroup)

Native <optgroup> elements are rendered as non-selectable group headers, with indented options grouped beneath them.

HTML
<select data-hcg-select>
  <option value="">- Pick food -</option>
  <optgroup label="Fruits">
    <option value="apple">Apple</option>
    <option value="mango">Mango</option>
  </optgroup>
  <optgroup label="Drinks">
    <option value="tea">Tea</option>
  </optgroup>
</select>

Clearable selection

Add data-clearable (or pass clearable: true) to show a clear button. Because a native select always has a value, clearing returns the selection to the placeholder option, so a placeholder must be present.

HTML
<select data-hcg-select data-clearable data-placeholder="Choose a city" id="city">
  <option value="">- Choose a city -</option>
  <option value="par">Paris</option>
  <option value="tok">Tokyo</option>
</select>

JavaScript usage:

JavaScript
hcgSelect("#city", {
  clearable: true
});

Keyboard and accessibility

When the control is focused and closed, arrow keys step through options like a native select, and typing performs type-ahead. When open, arrow keys move the highlight, Enter selects, Escape closes, and Home or End jump to the first or last option.

hcg-searchable-select follows the ARIA combobox and listbox pattern, exposing roles, aria-expanded, aria-activedescendant, and aria-selected so screen readers announce navigation correctly.

Mobile behavior

On touch devices the search input is not auto-focused when the dropdown opens, so the on-screen keyboard does not cover the options list. Users can scroll and tap an option, or tap the search box to start filtering.

Using it in React

The core is framework-agnostic vanilla JS, so it works in React via a ref + useEffect. The destroy() API method makes mount/unmount clean.

Option A: use the bundled component

JavaScript
import { useState } from "react";
import HcgSelect from "hcg-searchable-select/react/HcgSelect.jsx";
import "hcg-searchable-select/hcg-select.css";

function Example() {
  const [country, setCountry] = useState("in");
  return (
    <HcgSelect value={country} onChange={setCountry} placeholder="Select a country">
      <option value="">- Select a country -</option>
      <option value="in">India</option>
      <option value="us">United States</option>
      <option value="uk">United Kingdom</option>
    </HcgSelect>
  );
}

onChange receives (value, option, select). Changing value or the <option> children re-syncs the widget automatically.

Option B: wrap it yourself

JavaScript
import { useEffect, useRef } from "react";
import hcgSelect from "hcg-searchable-select";
import "hcg-searchable-select/hcg-select.css";

function CountrySelect({ value, onChange }) {
  const ref = useRef(null);
  const apiRef = useRef(null);

  useEffect(() => {
    apiRef.current = hcgSelect(ref.current, {
      onChange: (val) => onChange(val)
    });
    return () => apiRef.current && apiRef.current.destroy(); // clean unmount
  }, []);

  useEffect(() => {
    if (ref.current && ref.current.value !== value) {
      ref.current.value = value;
      apiRef.current.refresh();
    }
  }, [value]);

  return (
    <select ref={ref} defaultValue={value} data-placeholder="Select a country">
      <option value="">- Select a country -</option>
      <option value="in">India</option>
      <option value="us">United States</option>
    </select>
  );
}

Always call api.destroy() in the effect cleanup so React can unmount theoriginal <select> without leaving the generated wrapper behind.

Browser support

hcg-searchable-select works in all modern browsers, including Chrome, Firefox, Safari, and Edge.

License

hcg-searchable-select is open source and MIT licensed, created by HTML Code Generator.

Frequently Asked Questions (FAQ)

What is hcg-searchable-select?

hcg-searchable-select is a free, open-source JavaScript plugin that turns a standard HTML <select> element into a searchable dropdown. It adds a search box, type-to-search filtering, optgroup support, an optional clear button, keyboard navigation, and ARIA accessibility - all with zero dependencies.

Does hcg-searchable-select require jQuery or any dependencies?

No. It is pure vanilla JavaScript with zero dependencies and works in the browser or with any bundler.

Does it keep the native form value?

Yes. It enhances a real native select that stays in the DOM, so forms submit a normal value and validation keeps working.

Is hcg-searchable-select accessible?

Yes. It follows the ARIA combobox and listbox pattern with keyboard navigation and screen reader support.

Does it support optgroups and a clear button?

Yes. Native optgroup elements become group headers, and an optional clear button returns the selection to the placeholder option.