Нативные Select’ы на любой вкус.

В данной статье предлагаю поговорить о кастомной стилизации инпута селекта (<select>) в виде кнопки.

Вступление

Современные браузеры предлагают нативные реализации достаточно большого количества инпутов, это type=date, type=month, type=email и множество других. Многие из них были добавлены “относительно” недавно. Но в html существует такой долгожитель как <select>, который предоставляет выбор значения из выпадающего списка.

Как правило, селект выглядит как инпут с иконкой. По нажатию на данный элемент появляется системное выпадающее меню. В чем прелесть нативного селекта? Нативный селект на мобильных ОС предлагает удобный и понятный пользователю кейс. Например, селект для iOS представляет собой “барабан”, а для Android — попап с пунктами для выбора. В то же время для практически всех desctop’ных ОС такой селект выглядит невзрачно (как обычный выпадающий список). Поэтому зачастую данный элемент управления разработчики пытаются стилизовать полностью кастомно, и от этого могут страдать пользователи мобильных платформ. В HeadHunter было принято волевое решение — стилизовать все простые селекты только на уровне визуального инпута, а выбор нужного пункта оставить за нативной системной реализацией. (под простыми селектами — здесь понимаются селекты, которые предполагают выбор только одного варианта, без мультивыбора, поиска и т.п.)

Сам по себе селект стилизовать не так сложно, например, селект, который входит в “библиотеку общих компонент HH” — bloko, выглядит таким образом:

Пример работы нативного селекта с кастомной стилизацией инпута

Стилизуются такие селекты достаточно просто. Как правило, используется css для задания размеров, границ, отступов, шрифта и т.п. Например, селект, который был представлен выше, можно получить, использовав следующий набор стилей:

Такие селекты предназначаются для выполнения действия — “выбор варианта”. Данный элемент управления по UX не предполагает дальнейшего действия после выбора. Для совершения некоторого действия необходимо использовать элемент управления “кнопка”. Однако, что делать, если при клике на кнопку необходимо открыть подобный выпадающий список?


Проблема

Таким образом, появилась необходимость стилизовать нативный селект как кнопку.

(решение для нетерпеливых — http://jsbin.com/qobado/edit?html,css,output . Описание решения в разделе “Заход #3”)


Какие отличительные черты кнопки от селекта? Кнопка не имеет иконки выпадающего списка справа, текст у кнопки, как правило, центрируется по середине.

Поиск решения

Контрол <select> не предполагает использования по типу “кнопка” по умолчанию и не поддерживает выравнивание текста любым отличным способ от “слева” (есть свойство text-indent, но оно не гарантирует выравнивание текста по центру). Таким образом, главной задачей стал поиск решения выравнивания текста.

Заход #1 Labels

Первой попыткой стала стилизация label как кнопки и работа с select через связку id-for:

Данный способ мог бы оказаться наиболее подходящим, если бы не критическая проблема — клик на label не открывает select :-/

Заход #2 JS

Вторая попытка — решить проблему c помощью js:

На данном этапе было рассмотрено несколько подходов к решению:

  1. Попытка стриггерить событие клика.
  2. Попытка раскрыть список программно.

Данный эксперимент показал, что <select> не “обрабатывает” события ни клика, ни моделирования событий mousedown\mouseup или touchstart\touchend. “Раскрыть” список можно только изменив его аттрибут size, но это может нарушить существующую верстку страницы и не является выпадающим меню. Эксперименты с JavaScript выложил — http://jsbin.com/bevegotela/edit?html,js,output . Поэтому данный способ тоже потерпел неудачу.

Заход #3 “визуальный подмен”

Третьим способом, который я рассматривал, стал способ визуальной подмены. Для этого отображаем обычную кнопку, при этом поверх кнопки устанавливаем <select>.

Шаг 1: определяем кнопку, не забывая задать значения для :hover и :active

Полученная кнопка:

Теперь создаем новый блок — select-button. Он будет состоять из блока: select-button, модификатора для блока select-button_stretched (для растяжения на всю ширину) и элемента — селекта select-button__select:

Самой важной строкой в данном коде является 24 строка — opacity: 0.

Представляем нашу html структуру следующим образом:

Итак, в чем состоит данный подход? Мы создаем блок select-button, который отображается как position:relative, внутрь этого блока инкапсулируем блок button и элемент .select-button__select, который с помощью абсолютного позиционирования “накрывает” .button по всему размеру, физически “закрывая” кнопку. Opacity: 0, в свое время гарантирует, что пользователь будет видеть только кнопку. Такой подход позволяет решить задачу стилизации селекта как кнопки.

Однако, у данного подхода есть свои минусы. Перечислим их:

  1. Кнопка потеряла стили для :hover и :active state, так как на самом деле пользователь делает :hover и :active с селектом.
  2. Табуляция идет как по селектам, так и по кнопкам. Причем клик на кнопку ничего не дает.

Попробуем разрешить и эти проблемы:

Создадим микс select-button__button для кнопки и определим его поведение:

Таким образом, нами будет решен вопрос :hover для кнопки. :active же решать не надо, так как при клике на кнопку будет отображен select, и кнопка в любом случае не сможет быть в состоянии :active.

Остается последняя проблема — работа с :focus. Данную задачу выполним в несколько шагов:

  1. Запрещаем табуляцию по кнопкам, добавляем tabindex=”-1"
  2. Переносим в html структуре select перед button. Выставляем для button (при помощи элемента .select-button__button) z-index: 1, а для .select-button__select: z-index: 2
  3. При помощи селектора .select-button__select:focus + .select-button__button задаем стили для :focus. Например, в моем случае, это может быть border-color: #0c59a7;

Приведем конечный результат (с примером работы)— jsBin http://jsbin.com/qobado/edit?html,css,output

Полученный код:

Браузерная поддержка

Данный код достаточно прост и поэтому работает корректно во всех браузерах, которые поддерживают “стилизацию” селекта как такового. Все современные браузеры работают с данными селектами корректно. Если рассматривать браузеры “постарше”, то проблемы возникают с IE9-. Это происходит по причине, что данные браузеры не позволяют стилизовать селект. Чтобы обработать данный случай достаточно добавить стили для “старых” браузеров, которые расширят селект по высоте (чтобы был равен кнопке). Таким образом, селект займет все необходимое пространство и пользователи старых браузеров не получат проблем с функционалом.

Данный юзкейс может быть применен в таких местах, когда по нажатию на кнопку пользователю необходимо предоставить “диалог” уточнения своего выбора. Например, запрос на выбор города для того, чтобы затем не просто “отобразить” город в селекте, но и изменить “состояние” приложения. В случае с городом, это может быть добавление города в список городов.

Другим примером является выбор дополнительного метода связи при редактировании контактов в мобильном резюмебилдере сайта http://m.hh.ru . При клике на кнопку выбора пользователю предоставляется диалог выбора способа связи, и после выбора пункта на форму заполнения данных добавляются новые поля.