Выпадающий список в HTML: как сделать, оформить через CSS и добавить поиск
Выпадающий список HTML создаёт тег select с вложенными option. Рабочий код – в первом разделе, он запускается без скриптов и стилей.
Дальше вы узнаете, как создать выпадающий список нужного вида: оформите его через CSS, добавите поиск и выбор нескольких значений. Затем заберёте выбранное в JavaScript и на сервере в PHP.
- Как сделать выпадающий список в HTML за минуту
- Из чего состоит select: атрибуты и значение варианта
- Группировка вариантов: optgroup
- Выпадающий список с автодополнением: input и datalist
- Выбор нескольких значений: multiple
- Выпадающий список HTML CSS: стилизация select
- Почему option почти не стилизуется
- Кастомный список на своей разметке: когда он оправдан
- Готовые библиотеки: Choices.js, Tom Select, Select2, Bootstrap-Select
- Выпадающий список JS: как получить выбранное значение
- Как принять значение на сервере: PHP
- Доступность и типичные ошибки
Как сделать выпадающий список в HTML за минуту
Чтобы сделать выпадающий список, оберните варианты в select. Каждый вариант – отдельный option. Вот полный пример из трёх вариантов.
<label for="color">Цвет:</label>
<select id="color" name="color">
<option value="green">Зелёный</option>
<option value="yellow">Жёлтый</option>
<option value="black">Чёрный</option>
</select>Вставьте разметку в любую страницу и откройте её в браузере. Появится поле со словом «Зелёный» и стрелкой, по щелчку раскроются 3 варианта. Если вы не указали selected, браузер выбирает первый доступный вариант.
Нужно больше вариантов? Добавляйте по строке option на каждый. Длинный список утомляет, поэтому при десятке вариантов и больше группируйте их или добавляйте поиск. Оба способа описаны ниже.
Раскрывающийся список HTML и выпадающий – одно и то же: это два названия элемента dropdown. В коде это всегда пара html select option: тег select держит список, а option описывает один вариант.
Из чего состоит select: атрибуты и значение варианта
Текст варианта и атрибут value – разные вещи. Пользователь видит текст между тегами. На сервер уходит value. Нет value – уйдёт текст варианта.
В первом примере «Зелёный» превращается в green. Значения на латинице проще проверять в коде, и они не ломаются при смене перевода. Поэтому в коде на сервере вы сравниваете значение с green, а не со словом «Зелёный».
Выбор из списка настраивают пять атрибутов:
name– имя поля, по нему значение приходит на сервер;selected– вариант выбран при загрузке страницы;disabled– вариант или весь список недоступен;required– форма не отправится без выбора;size– сколько строк видно сразу: приsize="4"список превращается в блок из 4 строк.
<select id="delivery" name="delivery" required>
<option value="">Выберите способ</option>
<option value="courier">Курьер</option>
<option value="pickup" selected>Самовывоз</option>
<option value="post" disabled>Почта (временно недоступна)</option>
</select>В этом примере при загрузке выбран «Самовывоз». Вариант «Почта» серый, его не выбрать. Пустой первый вариант нужен для required: об этом ещё раз в последнем разделе.
Атрибут id нужен не серверу, а подписи и скриптам. По нему label for находит список, а querySelector – его в коде. Пара id и name не обязана совпадать, но одинаковые значения упрощают чтение разметки.
Группировка вариантов: optgroup
Когда вариантов больше десятка и они делятся по смыслу, объедините их тегом optgroup. Список с группами читается быстрее: глаз находит заголовок, а не перебирает 30 строк подряд.
<select name="city">
<optgroup label="Россия">
<option value="msk">Москва</option>
<option value="spb">Санкт-Петербург</option>
</optgroup>
<optgroup label="Беларусь">
<option value="minsk">Минск</option>
</optgroup>
</select>Атрибут label задаёт заголовок группы. Заголовок выбрать нельзя, и в форму он не уходит. Если добавить disabled к optgroup, отключится вся группа сразу.
Группы не вкладываются друг в друга: optgroup внутри другого optgroup недопустим. Нужна иерархия из трёх уровней? Сделайте два связанных списка: второй заполняйте скриптом после выбора в первом.
Группировка полезна не всегда. Для пяти вариантов она только усложняет разметку. Для длинного справочника – стран, городов, категорий товаров – она заметно ускоряет поиск нужной строки глазами.
Выпадающий список с автодополнением: input и datalist
Нужен выбор из списка, где пользователь печатает и получает подсказки? Свяжите поле input с тегом datalist. Скрипты не нужны: браузер сам фильтрует варианты по введённым буквам.
<label for="city-input">Город:</label>
<input id="city-input" name="city" list="cities" autocomplete="off">
<datalist id="cities">
<option value="Москва">
<option value="Минск">
<option value="Мурманск">
</datalist>Связь держит атрибут list: его значение равно id списка. Главное отличие от select – в поле можно ввести текст, которого нет среди подсказок. Проверяйте такое значение на сервере.
Когда что выбирать? Если допустимы только заранее известные значения, берите select: он не даёт ввести лишнее. Если вариантов много, а свой текст допустим, берите datalist. Он подходит для города, тега или названия товара.
Дополнительная проверка на стороне браузера ловит опечатки до отправки формы. Сверьте введённое значение с вариантами и подсветите поле:
<script>
const input = document.querySelector("#city-input");
const options = Array.from(document.querySelectorAll("#cities option"), (o) => o.value);
input.addEventListener("input", () => {
const known = options.includes(input.value);
input.setCustomValidity(known ? "" : "Выберите город из списка");
});
</script>Пустая строка в setCustomValidity снимает ошибку, непустая блокирует отправку формы. Скрипт стоит после разметки, поэтому оба элемента к этому моменту уже есть на странице. Проверка в браузере не заменяет серверную: запрос можно отправить в обход формы.
Поддержка неполная. По данным caniuse, в Safari на iOS она частичная, а в Chrome для Android полная. Проверьте вид подсказок на своих устройствах, прежде чем выкладывать форму.
Выбор нескольких значений: multiple
Атрибут multiple превращает список в поле выбора нескольких значений. На компьютере браузер рисует не раскрывающееся меню, а прокручиваемый блок. Высоту блока задаёт size: при size="4" видны 4 строки.
<select id="colors" name="colors[]" multiple size="4">
<option value="red">Красный</option>
<option value="green">Зелёный</option>
<option value="blue">Синий</option>
<option value="black">Чёрный</option>
</select>На компьютере зажмите Ctrl (в macOS – Cmd) или Shift и щёлкайте по вариантам. Посетители не всегда об этом знают, поэтому добавьте подсказку рядом со списком.

Для трёх–пяти вариантов удобнее группа чекбоксов: их отмечают одним щелчком без подсказок. Список с multiple берите, когда вариантов много и блок с прокруткой экономит место на странице.
На телефоне окно вариантов рисует браузер или система, и вид отличается от компьютерного. Ваши стили туда могут не дойти. Проверьте список на реальном устройстве до выкладки.
Имя поля для нескольких значений заканчивайте квадратными скобками: name="colors[]". PHP тогда соберёт значения в массив. Без скобок останется только последнее значение – такое правило описано в FAQ на php.net. При выборе красного и синего придёт массив из двух элементов.
Выпадающий список HTML CSS: стилизация select
Привести список к макету можно за три шага.
- Уберите системный вид свойством
appearance: none. MDN относит его к Baseline: оно работает во всех актуальных браузерах без вендорных префиксов. - Нарисуйте свою стрелку фоном: встроенный SVG в
background-image. Справа оставьте отступpadding-right, чтобы текст не заезжал на стрелку. - Добавьте состояния
:hover,:focus-visibleи:disabled. Проверьте результат: стрелка должна стоять справа, а по Tab список должен получать видимую рамку.
.select {
appearance: none;
width: 240px;
padding: 10px 40px 10px 12px;
border: 1px solid #517b97;
border-radius: 6px;
background-color: #fff;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23333' stroke-width='2'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
font: inherit;
color: #222;
}
.select:hover {
border-color: #2b5d82;
}
.select:focus-visible {
outline: 2px solid #1a73e8;
outline-offset: 2px;
}
.select:disabled {
background-color: #f0f0f0;
color: #888;
}Повесьте класс на список: <select class="select">.
Разберём ключевые строки. Отступ справа в 40 пикселей оставляет место под стрелку. Строка background-position: right 12px center ставит стрелку в 12 пикселях от правого края и по центру высоты. Значение font: inherit берёт шрифт страницы вместо системного.
Стрелка – это SVG внутри адреса data:image/svg+xml. Символ решётки в цвете записан как %23: иначе браузер прочтёт его как начало якоря. Цвет стрелки меняйте в параметре stroke, размер – в width и height.
Псевдокласс :focus-visible тоже относится к Baseline. Не убирайте outline без замены: клавиатурный фокус исчезнет, и пользователь потеряет место на форме.
Если проект на Bootstrap, стилизацию можно не писать. В Bootstrap 5.3 штатный класс для списка – form-select, есть размеры form-select-lg и form-select-sm. В четвёртой версии для этого служил custom-select.
Почему option почти не стилизуется
Стилизовать можно сам select, но не окно с вариантами. MDN пишет об этом прямо: вы не управляете списком, который раскрывается по щелчку. Наследовать шрифт родителя можно, а отступы и цвета – нет.
Для самих option есть только узкая лазейка. Chromium может дополнительно применить color, background-color, font-family, font-variant и text-align. Результат зависит от браузера и системы, поэтому на одинаковый вид не рассчитывайте.
Будущее решение – значение appearance: base-select и псевдоэлемент ::picker(select). Они открывают окно вариантов для CSS. В Chrome они работают с версии 135, но MDN отмечает ограниченную доступность: это не Baseline. Про Firefox и Safari проверяйте таблицу совместимости MDN.
@supports (appearance: base-select) {
select,
::picker(select) {
appearance: base-select;
}
}Блок в @supports срабатывает только там, где значение известно. В остальных браузерах останется обычный вид из прошлого раздела.
Что делать, если дизайнер просит иконки, цветные метки или две строки в варианте? Есть три пути.
Первый: упростить макет и оставить нативный список. Второй: подождать, пока base-select станет доступен всем вашим посетителям. Третий: взять кастомный список или готовую библиотеку – о них следующие разделы.
Кастомный список на своей разметке: когда он оправдан
Свой список оправдан, когда в вариантах нужны иконки, картинки или две строки текста. Для обычного выбора из 5–10 пунктов нативный select лучше. Он бесплатно даёт клавиатуру, поиск по первой букве и доступность для скринридеров.
В своей разметке всё это вы пишете сами. Список работ выглядит так:
- роли ARIA:
listboxдля списка иoptionдля вариантов; - стрелки вверх и вниз для перехода по вариантам;
- Enter выбирает вариант, Esc закрывает список;
- возврат фокуса на кнопку после закрытия;
- скрытое поле, через которое значение попадёт в форму.
<button type="button" id="trigger" aria-haspopup="listbox" aria-expanded="false">
Выберите цвет
</button>
<ul role="listbox" aria-labelledby="trigger" hidden>
<li role="option" aria-selected="false">Зелёный</li>
<li role="option" aria-selected="false">Жёлтый</li>
</ul>Это только каркас: без скрипта он не раскрывается. Значение к тому же не попадёт в форму, пока вы не добавите скрытое поле input type="hidden". Скрипт для раскрытия, выбора и клавиатуры займёт ещё десятки строк.
На телефоне придётся отдельно проверить жесты и прокрутку. Скринридер должен зачитать вариант и его состояние, и проверить это можно только вживую. Если такой объём работы не оправдан, возьмите готовую библиотеку.
Готовые библиотеки: Choices.js, Tom Select, Select2, Bootstrap-Select
Библиотеки добавляют к списку поиск, теги и удобный мультивыбор. Ниже – четыре популярных варианта. Даты релизов и размеры собраны в таблице.
| Библиотека | Последний релиз | Зависимости | Вес JS (min / gzip) | Поиск | Мультивыбор |
|---|---|---|---|---|---|
| Choices.js | 11.2.4, август 2026 | нет, без jQuery | 78,3 / 21,4 КБ | да | да |
| Tom Select | 2.6.2, июль 2026 | нет, без jQuery | 55,2 / 17,8 КБ | да | да |
| Select2 | 4.1.0, май 2026 | jQuery | 73,4 / 20,5 КБ | да | да |
| Bootstrap-Select | 1.13.18, июнь 2020 | jQuery, CSS и dropdown.js из Bootstrap | 52,5 / 16,0 КБ | да, data-live-search="true" | да |
Версии, размеры и даты релизов приведены как ориентир на момент обновления статьи. Размеры взяты у Bundlephobia и не включают jQuery. Перед подключением сверьте их в репозитории библиотеки.
Choices.js и Tom Select работают без jQuery, и релизы у обоих свежие. Начинайте с них. Select2 берите, если jQuery в проекте уже есть: дата его релиза стоит в таблице, а свежие правки в репозитории продолжают появляться.
Bootstrap-Select для нового проекта не берите. Стабильная версия 1.13.18 вышла в июне 2020 года, последний тег – бета 1.14.0 от апреля 2022 года. Поддержки Bootstrap 5 в стабильной версии нет.

Покажем подключение на Choices.js: он не требует jQuery и сборщика. Ниже полная страница, которую можно сохранить в файл и открыть в браузере. Стили и скрипт грузятся с CDN jsDelivr.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Choices.js</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js/public/assets/styles/base.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js/public/assets/styles/choices.min.css">
</head>
<body>
<label for="colors">Цвета:</label>
<select id="colors" name="colors[]" multiple>
<option value="red">Красный</option>
<option value="green">Зелёный</option>
<option value="blue">Синий</option>
<option value="black">Чёрный</option>
</select>
<script src="https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js"></script>
<script>
new Choices("#colors", {
removeItemButton: true
});
</script>
</body>
</html>Две строки link подключают стили, а скрипт создаёт объект Choices на месте обычного списка. Опция removeItemButton добавляет крестик у выбранных значений. Поиск по вариантам включён по умолчанию, за него отвечает опция searchEnabled.
Со сборщиком порядок другой. Поставьте пакет командой npm install choices.js, затем подключите скрипт и стили в своём коде:
import Choices from "choices.js";
import "choices.js/public/assets/styles/choices.css";
new Choices("#colors", {
removeItemButton: true
});Список остаётся обычным select, поэтому форма отправляется как прежде. Значения уходят на сервер под тем же именем, и PHP-код из раздела про приём значений менять не нужно.
Выпадающий список JS: как получить выбранное значение
В JavaScript выбранное значение выпадающий список отдаёт через свойство value. Оно возвращает значение первого выбранного варианта, а если выбора нет – пустую строку. Событие change срабатывает при каждой смене выбора.
<select id="color" name="color">
<option value="green">Зелёный</option>
<option value="yellow">Жёлтый</option>
</select>
<p id="result"></p>
<script>
const select = document.querySelector("#color");
const result = document.querySelector("#result");
select.addEventListener("change", () => {
const option = select.options[select.selectedIndex];
result.textContent = option.text + " = " + select.value;
});
</script>Выберите «Жёлтый»: под списком появится «Жёлтый = yellow». Так видно разницу между текстом варианта и его value. Скрипт стоит после списка, поэтому элемент к этому моменту уже есть на странице.
Свойство selectedIndex хранит номер выбранного варианта, а options – коллекцию всех вариантов. Из них код берёт текст выбранного пункта. Если нужен только value, читайте select.value напрямую.
Для списка с multiple одного value мало. Соберите все выбранные варианты из коллекции selectedOptions:
const values = Array.from(select.selectedOptions, (option) => option.value);В старом проекте с jQuery то же читает строка $("#color").val(). В новом коде jQuery ради этого не подключайте.
Как принять значение на сервере: PHP
Форма с методом post отправляет значение под именем из атрибута name. Вот форма и обработчик в одном файле.
<form method="post">
<label for="selphp">Выберите цвет:</label>
<select id="selphp" name="selphp">
<option value="red">Красный</option>
<option value="green">Зелёный</option>
<option value="blue">Синий</option>
</select>
<button type="submit">Отправить</button>
</form>
<?php
$allowed = ['red', 'green', 'blue'];
$color = $_POST['selphp'] ?? '';
if (in_array($color, $allowed, true)) {
echo 'Вы выбрали: ' . htmlspecialchars($_POST['selphp'], ENT_QUOTES, 'UTF-8');
}
?>Атрибута action у формы нет: без него браузер отправляет данные на адрес этой же страницы. Сохраните файл с расширением .php и откройте через веб-сервер. Код из файла, открытого прямо с диска, PHP не выполнит.

Значение из $_POST нельзя выводить в HTML напрямую. Пользователь может отправить не то, что в списке: подменить value в инструментах браузера или послать запрос из командной строки. Если в поле придёт тег script, он выполнится на странице.
Защиту дают два приёма. Сначала сверьте значение со списком допустимых, как в примере. Затем экранируйте вывод функцией htmlspecialchars(): флаг ENT_QUOTES превращает в безопасные символы и двойные, и одинарные кавычки. В новых версиях PHP этот флаг уже включён по умолчанию, но явная запись делает намерение видимым.
Для списка с multiple приведите поле к массиву. В примере ниже значения склеиваются в одну строку:
$colors = (array) ($_POST['colors'] ?? []);
echo htmlspecialchars(implode(', ', $colors), ENT_QUOTES, 'UTF-8');Форму с методом get читайте из массива $_GET по тому же имени.
Доступность и типичные ошибки
Свяжите каждый список с подписью: label for должен совпадать с id списка. Так скринридер зачитает вопрос вместе со списком. Плейсхолдер вместо подписи не подходит.
Для плейсхолдера ставьте первым пустой вариант: <option value="">Выберите</option>. Вместе с required он не даёт отправить форму без выбора. Спецификация HTML называет его «placeholder label option».
Отключённый select в форму не попадает: его значение на сервер не уходит. Нужно показать список, но отправить значение? Добавьте скрытое поле input type="hidden" с тем же именем.
Ещё три ошибки встречаются чаще прочих. Разработчики убирают outline и оставляют клавиатурного пользователя без рамки фокуса. Они полагаются на стили option, хотя браузер их не применит. И они не проверяют список на реальном телефоне.
Совет от нашей редакции: начинайте с нативного select и стилей из раздела про стилизацию. Библиотеку подключайте, когда нужен поиск по сотне вариантов или теги.
Комментарии
с опцией поиска как шире элемент сделать? size и width не работают!