Searchable Select Dropdown for HTML Select Elements


By HTML Code Generator

Turn any native <select> into a searchable dropdown without changing how your form works. hcg-searchable-select is a zero-dependency JavaScript library (~6 KB gzipped) that adds type-to-search filtering, match highlighting, keyboard navigation, and full ARIA accessibility while keeping the real select in the DOM for submission and validation.

What is hcg-searchable-select?

hcg-searchable-select is a lightweight vanilla JavaScript library for making standard HTML select elements searchable. Add a data-hcg-select attribute or call hcgSelect() on a selector, and users can filter long option lists by typing in a search box - without React, jQuery, or any other dependency.

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

Why use hcg-searchable-select?

Long dropdowns are slow to scan on desktop and painful on mobile. Most searchable-select libraries bundle remote data, tagging, and multi-select you may not need. hcg-searchable-select stays small (~6 KB gzipped, single-select only) and focuses on one job: make a normal single-select searchable while preserving native form behavior - no jQuery, no framework.

  • Zero dependencies - one JS file and one CSS file, nothing else.
  • Native form parity - the real <select> stays in the DOM for submission, name, labels, and form.reset(). The visible control mirrors aria-required and aria-invalid.
  • Type-to-search - filter options as you type with match highlighting.
  • Optgroup support - native <optgroup> elements become group headers.
  • Clearable selection - optional clear button returns to a placeholder option.
  • Keyboard and screen reader friendly - ARIA combobox and listbox roles, arrow-key navigation, type-ahead while closed.
  • Mobile-aware - search input is not auto-focused on open, so the on-screen keyboard does not cover the list.
  • Smart positioning - panel auto-flips upward when there is not enough room below.
  • Dynamic options - adding, removing, or renaming options rebuilds the list automatically.
  • One-attribute setup - add data-hcg-select to markup you already have.

Features

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

  • Type-to-search filtering with match highlighting in the dropdown.
  • Native optgroup support - group headers hide when all their options are filtered out.
  • Optional clear button - returns selection to the first empty-value placeholder option.
  • Keyboard navigation - arrow keys, Home/End, Enter, Escape, and type-ahead while closed.
  • ARIA accessibility - combobox and listbox roles, aria-activedescendant, aria-selected.
  • Form validation - required, aria-required, and aria-invalid mirror the native select.
  • Disabled options and selects - respected and synced at runtime.
  • Smart auto-flip - panel opens upward when there is not enough room below.
  • Mobile-friendly - search input is not auto-focused on open.
  • Dynamic option - DOM changes rebuild the list via MutationObserver.
  • Programmatic value sync - setting select.value or selectedIndex updates the label.
  • Multiple selects - initialize as many widgets as you need on one page.
  • Instance API - open, close, refresh, clear, enable, disable, and destroy.
  • Tiny footprint - about 6 KB JavaScript gzipped, ~8 KB total with CSS.

Comparison Table

How hcg-searchable-select compares with common alternatives for single-select search.

Feature hcg-searchable-select Select2 Choices.js Tom Select
DependenciesNoneRequires jQueryNoneNone
Size (JS, gzipped)~6 KBLarger, plus jQueryLargerLarger
Keeps native select for formsYesYesYesYes
Optgroup supportYesYesYesYes
Multi-select / taggingNo (by design)YesYesYes
Remote data (AJAX)NoYesYesYes
Best forLightweight searchable single selectjQuery projectsText inputs + selectsFeature-rich selects

Demo

Try the selects below. Click to open, then type to filter. For every demo in one place, see the full interactive demo page.

Basic searchable select

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

Grouped options Select DropDown

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

Clearable selection DropDown

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 DropDown

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

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.

Direct download

Browse the source code on GitHub or package from npm.


npm
Command Line
npm install 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.

Usage

1. Include the files

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

2. Add a select

The quickest way is to add the data-hcg-select attribute. Selects with this attribute are enhanced automatically when the page loads.

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

Call hcgSelect() with a CSS selector or DOM element and optional settings. Calling it again on the same select returns the existing instance.

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

Re-initialize elements added later

JavaScript
hcgSelect.init();              // scan the whole document
hcgSelect.init(containerEl);   // scan inside a specific element

Data attributes

Common options can be set in HTML without JavaScript.

AttributeMaps toExample
data-hcg-selectenables auto-initdata-hcg-select
data-placeholderplaceholderdata-placeholder="Select a country"
data-clearableclearabledata-clearable or data-clearable="false"

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.

OptionTypeDefaultDescription
placeholderstring"Select..."Text shown when nothing is selected (or use data-placeholder).
searchPlaceholderstring"Search..."Placeholder for the search input inside the panel.
noResultsTextstring"No results"Message when no option matches the filter.
clearablebooleanfalseShow a clear button (requires a placeholder option).
typeAheadbooleantrueJump to a matching option by typing while the panel is closed.
onChangefunction-Called as (value, option, select) when a value is committed.
onOpenfunction-Called as (select, api) when the panel opens.
onClosefunction-Called as (select, api) 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.

Instance methods API

Calling hcgSelect() on a <select> returns an API object. Calling it again on the same element returns the existing instance (no duplicate widget).

API
Method Description
open() Open the dropdown panel.
close() Close the dropdown panel.
refresh() Rebuild the option list from the native <select>, then re-sync the label, disabled state, and required mirror.
clear() Reset to the first placeholder option (<option value="">). Works when clearable is enabled and a placeholder option exists.
disable() Disable the widget (select.disabled = true).
enable() Enable the widget (select.disabled = false).
setDisabled(bool) Set disabled state from a boolean. Same as disable() / enable().
destroy() Remove the widget, restore the original native <select>, and clean up listeners.
Properties
Property Description
api.element The wrapper DOM node (the visible .hcg-select widget).
api.select The underlying native <select> (used for form submission).
Static helper
Method Description
hcgSelect.init() Auto-initialize every select[data-hcg-select] in the document.
hcgSelect.init(container) Auto-initialize only inside a container element (useful after adding markup with AJAX).
Example
JavaScript
const api = hcgSelect(document.getElementById("country"), {
	clearable: true,
	onChange: function (value) {
		console.log("Selected:", value);
	}
});

api.open();
api.close();
api.clear();
api.refresh();
api.setDisabled(true);
api.enable();

// DOM references
console.log(api.element);  // wrapper
console.log(api.select);   // native <select>

// cleanup (e.g. SPA route change)
api.destroy();

// after adding new selects to the page
hcgSelect.init(document.getElementById("my-form"));

Grouped options (optgroup)

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

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 when a real value is selected. Because a native select always has a value, clearing returns to the first placeholder option (<option value="">), so that option must exist.

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" selected>Tokyo</option>
</select>

JavaScript usage:

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

Keyboard and accessibility

hcg-searchable-select follows the ARIA combobox and listbox pattern. The hidden native select uses inert when supported so it never holds focus; associated labels focus the custom control instead.

When the control is focused and closed

KeyAction
ArrowDown / ArrowUpStep to the next or previous selectable option (skips disabled and placeholder).
Typing a letterType-ahead: jump to the first option starting with the typed characters.
Enter / Space / Alt+ArrowDownOpen the search panel.
Delete / BackspaceClear selection when clearable is enabled.

When the panel is open

KeyAction
TypeFilter the option list in the search input.
ArrowDown / ArrowUpMove the highlight (wraps); does not commit until Enter.
Home / EndHighlight the first or last selectable option.
EnterSelect the highlighted option and close.
EscapeClose the panel and return focus to the control.
TabMove focus out, which closes the panel.

Native parity notes

  • Disabled options (<option disabled>) are greyed out and cannot be selected.
  • Disabled select (<select disabled>) renders a non-interactive widget. Toggling select.disabled at runtime is reflected automatically.
  • Placeholder vs empty value: only the first option with an empty value is treated as the placeholder and hidden from the list.
  • Programmatic changes: setting select.value or select.selectedIndex updates the displayed label; form.reset() restores it.
  • Dynamic options: adding, removing, renaming, or disabling options rebuilds the list automatically.
  • Multiple selects (<select multiple>) are not supported and are left untouched.

Mobile behavior

When the dropdown opens, the search input is not focused automatically, so the on-screen keyboard does not cover the options list. Users can scroll and tap an option, or tap the search box when they want to filter.

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.

Live React demo on StackBlitz: https://stackblitz.com/edit/hcg-searchable-select-react

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>
  );
}

Alternatively, call hcgSelect(ref.current) inside useEffect and destroy() in the cleanup function.

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.

Styling and customization

All default styles live in hcg-select.css, using CSS custom properties on .hcg-select. Override from your own stylesheet loaded after it:

CSS
.hcg-select {
  --hcg-select-border: #cbd5e1;
  --hcg-select-border-focus: #2563eb;
  --hcg-select-bg: #fff;
  --hcg-select-hover: #eff6ff;
  --hcg-select-active: #dbeafe;
  --hcg-select-text: #1e293b;
  --hcg-select-muted: #64748b;
  --hcg-select-mark: #fde047;
  --hcg-select-radius: 8px;
  --hcg-select-z: 9999;
  --hcg-select-font-size: 15px;
}

Animations respect prefers-reduced-motion: reduce.

Browser support

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

License

hcg-searchable-select is Released under the MIT License. Free for personal and commercial use. Copyright 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.

How do I use a searchable select in React?

Import the bundled wrapper component and use it like a normal select: import HcgSelect from "hcg-searchable-select/react/HcgSelect.jsx". It initializes the widget on mount, cleans up on unmount, and exposes an onChange prop. See the Using it in React section for full examples.

Does it support multi-select?

No. hcg-searchable-select is deliberately single-select to stay small (~6 KB gzipped). A <select multiple> is left untouched, keeping its native behavior. If you need multi-select with tagging, use a larger library such as Tom Select or Choices.js - see the comparison section.

Is hcg-searchable-select accessible?

Yes. It follows the ARIA combobox and listbox pattern with keyboard navigation and screen reader support. See the keyboard and accessibility section for shortcuts.

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.