Как заменить стандартный select на кастомный?
Плагины использовать не хочу, так как они слишком нагруженные, а мне нужно стилизовать 1 селект на сайте.
- Вопрос задан более трёх лет назад
- 1200 просмотров
5 комментариев
Простой 5 комментариев
Andrey Suha @andreysuha
Вы ждете готовое решение или просто хотите понять в каком направлении двигаться?

Михаил Юматов @yuumatov Автор вопроса
Andrey Suha, хотите готовое решение приготовить, готовьте. Если подскажите куда двигаться тоже хорошо.

Михаил Юматов @yuumatov Автор вопроса
Andrey Suha, Может лучше реализовать подобие селекта сразу на этой разметке?
Andrey Suha @andreysuha
Михаил Юматов, уже наготовился. По сути выбираете все опшины в селект из них получаете значения по ним строите лист итемов и вставляєте в остальную верстку селекта, определяете момент когда ваш кастомнный селект должен сгенерировать событие change и в этот момент вызывает change на селекте, потомстандартный селект может скрыть и после него вставить свою вёрстку. В общем случае это выглядит как-то так
Andrey Suha @andreysuha
Михаил Юматов, ну если Вам не обязательно что бы оставался стандартный например для отлавливания события change на нем то да лучше
Свойства и методы формы
Формы и элементы управления, такие как , имеют множество специальных свойств и событий.
Работать с формами станет намного удобнее, когда мы их изучим.
Навигация: формы и элементы
Формы в документе входят в специальную коллекцию document.forms .
Это так называемая «именованная» коллекция: мы можем использовать для получения формы как её имя, так и порядковый номер в документе.
document.forms.my - форма с именем "my" (name="my") document.forms[0] - первая форма в документе
Когда мы уже получили форму, любой элемент доступен в именованной коллекции form.elements .
Может быть несколько элементов с одним и тем же именем, это часто бывает с кнопками-переключателями radio .
В этом случае form.elements[name] является коллекцией, например:
Эти навигационные свойства не зависят от структуры тегов внутри формы. Все элементы управления формы, как бы глубоко они не находились в форме, доступны в коллекции form.elements .
Форма может содержать один или несколько элементов внутри себя. Они также поддерживают свойство elements , в котором находятся элементы управления внутри них.
Сокращённая форма записи: form.name
Есть более короткая запись: мы можем получить доступ к элементу через form[index/name] .
Другими словами, вместо form.elements.login мы можем написать form.login .
Это также работает, но есть небольшая проблема: если мы получаем элемент, а затем меняем его свойство name , то он всё ещё будет доступен под старым именем (также, как и под новым).
В этом легче разобраться на примере:
Обычно это не вызывает проблем, так как мы редко меняем имена у элементов формы.
Обратная ссылка: element.form
Для любого элемента форма доступна через element.form . Так что форма ссылается на все элементы, а эти элементы ссылаются на форму.
Элементы формы
Рассмотрим элементы управления, используемые в формах.
input и textarea
К их значению можно получить доступ через свойство input.value (строка) или input.checked (булево значение) для чекбоксов.
input.value = "Новое значение"; textarea.value = "Новый текст"; input.checked = true; // для чекбоксов и переключателей
Используйте textarea.value вместо textarea.innerHTML
Обратим внимание: хоть элемент и хранит своё значение как вложенный HTML, нам не следует использовать textarea.innerHTML для доступа к нему.
Там хранится только тот HTML, который был изначально на странице, а не текущее значение.
select и option
Элемент имеет 3 важных свойства:
- select.options – коллекция из подэлементов ,
- select.value – значение выбранного в данный момент ,
- select.selectedIndex – номер выбранного .
Они дают три разных способа установить значение в :
- Найти соответствующий элемент и установить в option.selected значение true .
- Установить в select.value значение нужного .
- Установить в select.selectedIndex номер нужного .
Первый способ наиболее понятный, но (2) и (3) являются более удобными при работе.
Вот эти способы на примере:
В отличие от большинства других элементов управления, позволяет нам выбрать несколько вариантов одновременно, если у него стоит атрибут multiple . Эту возможность используют редко, но в этом случае для работы со значениями необходимо использовать первый способ, то есть ставить или удалять свойство selected у подэлементов .
Их коллекцию можно получить как select.options , например:
new Option
Элемент редко используется сам по себе, но и здесь есть кое-что интересное.
В спецификации есть красивый короткий синтаксис для создания элемента :
option = new Option(text, value, defaultSelected, selected);
- text – текст внутри ,
- value – значение,
- defaultSelected – если true , то ставится HTML-атрибут selected ,
- selected – если true , то элемент будет выбранным.
Тут может быть небольшая путаница с defaultSelected и selected . Всё просто: defaultSelected задаёт HTML-атрибут, его можно получить как option.getAttribute(‘selected’) , а selected – выбрано значение или нет, именно его важно поставить правильно. Впрочем, обычно ставят оба этих значения в true или не ставят вовсе (т.е. false ).
let option = new Option("Текст", "value"); // создаст
Тот же элемент, но выбранный:
let option = new Option("Текст", "value", true, true);
Элементы имеют свойства:
option.selected Выбрана ли опция. option.index Номер опции среди других в списке . option.value Значение опции. option.text Содержимое опции (то, что видит посетитель).
Ссылки
- Спецификация: https://html.spec.whatwg.org/multipage/forms.html.
Итого
Свойства для навигации по формам:
document.forms Форма доступна через document.forms[name/index] . form.elements Элементы формы доступны через form.elements[name/index] , или можно просто использовать form[name/index] . Свойство elements также работает для . element.form Элементы хранят ссылку на свою форму в свойстве form .
Значения элементов формы доступны через input.value , textarea.value , select.value и т.д. либо input.checked для чекбоксов и переключателей.
Для элемента мы также можем получить индекс выбранного пункта через select.selectedIndex , либо используя коллекцию пунктов select.options .
Это были основы для начала работы с формами. Далее в учебнике мы встретим ещё много примеров.
В следующей главе мы рассмотрим такие события, как focus и blur , которые могут происходить на любом элементе, но чаще всего обрабатываются в формах.
Замена выбранного по-умолчанию элемента select
Есть автоматически сгенерированный список с уже установленным атрибутом selected , такой код генерится по-умолчанию для посетителей:
Необходимо переставить атрибут selected со второго пункта на любой другой (скажем, на третий) при загрузке страницы.
Библиотека jQuery для работы не доступна.
Отслеживать
9,644 4 4 золотых знака 35 35 серебряных знаков 72 72 бронзовых знака
задан 16 янв 2017 в 7:48
23 1 1 серебряный знак 3 3 бронзовых знака
4 ответа 4
Сортировка: Сброс на вариант по умолчанию
Просто установка значения в select
Для того чтобы установить для select опцию используйте свойство value :
document.querySelector("#size").value = "30";
Модификация DOM
Если вам надо именно модифицировать DOM и установить selected нужной опции, то для этого можно воспользоваться следующим кодом:
document.querySelector("#size > option[selected]").removeAttribute("selected"); document.querySelector("#size > option[value='30']").setAttribute("selected", "selected");
Отслеживать
ответ дан 16 янв 2017 в 8:05
Vadim Ovchinnikov Vadim Ovchinnikov
9,644 4 4 золотых знака 35 35 серебряных знаков 72 72 бронзовых знака
Благодарю, код срабатывает, в части модификации DOM, однако значение value не передается в форму =( почему-то был уверен, что модификация при загрузке этого параметра будет аналогичная «ручной» активации пользователем. попробовал немного модицифировать: document.querySelector(«#size > option[value=’30’]»).click(); . но не срабатывает.
Ищем баланс между нативным и кастомным селектом
Есть план! Мы сделаем стилизованный селект. Стилизуем не просто снаружи, но и внутри. Полный контроль над стилизацией. Вдобавок к этому мы сделаем его доступным. Мы не будем пытаться повторить за браузером всё, что он делает по умолчанию при отрисовке нативного . Мы буквально будем использовать нативный , как только используется любая вспомогательная технология. Но когда будет использоваться мышь, мы отрисуем стилизованную версию и заставим ее функционировать как .
Вот что я понимаю под «гибридным» селектом: это одновременно и нативный , и его стилизованная альтернатива.

Селект, выпадающий список, навигация, меню… название имеет значение Скопировать ссылку
Во время изучения данной темы я думала обо всех тех названиях, которыми разбрасываются, когда говорят о селектах. Наиболее общие из них — «выпадающий список» и «меню». Есть два типа ошибок при наименовании, которые мы можем допустить: дать одинаковые названия разным элементам или дать разные названия одинаковым элементам.
Перед тем, как мы двинемся дальше, позвольте мне внести ясность касательно использования термина «выпадающий список». Вот как я его понимаю:
Выпадающий список — интерактивный компонент, состоящий из кнопки, которая показывает и прячет список элементов, в основном по наведению мыши, клику или тапу. По умолчанию список невидим до начала взаимодействия. Список обычно показывает блок содержимого (опций) поверх другого контента.
Множество интерфейсов могут выглядеть похоже на выпадающий список. Но просто назвать элемент «выпадающим списком» — всё равно что использовать слово «рыба» для описания животного. Какое семейство рыб? Рыба-клоун не то же самое, что и акула. То же касается и выпадающих списков.

Подобно тому, как в море существует много разных видов рыб, есть множество компонентов, о которых мы можем вести речь, столкнувшись со словосочетанием «выпадающий список».
- Меню: список команд или действий, которые пользователь может исполнить на странице.
- Навигация: список ссылок, используемых для перемещения по сайту.
- Селект: контрол формы , показывающий пользователю список опций, которые он может в ней выбрать.
Решение, о каком типе выпадающих списков мы ведём речь, может быть туманным. Вот несколько примеров из веба, подходящих под мою классификацию вышеупомянутых элементов. Оно основано на моём исследовании, и иногда, когда я не могу найти подходящий ответ, интуитивно основано на моём опыте.

| № | Ожидаемое поведение | Тип списка |
|---|---|---|
| 1 | Ожидается, что выбранный вариант отправится внутри формы на сервер, например, возраст. | Селект |
| 2 | Выпадающему списку не нужен выбранный вариант, например, список действий: копировать, вставить и вырезать. | Меню |
| 3 | Выбранный вариант влияет на контент, например, сортировка списка. | Меню или селект, подробности чуть позже. |
| 4 | Выпадающий список содержит ссылки на другие страницы, например, большая навигация со ссылками на разделы сайта. | Открывающаяся навигация |
| 5 | Содержимое выпадающего меню — не список, например, выбор даты. | Что-то другое, что не следует называть выпадающим списком |
Не все воспринимают интернет и взаимодействуют с ним одинаково. Именование пользовательских интерфейсов и определение дизайн-паттернов — фундаментальный процесс, хотя и с достаточным пространством для личной интерпретации.
Вот тип выпадающего списка, который определенно можно назвать меню. Его использование является горячей темой при обсуждении доступности. Я не буду много говорить об этом здесь, но позвольте мне просто подчеркнуть, что тег устарел и не рекомендуется к использованию. Вот подробное руководство по инклюзивным меню и меню-кнопкам (в переводе на «Веб-стандартах», прим. редактора), включая объяснение почему ARIA-роль menu не следует использовать для навигации по сайту.
Мы даже не коснулись других элементов, которые попадают в довольно серую зону, что делает классификацию выпадающих списков ещё более туманной из-за недостака практических примеров использования от WCAG.
Уфф… получилось много. Давайте забудем обо всём этом беспорядке с выпадающими списками и сосредоточимся исключительно на элементе .
Давайте поговорим про Скопировать ссылку
Стилизация элементов формы — увлекательное путешествие. Согласно MDN, есть хорошие, плохие и злые. К хорошим относится тег , который попросту является блочным элементом. К плохим — чекбоксы, стилизация которых возможна, но громоздка. определенно из области злых.
Для разработчиков — самый разочаровывающий элемент форм, главным образом из-за отстутствия поддержки стилизации. Борьба в UX за это настолько велика, что мы ищем альтернативы. Что ж, я думаю, что первое правило такое же, как с ARIA: избегайте его использования, если можете.
Я могла бы закончить статью прямо сейчас словами «Не используйте , точка». Но давайте посмотрим правде в глаза: селект для нас всё ещё лучшее решение в ряде случаев. Сюда можно отнести сценарии, когда мы работаем со списком, содержащим множество опций, раскладкой, ограниченной в пространстве, или же просто при нехватки времени или бюджета для разработки и реализации пользовательского интерактивного компонента с нуля.
Требования к кастомному Скопировать ссылку
Приняв решение создать кастомный селект — пусть и самый простой — мы сталкиваемся с требованиями, которые мы должны учесть:
- Должна быть кнопка, содержащая текущий выбранный вариант.
- Клик по блоку переключает видимость списка опций.
- Клик по опции, расположенной в списке, обновляет выбранное значение. Текст кнопки меняется и список закрывается.
- Клик по области вне компонента закрывает список.
- Переключатель содержит маленький треугольник, направленный вниз, указывающий на то, что есть варианты.
Что-то вроде такого:
Кто-то из вас подумает: «Работает и хорошо». Но постойте… Разве это работает для всех? Не все используют мышку (или тачскрин). К тому же нативный обладает более широким списком возможностей, которые достаются нам бесплатно и не входят в этот список требований:
- Выбранный вариант доступен для восприятия всеми пользователями, вне зависимости от их возможностей зрения.
- С компонентом можно предсказуемо взаимодействовать с помощью клавиатуры во всех браузерах — например, используя клавиши стрелок для навигации, Enter для выбора, Esc для отмены и так далее.
- Вспомогательные технологии (например, скринридеры) чётко объявляют пользователям элемент, называя его роль, имя и состояние.
- Положение списка регулируется, то есть он не обрезается за краями экрана.
- Элемент следует настройкам операционной системы пользователя — например, высокую контрастность, цветовую схему, ограничение движений и другие.
Именно на этом этапе большинство кастомных селектов так или иначе терпят крах. Взгляните на некоторые крупные UI-библиотеки. Я не буду упоминать конкретные, потому что веб достаточно недолговечный, но сходите попробуйте. Вероятно, вы заметите разное поведение селекта в разных фреймворках.
Вот дополнительные характеристики, за которыми нужно следить:
- Выбирается ли опция списка сразу же при получения фокуса с клавиатуры?
- Можно ли использовать Enter и Space для выбора варианта?
- Нажатие на Tab переносит нас к следующему варианут списка или же к следующему элементу формы?
- Что будет, когда вы достигнете последнего варианта в списке с помощью стрелок? Фокус замрет на последнем варианте, вернется к первому или же, что хуже всего, перейдет к следующему элементу формы?
- Возможно ли перейти к последней опции списка с помощью клавиши Page Down ?
- Можно ли прокручивать элементы списка, если их больше, чем в поле видимости в данный момент?
Это был небольшой пример функций нативного селекта.
Решив создать наш собственный кастомный селект, мы обязываем людей пользоваться им определенным образом, который может отличаться от их ожиданий.
Но всё ещё хуже. Даже нативный ведет себя по-разному в разных браузерах и скринридерах.
Создав наш собственный селект, мы заставим людей пользоваться им не так, как они ожидают. Это опасное решение и это именно те мелочи, в которых кроется дьявол.
Создаём гибридный селект Скопировать ссылку
При создании простого кастомного селекта мы, того не замечая, идём на компромисс. В частности, мы жертвуем функциональностью ради эстетики. Всё должно быть наоборот.
Что если вместо этого мы зададим нативный селект по умолчанию и заменим его более эстетичным, если это возможно? Вот тут и вступает в игру идея о гибридном селекте. Он гибридный, потому что состоит из двух селектов, каждый из которых показывается в нужный для него момент:
- Нативный селект, видимый и доступный по умолчанию.
- Кастомный селект, скрытый до тех пор, пока не произойдёт взаимодействие посредством мыши.
Начнём с разметки. Вначале, добавим нативный с несколькими до кастомного. Чуть позже я объясню почему.
Любой контрол формы должен содержать лейбл. Мы можем прибегнуть к , но фокус будет попадать на нативный селект, когда мы будем кликать на подпись. В целях предотвращения такого поведения используем и свяжем его с селектом с помощью aria-labelledby .
Наконец, с помощью aria-hidden=»true» нужно сообщить вспомогательным технологиям, чтобы те игнорировали кастомный селект. Таким образом, они видят только нативный селект, несмотря ни на что.
Основная рабочая роль
Это приводит нас к стилизации, в ходе которой мы не только заставляем всё выглядеть красивее, но также и управляем переключением между селектами. Нам не хватает лишь пары строк, чтобы начать магию.
Для начала, оба селекта должны обладать одинаковой шириной и высотой. Это позволит пользователям не увидеть серьезного расхождения с макетом при переключении.
.selectNative, .selectCustom
Вот два селекта. Но лишь один может устанавливать пространство, которое они занимают. Второй должен быть спозиционирован абсолютно, чтобы быть вне потока документа. Давайте провернём это с кастомным селектом, так как замена производится только тогда, когда она возможна. Мы спрячем его по умолчанию, чтобы никто пока до него не добрался.
.selectCustom
Вот здесь-то и начинается веселье. Нам нужно определить, использует ли пользователь устройство, в котором наведение — часть основного ввода информации. Например, компьютер с мышью. Хотя мы и думаем о медиавыражениях только как о способе проверки определённых функций или же инструменте адаптивности на брейкпоинтах, их также можно использовать для обнаружения поддержки ховера с помощью hover: hover , который поддерживается всеми основными браузерами. Итак, давайте используем это для отображения кастомного селекта на устройствах, где можно навести курсор.
@media (hover: hover) < .selectCustom < display: block; >>
Отлично. Но что насчёт людей, которые используют клавиатуру для навигации даже на устройствах, поддерживающих ховер? Что делать? Мы будем прятать кастомный селект, когда нативный находится в состоянии фокуса. Мы можем поймать соседний элемент с помощью комбинирующего селектора + . Как только нативный селект в фокусе, прячем кастомный, который следует сразу за ним в DOM. Вот почему кастомный селект должен следовать за нативным.
@media (hover: hover) < .selectNative:focus + .selectCustom < display: none; >>
Вот и всё! Трюк переключения между двумя селектами готов. Есть другие способы сделать это через CSS, но и этот прекрасно работает.
Наконец, нам нужно немного JavaScript. Добавим несколько обработчиков событий:
- Один для события клика, по которому в игру вступает кастомный селект, раскрываясь и показывая варианты выбора.
- Один, чтобы синхронизировать выбранные варианты. При изменении одного варианта выбора, меняется и второй.
- И ещё один для установки навигации через клавиатуру с помощью клавиш Up и Down , выбора варианта с помощью клавиш Enter или Space , и закрытия списка через Esc .
Юзабилити-тест Скопировать ссылку
Я провела небольшое юзабилити-тестирование, в котором я попросила нескольких людей с ограниченными возможностями воспользоваться гибридным селектом. Были протестированы следующие устройства и инструменты с использованием последних версий Chrome 81, Firefox 76, Safari 13:
- Компьютер только с мышью.
- Компьютер только с клавиатурой.
- VoiceOver на macOS с помощью клавиатуры.
- NVDA в Windows с помощью клавиатуры.
- VoiceOver на iPhone и iPad в Safari
Все эти тесты дали желаемый результат, но я уверена, что можно было бы провести ещё больше юзабилити-тестов с более разнообразными устройствами и широким диапазоном лиц. Если у вас есть возможность протестировать на других устройствах или с другими инструментами — такими как JAWS, Dragon и подобным — пожалуйста, расскажите мне, как прошёл тест.
Во время теста была обнаружена проблема. В частности, проблема связана с настройкой VoiceOver «Использовать виртуальный курсор VoiceOver». Если пользователь откроет селект с помощью этого курсора, вместо нативного покажется кастомный селект.
Больше всего мне нравится в этом подходе то, как он совмещает всё самое лучшее из обоих миров без нанесения ущерба функциональности.
- Пользователи мобильных устройств и планшетов получают нативный селект, предлагающий лучший пользовательский интерфейс по сравнению с кастомным селектом, включая преимущества производительности.
- Пользователи клавиатур получают возможность взаимодействия с нативным селектом в соответствии с их ожиданиями.
- Вспомогательные технологии спокойно могут взаимодействовать с нативным селектом.
- Пользователи мыши получают возможность взаимодействовать с расширенным кастомным селектом.
Данный подход обеспечивает необходимую для каждого функциональность без дополнительного громоздкого кода, реализующего функции нативного селекта.
Не поймите меня неправильно, этот метод не является универсальным решением для всех. Он может являться рабочим для простых селектов, но, вероятно, не будет работать в случаях со сложным взаимодействием. В этих случаях нам нужно использовать ARIA и JavaScript для восполнения пробелов и создания действительно доступного селекта.
Примечание касательно селекта-меню Скопировать ссылку
Давайте вернёмся к третьему сценарию нашего списка селектов. Если вы помните, это выпадающий список, который всегда имеет отмеченный вариант (например, сортировка). Я отнесла его к серой области как и меню или селект.
Идея такая: много лет назад этот тип выпадающего списка реализовывался в основном с помощью нативного . В настоящее время часто можно увидеть что он реализован с нуля с помощью кастомных стилей (доступных или нет). И мы получаем селект, стилизованный под меню.

— это вид меню. Оба имеют схожую семантику и поведение, особенно в случае, когда один вариант всегда выбран. Теперь позвольте мне упомянуть критерий из WCAG 3.2.2 о полях (уровень A):
Изменение состояния любого пользовательского элемента не должно влечь за собой автоматическое изменение контекста без уведомления об этом пользователя перед самим изменением.
Давайте применим это на практике. Представьте себе сортируемый список студентов. Может быть визуально очевидно, что сортировка происходит незамедлительно, но это не обязательно так для всех людей. Таким образом, при использовании , мы рискуем нарушить правила WCAG, поскольку контент страницы изменился, а это попадает под понятие «изменение контекста».
Чтобы соблюсти критерий, мы должны уведомить пользователя о действии до того, как он начнёт взаимодействовать с элементом или же поставить сразу после списка, чтобы подтвердить изменения.
Тем не менее, использование наряду с созданием пользовательского меню является хорошим подходом, когда речь заходит о несложных меню, требующих изменение содержимого страницы. Просто помните, что от вашего решения зависит объём работ, необходимых для создания полностью доступного компонента. Это как раз тот случай, когда гибридный селект может выручить.
Заключение Скопировать ссылку
Вся эта идея зарождалась как невинный CSS-трюк. Но после всех этих исследований, я вновь убедилась, что создание уникальных элементов для юзабилити с сохранением полной доступности — непростая задача.
Создание действительно доступных селектов (или же любого вида выпадающего списка) сложнее, чем может казаться. Руководство WCAG даёт прекрасные инструкции наряду с лучшими практиками, но без конкретных примеров использования эти инструкции носят рекомендательный характер. Не говоря уже о том, что поддержка ARIA слабая, а внешний вид и поведение браузерного отличаются в разных браузерах.
Гибридный селект — это всего лишь ещё одна попытка создать красивый селект, сохранив при этом как можно больше изначальных функций. Не расценивайте этот эксперимент как попытку извинения за уменьшение доступности. Скорее это попытка угодить обоим мирам. При наличии ресурсов, времени и необходимых навыков пожалуйста, сделайте всё как надо и не забудьте протестировать всё на разных пользователях перед тем, как публиковать своё творение.
P.S. Не забудьте выбрать правильное название при создании выпадающего списка