4 Pure CSS4 with JSPublished 2026-10-04Free · no sign-up
Eight select menus, from the most robust styled native select to a fully custom listbox: an icon tile beside a native select, a tag-based multi-select, a compact pill, grouped options, a type-to-filter datalist and the new customisable select using appearance: base-select.
The most robust custom select: a real select element with appearance: none, a custom border and an SVG chevron as a background image. It keeps the native option list, keyboard behaviour and mobile pickers, which a fully custom dropdown cannot match.
A fully custom select following the ARIA select-only combobox pattern: the button shows the choice, the list opens below with role="listbox", and arrow keys, Enter, Escape and type-to-jump all work. Use it only when a native select cannot do the job.
const btn = document.getElementById('lb-b'), list = document.getElementById('lb-o'), opts = [...list.children];
let act = 0;
function open(o) { list.hidden = !o; btn.setAttribute('aria-expanded', String(o)); if (o) mark(opts.findIndex(x => x.getAttribute('aria-selected') === 'true')); }
function mark(i) { act = (i + opts.length) % opts.length; opts.forEach((o, k) => { o.classList.toggle('act', k === act); o.id = 'lbo' + k; }); btn.setAttribute('aria-activedescendant', 'lbo' + act); }
function pick(i) { opts.forEach((o, k) => o.setAttribute('aria-selected', String(k === i))); btn.textContent = opts[i].textContent; open(false); btn.removeAttribute('aria-activedescendant'); }
btn.addEventListener('click', () => open(list.hidden));
btn.addEventListener('keydown', e => {
if (list.hidden && (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); open(true); return; }
if (list.hidden) return;
if (e.key === 'ArrowDown') { e.preventDefault(); mark(act + 1); }
else if (e.key === 'ArrowUp') { e.preventDefault(); mark(act - 1); }
else if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); pick(act); }
else if (e.key === 'Escape') { open(false); }
else if (e.key.length === 1) { const i = opts.findIndex(o => o.textContent.toLowerCase().startsWith(e.key.toLowerCase())); if (i > -1) mark(i); }
});
opts.forEach((o, i) => o.addEventListener('click', () => { pick(i); btn.focus(); }));
document.addEventListener('click', e => { if (!e.target.closest('.lb')) open(false); });
03. Select with Flag Icons
IconsNative
A currency picker that shows a symbol tile beside a native select. The tile is updated from the change event, so the select keeps all native behaviour while the visible design includes an icon, a trick that avoids rebuilding the dropdown.
A multi-select presented as removable tags above a list of checkboxes. Ticking a box adds a tag, each tag's remove button unticks it, and the remove buttons are named Remove Linen and so on, so the control works fully from the keyboard.
A small inline select styled as a pill, for sort and filter bars where a full-width field would be too heavy. The visible text is the label plus the current value, and the native select sits on top invisibly so the whole pill opens the menu.
A native select with optgroup headings to organise a long list, here shipping services grouped by carrier. Group labels are announced by screen readers and cannot be selected, and the dark styling uses color-scheme so the open menu matches.
View code
<div class="gs">
<label for="gs-s">Shipping service</label>
<select id="gs-s">
<optgroup label="Standard"><option>Postal, 3 to 5 days</option><option>Courier, 2 to 3 days</option></optgroup>
<optgroup label="Express"><option>Next day</option><option>Same day (city only)</option></optgroup>
<optgroup label="Collection"><option>Pick up in store</option></optgroup>
</select>
</div>
A text input with a datalist, which gives a type-to-filter suggestion list using only HTML. It suits long lists such as cities or job titles, people can still type a value that is not on the list, and the browser draws an accessible suggestion menu.
View code
<div class="dl">
<label for="dl-i">Job title</label>
<input id="dl-i" list="dl-l" placeholder="Start typing">
<datalist id="dl-l"><option value="Product designer"><option value="Product manager"><option value="Frontend developer"><option value="Backend developer"><option value="Data analyst"><option value="Marketing lead"></datalist>
<small>Pick a suggestion or type your own.</small>
</div>
08. Customisable Select with appearance: base-select
ModernExperimental
The new appearance: base-select lets you style a real select's button and option list with CSS, including icons in options, while keeping native behaviour. Browsers without support fall back to an ordinary select, so it is safe to use today.
View code
<div class="bs">
<label for="bs-s">Status</label>
<select id="bs-s">
<option value="todo">○ To do</option>
<option value="doing" selected>◐ In progress</option>
<option value="done">● Done</option>
</select>
</div>
A real <select> brings keyboard support, screen reader support and the phone's own picker. With appearance: none and a background chevron you can match your design while keeping all of that.
When to build your own
Build a custom listbox only when the options need rich content. Then follow the ARIA select-only combobox pattern: a button with role="combobox", a role="listbox" list, aria-activedescendant, and full keyboard support including type-to-jump.
The new customisable select
appearance: base-select lets you style a real select's button and option list, including the open picker through ::picker(select). Wrap the styles in @supports, and older browsers simply show a normal select.
Long lists
For long lists such as countries, a datalist gives type-to-filter suggestions with no script, and optgroups organise options into labelled sections.
Frequently asked questions
Is a custom dropdown accessible?
Only if it implements the full ARIA pattern. A styled native select is accessible by default.
Can a select hold icons?
Not in most browsers yet. Use base-select where supported, or show an icon beside the select as in the icon demo.
Which demos need JavaScript?
The listbox, icon, multi-select and pill demos. The others are HTML and CSS.