22 CSS Dropdown Menu Designs 20 / 22
Autocomplete Suggestion Dropdown
A live autocomplete input that filters a dataset and shows a suggestion dropdown as you type, with keyboard selection and highlighted match prefix.
The code
<div class="dd-20">
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600&display=swap" rel="stylesheet">
<div class="dd-20__card">
<p class="dd-20__title">🌎 Where to?</p>
<p class="dd-20__sub">Search for a destination</p>
<div class="dd-20__field" id="dd-20-field">
<span class="dd-20__field-icon">🔍</span>
<input class="dd-20__input" id="dd-20-input" type="text" placeholder="Type a city or country…" autocomplete="off">
<button class="dd-20__clear" id="dd-20-clear" aria-label="Clear">×</button>
</div>
<ul class="dd-20__list" id="dd-20-list" role="listbox"></ul>
<p class="dd-20__hint" id="dd-20-hint">Try: "Tok", "Par", "Bar"</p>
</div>
</div> <div class="dd-20">
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600&display=swap" rel="stylesheet">
<div class="dd-20__card">
<p class="dd-20__title">🌎 Where to?</p>
<p class="dd-20__sub">Search for a destination</p>
<div class="dd-20__field" id="dd-20-field">
<span class="dd-20__field-icon">🔍</span>
<input class="dd-20__input" id="dd-20-input" type="text" placeholder="Type a city or country…" autocomplete="off">
<button class="dd-20__clear" id="dd-20-clear" aria-label="Clear">×</button>
</div>
<ul class="dd-20__list" id="dd-20-list" role="listbox"></ul>
<p class="dd-20__hint" id="dd-20-hint">Try: "Tok", "Par", "Bar"</p>
</div>
</div>.dd-20, .dd-20 *, .dd-20 *::before, .dd-20 *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
.dd-20 ::selection { background: #0ea5e9; color: #fff; }
.dd-20 {
--brand: #0ea5e9;
--surface: #fff;
--text: #0f172a;
--muted: #64748b;
--border: #e2e8f0;
--hover: #f0f9ff;
font-family: 'DM Sans', sans-serif;
min-height: 380px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(160deg, #f0f9ff 0%, #bae6fd 100%);
padding: 40px 20px;
}
.dd-20__card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 20px;
padding: 28px 24px;
width: 100%;
max-width: 380px;
box-shadow: 0 8px 40px rgba(14,165,233,.15);
position: relative;
}
.dd-20__title { font-size: 20px; font-weight: 700; color: var(--text); margin-bottom: 4px; }
.dd-20__sub { font-size: 13px; color: var(--muted); margin-bottom: 18px; }
.dd-20__field {
display: flex;
align-items: center;
gap: 8px;
border: 2px solid var(--border);
border-radius: 12px;
padding: 10px 14px;
transition: border-color 0.18s, box-shadow 0.18s;
}
.dd-20__field:focus-within {
border-color: var(--brand);
box-shadow: 0 0 0 4px rgba(14,165,233,.12);
}
.dd-20__field-icon { font-size: 16px; color: var(--muted); flex-shrink: 0; }
.dd-20__input {
flex: 1;
border: none;
outline: none;
font-family: inherit;
font-size: 15px;
font-weight: 500;
color: var(--text);
background: transparent;
}
.dd-20__input::placeholder { color: #94a3b8; font-weight: 400; }
.dd-20__clear {
background: none;
border: none;
font-size: 18px;
color: var(--muted);
cursor: pointer;
line-height: 1;
opacity: 0;
transition: opacity 0.15s;
}
.dd-20__clear.is-visible { opacity: 1; }
.dd-20__clear:hover { color: var(--text); }
.dd-20__list {
list-style: none;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
overflow: hidden;
max-height: 0;
opacity: 0;
margin-top: 6px;
box-shadow: 0 8px 24px rgba(0,0,0,.10);
transition: max-height 0.3s ease, opacity 0.2s ease;
}
.dd-20__list.is-open { max-height: 280px; opacity: 1; overflow-y: auto; }
.dd-20__list li {
padding: 10px 16px;
font-size: 14px;
font-weight: 500;
color: var(--text);
cursor: pointer;
display: flex;
align-items: center;
gap: 10px;
transition: background 0.12s;
}
.dd-20__list li:hover, .dd-20__list li.is-active { background: var(--hover); color: var(--brand); }
.dd-20__list li strong { color: var(--brand); font-weight: 700; }
.dd-20__hint { margin-top: 12px; font-size: 12px; color: var(--muted); text-align: center; }
@media (prefers-reduced-motion: reduce) {
.dd-20__list, .dd-20__field { transition: none; }
} .dd-20, .dd-20 *, .dd-20 *::before, .dd-20 *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
.dd-20 ::selection { background: #0ea5e9; color: #fff; }
.dd-20 {
--brand: #0ea5e9;
--surface: #fff;
--text: #0f172a;
--muted: #64748b;
--border: #e2e8f0;
--hover: #f0f9ff;
font-family: 'DM Sans', sans-serif;
min-height: 380px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(160deg, #f0f9ff 0%, #bae6fd 100%);
padding: 40px 20px;
}
.dd-20__card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 20px;
padding: 28px 24px;
width: 100%;
max-width: 380px;
box-shadow: 0 8px 40px rgba(14,165,233,.15);
position: relative;
}
.dd-20__title { font-size: 20px; font-weight: 700; color: var(--text); margin-bottom: 4px; }
.dd-20__sub { font-size: 13px; color: var(--muted); margin-bottom: 18px; }
.dd-20__field {
display: flex;
align-items: center;
gap: 8px;
border: 2px solid var(--border);
border-radius: 12px;
padding: 10px 14px;
transition: border-color 0.18s, box-shadow 0.18s;
}
.dd-20__field:focus-within {
border-color: var(--brand);
box-shadow: 0 0 0 4px rgba(14,165,233,.12);
}
.dd-20__field-icon { font-size: 16px; color: var(--muted); flex-shrink: 0; }
.dd-20__input {
flex: 1;
border: none;
outline: none;
font-family: inherit;
font-size: 15px;
font-weight: 500;
color: var(--text);
background: transparent;
}
.dd-20__input::placeholder { color: #94a3b8; font-weight: 400; }
.dd-20__clear {
background: none;
border: none;
font-size: 18px;
color: var(--muted);
cursor: pointer;
line-height: 1;
opacity: 0;
transition: opacity 0.15s;
}
.dd-20__clear.is-visible { opacity: 1; }
.dd-20__clear:hover { color: var(--text); }
.dd-20__list {
list-style: none;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
overflow: hidden;
max-height: 0;
opacity: 0;
margin-top: 6px;
box-shadow: 0 8px 24px rgba(0,0,0,.10);
transition: max-height 0.3s ease, opacity 0.2s ease;
}
.dd-20__list.is-open { max-height: 280px; opacity: 1; overflow-y: auto; }
.dd-20__list li {
padding: 10px 16px;
font-size: 14px;
font-weight: 500;
color: var(--text);
cursor: pointer;
display: flex;
align-items: center;
gap: 10px;
transition: background 0.12s;
}
.dd-20__list li:hover, .dd-20__list li.is-active { background: var(--hover); color: var(--brand); }
.dd-20__list li strong { color: var(--brand); font-weight: 700; }
.dd-20__hint { margin-top: 12px; font-size: 12px; color: var(--muted); text-align: center; }
@media (prefers-reduced-motion: reduce) {
.dd-20__list, .dd-20__field { transition: none; }
}(function() {
var data = [
'Tokyo','Paris','Barcelona','Kyoto','Nairobi','Madrid',
'New York','Rio de Janeiro','Sydney','Rome','Oslo','Cairo',
'Toronto','Mexico City','Lisbon','Amsterdam','Doha','Shanghai',
'Dublin','Bergen','Porto','Nicosia','Singapore','Berlin'
];
var flags = ['🇮🇹','🇫🇷','🇧🇦','🇰🇾','🇰🇪','🇪🇸',
'🇺🇸','🇧🇷','🇦🇺','🇮🇹','🇳🇴','🇪🇬',
'🇨🇦','🇲🇽','🇵🇹','🇳🇱','🇶🇦','🇨🇳',
'🇮🇪','🇳🇴','🇵🇹','🇨🇾','🇸🇬','🇩🇪'];
var input = document.getElementById('dd-20-input');
var list = document.getElementById('dd-20-list');
var clearBtn = document.getElementById('dd-20-clear');
var hint = document.getElementById('dd-20-hint');
if (!input || !list) return;
var activeIdx = -1;
function getItems() { return Array.from(list.querySelectorAll('li[data-idx]')); }
function render(query) {
var q = query.trim().toLowerCase();
list.innerHTML = '';
activeIdx = -1;
if (!q) {
list.classList.remove('is-open');
hint && (hint.style.display = '');
clearBtn.classList.remove('is-visible');
return;
}
clearBtn.classList.add('is-visible');
if (hint) hint.style.display = 'none';
var matched = data.filter(function(d) { return d.toLowerCase().includes(q); });
if (!matched.length) {
var li = document.createElement('li');
li.style.color = '#94a3b8'; li.style.cursor = 'default';
li.textContent = 'No destinations found';
list.appendChild(li);
} else {
matched.slice(0, 8).forEach(function(city) {
var li = document.createElement('li');
var origIdx = data.indexOf(city);
var flag = flags[origIdx] || '🌎';
var lc = city.toLowerCase();
var start = lc.indexOf(q);
var highlighted = start > -1
? city.slice(0, start) + '<strong>' + city.slice(start, start + q.length) + '</strong>' + city.slice(start + q.length)
: city;
li.innerHTML = '<span>' + flag + '</span><span>' + highlighted + '</span>';
li.dataset.idx = origIdx;
li.addEventListener('mousedown', function() {
input.value = city;
list.classList.remove('is-open');
clearBtn.classList.add('is-visible');
if (hint) hint.style.display = 'none';
});
list.appendChild(li);
});
}
list.classList.add('is-open');
}
input.addEventListener('input', function() { render(input.value); });
input.addEventListener('keydown', function(e) {
var items = getItems();
if (e.key === 'ArrowDown') { e.preventDefault(); activeIdx = (activeIdx + 1) % items.length; }
else if (e.key === 'ArrowUp') { e.preventDefault(); activeIdx = (activeIdx - 1 + items.length) % items.length; }
else if (e.key === 'Enter' && activeIdx > -1) { items[activeIdx].dispatchEvent(new Event('mousedown')); return; }
else if (e.key === 'Escape') { list.classList.remove('is-open'); return; }
items.forEach(function(li, i) { li.classList.toggle('is-active', i === activeIdx); });
});
clearBtn.addEventListener('click', function() { input.value = ''; render(''); input.focus(); });
document.addEventListener('click', function(e) {
if (!e.target.closest('#dd-20-field') && !list.contains(e.target)) list.classList.remove('is-open');
});
})(); (function() {
var data = [
'Tokyo','Paris','Barcelona','Kyoto','Nairobi','Madrid',
'New York','Rio de Janeiro','Sydney','Rome','Oslo','Cairo',
'Toronto','Mexico City','Lisbon','Amsterdam','Doha','Shanghai',
'Dublin','Bergen','Porto','Nicosia','Singapore','Berlin'
];
var flags = ['🇮🇹','🇫🇷','🇧🇦','🇰🇾','🇰🇪','🇪🇸',
'🇺🇸','🇧🇷','🇦🇺','🇮🇹','🇳🇴','🇪🇬',
'🇨🇦','🇲🇽','🇵🇹','🇳🇱','🇶🇦','🇨🇳',
'🇮🇪','🇳🇴','🇵🇹','🇨🇾','🇸🇬','🇩🇪'];
var input = document.getElementById('dd-20-input');
var list = document.getElementById('dd-20-list');
var clearBtn = document.getElementById('dd-20-clear');
var hint = document.getElementById('dd-20-hint');
if (!input || !list) return;
var activeIdx = -1;
function getItems() { return Array.from(list.querySelectorAll('li[data-idx]')); }
function render(query) {
var q = query.trim().toLowerCase();
list.innerHTML = '';
activeIdx = -1;
if (!q) {
list.classList.remove('is-open');
hint && (hint.style.display = '');
clearBtn.classList.remove('is-visible');
return;
}
clearBtn.classList.add('is-visible');
if (hint) hint.style.display = 'none';
var matched = data.filter(function(d) { return d.toLowerCase().includes(q); });
if (!matched.length) {
var li = document.createElement('li');
li.style.color = '#94a3b8'; li.style.cursor = 'default';
li.textContent = 'No destinations found';
list.appendChild(li);
} else {
matched.slice(0, 8).forEach(function(city) {
var li = document.createElement('li');
var origIdx = data.indexOf(city);
var flag = flags[origIdx] || '🌎';
var lc = city.toLowerCase();
var start = lc.indexOf(q);
var highlighted = start > -1
? city.slice(0, start) + '<strong>' + city.slice(start, start + q.length) + '</strong>' + city.slice(start + q.length)
: city;
li.innerHTML = '<span>' + flag + '</span><span>' + highlighted + '</span>';
li.dataset.idx = origIdx;
li.addEventListener('mousedown', function() {
input.value = city;
list.classList.remove('is-open');
clearBtn.classList.add('is-visible');
if (hint) hint.style.display = 'none';
});
list.appendChild(li);
});
}
list.classList.add('is-open');
}
input.addEventListener('input', function() { render(input.value); });
input.addEventListener('keydown', function(e) {
var items = getItems();
if (e.key === 'ArrowDown') { e.preventDefault(); activeIdx = (activeIdx + 1) % items.length; }
else if (e.key === 'ArrowUp') { e.preventDefault(); activeIdx = (activeIdx - 1 + items.length) % items.length; }
else if (e.key === 'Enter' && activeIdx > -1) { items[activeIdx].dispatchEvent(new Event('mousedown')); return; }
else if (e.key === 'Escape') { list.classList.remove('is-open'); return; }
items.forEach(function(li, i) { li.classList.toggle('is-active', i === activeIdx); });
});
clearBtn.addEventListener('click', function() { input.value = ''; render(''); input.focus(); });
document.addEventListener('click', function(e) {
if (!e.target.closest('#dd-20-field') && !list.contains(e.target)) list.classList.remove('is-open');
});
})();How this works
A static dataset array of strings is filtered on every input event using Array.filter() with a case-insensitive includes() check. Matched results are rendered into a <ul> list beneath the input — each <li> gets an innerHTML where the matching portion is wrapped in <strong> for emphasis.
The suggestion list is shown/hidden by toggling an is-open class rather than manipulating display, so the CSS entrance transition (max-height + opacity combo) plays correctly. Clicking a suggestion populates the input and hides the dropdown. Arrow keys navigate the list with a .is-active highlight class, and Enter selects the highlighted item. Clicking outside dismisses the list.
Customize
- Switch from substring match to prefix-only by replacing
includeswithstartsWithfor stricter typeahead behavior. - Add a "no results" empty state by checking
if (filtered.length === 0)and rendering a disabled<li>with placeholder text. - Debounce the input handler with
setTimeoutfor real API calls — 200ms debounce prevents a request on every keystroke. - Add grouping by inserting
<li class="group-header">elements between category groups in the rendered list.
Watch out for
- Filtering live with
innerHTMLreplacement on every keystroke can cause flicker — throttle to once per animation frame withrequestAnimationFrameif the dataset is large. - Prefix highlight with
innerHTMLis safe here because the dataset is hardcoded — sanitize withtextContentinterpolation if the data comes from user input or an API. blurfires beforeclickon list items — usingmousedownon list items prevents the dropdown from closing before the click registers.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 49+ | 10+ | 44+ | 49+ |
Array.filter, includes, and input event are fully supported in all modern browsers.