worse-select

Native-first, dependency-free custom selects.

Basic

<select>
  <option value="" disabled selected>Choose one</option>
  <option value="ford">Ford</option>
  <option value="honda">Honda</option>
  ...
</select>

The placeholder clears when the dropdown opens and reappears when it closes without a selection.

Search

<select data-searchable="true">
  <option value="" disabled selected>
    Choose a country
  </option>
  <option>Australia</option>
  <option>Canada</option>
  ...
</select>

Matches are highlighted. Screen readers receive a live announcement of the result count.

Disabled

<select disabled>
  <option selected>North America</option>
  <option>Europe</option>
  <option>Asia Pacific</option>
</select>

Native disabled attribute. No widget-specific API needed.

Listbox

<select size="6" data-searchable="true">
  <option>Critical</option>
  <option>High</option>
  <option>Medium</option>
  ...
</select>

size > 1 triggers listbox mode — the native attribute stays native.

Multi-select

<select multiple size="8"
        data-searchable="true">
  <option>Delaware</option>
  <option>Maryland</option>
  ...
</select>

multiple stays native. Space or Enter toggles individual options.

Dynamic options

const select = document.querySelector('select');

// Add
const opt = document.createElement('option');
opt.textContent = 'Delta';
select.appendChild(opt);

// Remove
select.remove(select.options.length - 1);

A MutationObserver keeps the widget in sync — no manual refresh call needed.

Form integration

The native <select> stays in the DOM. FormData, form submission, and constraint validation work without any extra wiring.