Кастомный select
Выпадающий список с полной стилизацией — через
appearance: noneи кастомный дропдаун на<div>с ARIA-атрибутами.
Задача
Нативный <select> стилизуется крайне ограниченно. Нужен красивый выпадающий список с иконками, поиском или группировкой, при этом доступный с клавиатуры.
Решение
Способ 1 — стилизация нативного <select> (простой)
<div class="select-wrap">
<select class="select" name="country">
<option value="">Выберите страну</option>
<option value="ru">Россия</option>
<option value="us">США</option>
<option value="de">Германия</option>
</select>
</div>
.select-wrap {
position: relative;
display: inline-block;
}
.select-wrap::after {
content: '▾';
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
color: #64748b;
}
.select {
appearance: none;
-webkit-appearance: none;
padding: 10px 36px 10px 14px;
border: 1px solid #cbd5e1;
border-radius: 8px;
background: #fff;
font-size: 0.95rem;
cursor: pointer;
min-width: 200px;
}
.select:focus {
outline: 2px solid #3b82f6;
outline-offset: 1px;
}
Способ 2 — полностью кастомный (с ARIA)
<div class="custom-select" id="cselect" role="combobox" aria-expanded="false" aria-haspopup="listbox" tabindex="0">
<div class="custom-select__value" id="cselectValue">Выберите...</div>
<ul class="custom-select__list" role="listbox" id="cselectList" hidden>
<li class="custom-select__option" role="option" data-value="ru">Россия</li>
<li class="custom-select__option" role="option" data-value="us">США</li>
<li class="custom-select__option" role="option" data-value="de">Германия</li>
</ul>
</div>
<input type="hidden" name="country" id="cselectHidden" />
.custom-select { position: relative; min-width: 200px; user-select: none; }
.custom-select__value {
padding: 10px 36px 10px 14px;
border: 1px solid #cbd5e1;
border-radius: 8px;
cursor: pointer;
background: #fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%2364748b' stroke-width='1.5' fill='none'/%3E%3C/svg%3E") no-repeat right 12px center;
}
.custom-select__list {
position: absolute;
top: calc(100% + 4px);
left: 0; right: 0;
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
z-index: 100;
list-style: none;
padding: 4px 0;
margin: 0;
}
.custom-select__option { padding: 9px 14px; cursor: pointer; }
.custom-select__option:hover,
.custom-select__option[aria-selected="true"] { background: #f1f5f9; }
const cs = document.getElementById('cselect');
const value = document.getElementById('cselectValue');
const list = document.getElementById('cselectList');
const hidden = document.getElementById('cselectHidden');
cs.addEventListener('click', () => {
const open = list.hidden;
list.hidden = !open;
cs.setAttribute('aria-expanded', String(!open));
});
list.addEventListener('click', (e) => {
const opt = e.target.closest('[role="option"]');
if (!opt) return;
value.textContent = opt.textContent;
hidden.value = opt.dataset.value;
list.hidden = true;
cs.setAttribute('aria-expanded', 'false');
});
document.addEventListener('click', (e) => {
if (!cs.contains(e.target)) { list.hidden = true; cs.setAttribute('aria-expanded', 'false'); }
});
Ключевые моменты
appearance: noneубирает стандартный стиль браузера — можно задать свойbackgroundсо стрелкой.- Для простых задач используй стилизацию нативного
<select>(способ 1) — меньше кода, лучше доступность. - Полностью кастомный требует ARIA (
role="combobox/listbox/option",aria-expanded) — без этого screen reader молчит. <input type="hidden">— отправляет значение кастомного select в форму.