Стилизация выпадающего списка select с помощью base-select
Стилизуйте нативные списки select с помощью appearance: base-select. Разберите picker, варианты, значки, доступность, поддержку браузерами и ограничения.
Если задать appearance: base-select для <select> и его псевдоэлемента ::picker(select), весь элемент управления можно стилизовать средствами CSS: кнопку, выпадающий список, стрелку, галочку и каждый вариант.
Многие команды поддерживают собственный компонент выпадающего списка по одной-единственной причине: нативный <select> нельзя было стилизовать под дизайн. Обработка клавиатуры, управление фокусом и ARIA-разметка прилагались в нагрузку. appearance: base-select устраняет эту причину.
В этом руководстве разобраны все возможности стилизации, от включения режима до вариантов со сложной разметкой, а также то, чем приходится жертвовать. Классические приёмы с appearance: none описаны в статье о стилизации элементов select с помощью современного CSS: она даёт более широкую картину, но base-select затрагивает лишь вскользь. Более старые подходы рассмотрены в статье о стилизации выпадающих списков select без JavaScript.
Ключевые выводы
appearance: base-selectнужно задать и дляselect, и для::picker(select). Если задать его только для пикера, ничего не произойдёт.- Пикер — это поповер в верхнем слое (top layer), привязанный к кнопке select через неявную ссылку на якорь, поэтому
z-index, порталы иanchor-nameне нужны. <selectedcontent>размещается внутри<button>, который является первым дочерним элементом<select>, и показывает клон выбранного варианта в закрытом состоянии.- Каждый вариант должен содержать текст: варианты из одних иконок не дают экранным дикторам ничего для озвучивания и отображаются пустыми в браузерах без поддержки.
- С base-select вы теряете системный пикер на мобильных устройствах, возможность отрисовки за пределами окна браузера и автоматическую подгонку ширины под самый длинный вариант.
Компонент, который можно удалить
Собственный компонент select обычно существует только для того, чтобы исправить внешний вид нативного элемента <select>, и при этом ему приходится брать на себя массу поведения. В анонсе команды Chrome говорится, что начиная с Chrome 135 элемент <select> можно стилизовать с помощью CSS, сохраняя доступность и стандартное поведение встроенного элемента управления. WebKit последовал примеру в Safari 27.0, где настоящий <select> теперь можно полностью перестилизовать. Как только браузер поддерживает эту возможность, главная причина держать listbox на div-ах исчезает, а вместе с ней — и поведение, которое приходилось воссоздавать ради него.
Как включить appearance: base-select?
Чтобы включить режим, примените appearance: base-select и к <select>, и к его пикеру. В руководстве MDN по настраиваемому select поясняется, что select можно перевести в этот режим отдельно, но пикер заработает только тогда, когда режим включён и для select. Рекомендации WebKit по настраиваемому select советуют оборачивать эти улучшения в @supports (appearance: base-select). Все последующие правила держите в том же блоке, чтобы браузеры без поддержки получали обычный select.
@supports (appearance: base-select) {
select,
::picker(select) {
appearance: base-select;
}
/* all following rules go here */
}
| Часть | Селектор | Примечания |
|---|---|---|
| Закрытый элемент управления | select | Кнопка select |
| Стрелка | ::picker-icon | Не входит в дерево доступности |
| Открытое состояние | :open | Срабатывает, пока пикер отображается |
| Выпадающий список | ::picker(select) | Всё, кроме кнопки — первого дочернего элемента |
| Каждый вариант | option | По умолчанию display: flex |
| Выбранный вариант | option:checked | |
| Отметка выбора | ::checkmark | Не входит в дерево доступности |
| Содержимое в закрытом состоянии | selectedcontent | Клон выбранного варианта |
Стилизация закрытого элемента управления
Закрытый элемент управления — это сам элемент select. Стрелка — ::picker-icon, а :open срабатывает, пока пикер отображается, поэтому при открытии select стрелку можно повернуть.
select {
border: 2px solid #d4d4d8;
background: #f4f4f5;
padding: 10px;
transition: 0.4s;
}
select:hover,
select:focus {
background: #e4e4e7;
}
select::picker-icon {
color: #71717a;
transition: 0.4s rotate;
}
select:open::picker-icon {
rotate: 180deg;
}
transition для rotate обеспечивает плавный поворот стрелки при каждом открытии и закрытии пикера.
Стилизация открытого списка
Открытый выпадающий список настраиваемого select — это ::picker(select). Браузер помещает в него всё содержимое <select>, кроме кнопки, идущей первой. Варианты по умолчанию являются flex-контейнерами. :checked выбирает отмеченный вариант, а ::checkmark — отметку рядом с ним.
::picker(select) {
border: none;
border-radius: 8px;
}
option {
display: flex;
gap: 16px;
padding: 10px;
}
option:hover,
option:focus {
background: #ede9fe;
}
option:checked {
font-weight: bold;
}
option::checkmark {
order: 1;
margin-left: auto;
content: "✔";
}
order: 1 вместе с margin-left: auto переносит галочку из начала строки в конец.
Позиционирование и анимация
В настраиваемом select выпадающий список — это поповер, который открывается в верхнем слое. Браузер самостоятельно привязывает его к кнопке select с помощью CSS anchor positioning, поэтому z-index, портал или настройка anchor-name не нужны. Кнопку и пикер связывают те же отношения invoker/popover, которые описаны для кнопок и поповеров в руководстве по invoker commands. Стили по умолчанию также переносят пикер на другую сторону, если он выходит за пределы экрана, а собственное положение можно задать через anchor():
::picker(select) {
top: calc(anchor(bottom) + 1px);
left: anchor(10%);
}
Чтобы пикер плавно появлялся и исчезал, задайте переход между его состояниями поповера:
::picker(select) {
opacity: 0;
transition: all 0.4s allow-discrete;
}
:open::picker(select) {
opacity: 1;
}
@starting-style {
:open::picker(select) {
opacity: 0;
}
}
allow-discrete позволяет дискретным свойствам display и overlay участвовать в переходе. Без него пикер покинул бы верхний слой раньше, чем стало бы заметно затухание. @starting-style задаёт прозрачность, с которой начинается появление.
Варианты со сложной разметкой и selectedcontent
Поместите <selectedcontent> внутрь <button>, который является первым дочерним элементом <select>. Он показывает клон содержимого выбранного варианта в закрытом элементе управления, и его можно стилизовать отдельно от вариантов в выпадающем списке. Теперь варианты могут содержать разметку, например иконки и изображения.
<label for="status">Status</label>
<select id="status" name="status">
<button>
<selectedcontent></selectedcontent>
</button>
<option value="draft">
<span class="icon" aria-hidden="true">📝</span>
<span class="option-label">Draft</span>
</option>
<option value="review">
<img class="icon" src="review.svg" alt="">
<span class="option-label">In review</span>
</option>
<option value="published">
<span class="icon" aria-hidden="true">✅</span>
<span class="option-label">Published</span>
</option>
</select>
MDN отмечает, что иконки могут увеличить высоту закрытой кнопки и сместить стрелку. Их можно скрыть только в закрытом состоянии:
selectedcontent .icon {
display: none;
}
Варианты в открытом пикере сохраняют свои иконки. Кнопка select инертна, поэтому размещённые в ней ссылки или кнопки нельзя по отдельности сфокусировать или нажать — всё работает как одна кнопка. MDN также указывает, что дополнительное содержимое в кнопке может изменить то, что экранные дикторы озвучивают как значение select, поэтому оставляйте в кнопке только <selectedcontent>.
Что браузер берёт на себя?
С base-select браузер обрабатывает всё то, что собственному выпадающему списку приходится реализовывать заново: взаимодействие с клавиатурой, фокус, размещение в верхнем слое, отправку значения в составе формы и семантику для экранных дикторов. WebKit подчёркивает, что поддержка клавиатуры и доступность встроены изначально, поскольку это по-прежнему собственный элемент управления браузера. Воспроизведение пользовательских сессий на выпадающем списке из div-ов, как правило, выявляет ошибки с клавиатурой и фокусом — именно тех ошибок, которых нативный select лишён по своей природе. Чтобы оценить, сколько кода требует ручной подход, загляните в руководство по созданию доступного выпадающего списка select вручную.
Два правила по-прежнему действуют:
- Оставляйте текст в каждом варианте. Главное правило WebKit: каждому варианту нужен настоящий текст или атрибут с доступным текстом. Визуально скрытый текст экранные дикторы всё равно зачитывают. Декоративным изображениям задаётся
alt="", а смысл передаёт текстовая подпись. - Считайте собственные иконки декоративными. Экранные дикторы игнорируют всё, что добавлено через
contentв::checkmarkи::picker-icon, поэтому собственная стрелка или галочка никогда не озвучивается.
Существующий JavaScript-код, который считывает значение select, продолжает работать. Если у варианта нет атрибута value, значением select становится textContent выбранного варианта с обрезанными пробелами, поэтому всегда задавайте value, если варианты содержат сложную разметку.
Поддержка, fallback и компромиссы
appearance: base-select поддерживается в Chrome 135 и новее, в других браузерах на базе Chromium, например Edge, а также в Safari 27.0. Firefox начал добавлять поддержку за настройкой: значение appearance: base-select появилось в Firefox 149 за флагом, но по умолчанию не включено. MDN по-прежнему помечает его как функцию с ограниченной доступностью (limited availability), поэтому сверяйтесь с таблицами совместимости на справочных страницах MDN. Safari поставляет собственные стили base-select по умолчанию, так что нестилизованные элементы управления выглядят в разных браузерах по-разному.
Браузеры без поддержки base-select игнорируют разметку <button> и <selectedcontent>. Они отрисовывают обычный, нормально работающий select, в котором от каждого варианта остаётся только текст. Такой fallback имеет смысл, только если текст есть.
В посте Chrome также перечислены три особенности поведения, которые вы теряете:
- Открытый список не выходит за пределы окна браузера.
- На телефонах и планшетах используется пикер браузера, а не собственный пикер операционной системы.
- Ширина select больше не подстраивается под самый длинный вариант, поэтому задайте
widthдляselect, ::picker(select)самостоятельно.
Кроме того, base-select меняет то, как HTML-парсер обрабатывает содержимое <select>. MDN предупреждает, что это может приводить к конфликтам с JavaScript-фреймворками: одни блокируют эти возможности, а в других серверный рендеринг может нарушить гидратацию. Протестируйте функцию в своём фреймворке, прежде чем удалять существующий компонент.
Заключение
Нативный select с appearance: base-select даёт контроль над стилизацией, а браузер продолжает обрабатывать клавиатуру, фокус, порядок наложения и доступность. Чтобы попробовать, выберите один простой собственный выпадающий список, пересоберите его внутри блока @supports с текстом в каждом варианте и проверьте в браузере без поддержки base-select. Если там он по-прежнему работает как обычный select, а перечисленные выше компромиссы приемлемы, собственный компонент можно удалить.
Часто задаваемые вопросы
Чем отличаются appearance: none и appearance: base-select для select?
appearance: none лишь убирает нативное оформление закрытой кнопки. Выпадающий список и его варианты по-прежнему используют пикер браузера или операционной системы, и большая часть CSS на них не действует. appearance: base-select, заданный и для select, и для ::picker(select), позволяет также стилизовать пикер, варианты, стрелку и галочку, а ещё разрешает сложную HTML-разметку внутри вариантов. Браузеры без поддержки игнорируют его и сохраняют классический элемент управления.
Как определить поддержку base-select в JavaScript?
Вызовите CSS.supports('appearance', 'base-select'). Метод возвращает true только в браузерах, которые распознают это значение. Он проверяет то же самое, что и CSS-условие @supports (appearance: base-select), поэтому с его помощью можно решить, загружать ли резервный компонент выпадающего списка или добавить класс, включающий собственные стили. Используйте определение возможностей (feature detection), а не анализ user agent, поскольку каждый движок добавлял поддержку в разное время.
Работает ли base-select с select multiple или атрибутом size?
Да, в режиме listbox. Атрибут multiple или значение size больше 1 отображают варианты в виде блока на странице, а не кнопки, открывающей всплывающий список. Начиная с Chrome 145, base-select работает и в этом режиме — как для одиночного, так и для множественного выбора. Всплывающий список с множественным выбором, который получается при multiple и size=1, пока не поддерживается. У настраиваемого listbox нет пикера, поэтому ::picker(select), ::picker-icon и selectedcontent к нему не применяются. Поддержку в других движках проверяйте по таблицам совместимости MDN.
Truly understand users experience
See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data.
Star on GitHub12k