Как-то вечером я убивал время, читая статьи в интернете, и наткнулся на вот этот хабропост пользователя Cyapa, где расписано, как кастомизировать select на чистом css. В процессе просмотра данного решения нашел несколько весьма неудобных моментов, которые постарался исправить в своем решении этой задачи. Итак, приступим.

Пока смотрел код того поста, заметил, что автор сделал селект, который невозможно закрыть при клике вне его, а также невозможность открыть селект при клике на название элемента, если оно уже выбрано.

В своем варианте кастомизации я, так же как и предыдущий автор, использовал label, input[type=«radio»] и мощь css-селекторов. Так как сам селект средствами css полностью кастомизировать нереально, я имитировал поведение селекта.

Для начала, я накидал вот такую разметку:

Показать html код
<!-- Лейбел, при клике на который открывается селект -->
<label for="select" class="select">
    <!-- Этот инпут отвечает за закрытие селекта при клике за его пределами. Также этот инпут является стандартным значением нашего селекта -->
    <input type="radio" name="list" value="not_changed" id="bg" checked />
    <!-- Этот инпут является переключателем селекта в состояние "открыт" -->
    <input type="radio" name="list" value="not_changed" id="select">
    <!-- Этот лейбел используется для создания подложки, клик по которой приводит к закрытию селекта -->
    <label class="bg" for="bg"></label>
    <!-- Этот див - список параметров селекта, где #text - то, что выводится, когда ничего не выбрано -->
    <div class="items">
      <!-- Инпут, при клике на который происходит выбор параметра и сворачивание селекта -->
      <input type="radio" name="list" value="first_value" id="list[0]">
      <!-- Название параметра -->
      <label for="list[0]">First option</label>
      <!-- Инпут, при клике на который происходит выбор параметра и сворачивание селекта[1] -->
      <input type="radio" name="list" value="second_value" id="list[1]">
      <!-- Инпут, при клике на который происходит выбор параметра и сворачивание селекта[1] -->
      <label for="list[1]">Second loooooong option</label>
      <!-- Текст селекта по умолчанию. Выводится тогда, когда ничего не выбрано -->
      <span id="text">Select something...</span>
    </div>
</label>


При просмотре данного кода у вас мог возникнуть вопрос: «Почему у всех инпутов одинаковое имя?». Отвечу сразу: это сделано для того, чтобы наш селект адекватно вел себя(открывался и закрывался тогда, когда нужно). Но обо всем по порядку. Давайте перейдем к самой интересной, на мой взгляд, части — css.

Показать css код
/* скрываем все инпуты, чтобы все выглядело красиво */
input
{
  display: none;
}

/* стилизуем стандартный текст лейбела(желательно смотреть этот стиль после .items) */

#text
{
  position: absolute;
  display: block;
  top: 0;
  padding-left: 10px;
}

/* Задаем параметры нашего селекта - ширину, высоту и line-height(для центрирования текста по вертикали;этот парметр меньше ширины на 4px, т.к. в нашем блоке есть border размером в 2px со всех сторон) */

.select
{
  display: inline-block;
  width: 160px;
  height: 34px;
  line-height: 30px;
  position: relative;
}

/* Это наша стрелочка, показывающая, что селект можно раскрыть */

.select:before
{
  content: ">";
  display: inline-block;
  background: white;
  position: absolute;
  right: -5px;
  top: 2px;
  z-index: 2;
  width: 30px;
  height: 26px;
  text-align: center;
  line-height: 26px;
  border: 2px solid #ddd;
  transform: rotate(90deg);
  cursor: pointer;
}

/* Если ничего не выбрано, то наш изначальный текст черного цвета, как и должно быть */

.select input[name="list"]:not(:checked) ~ #text
{
  color: black;
  background: white;
}

/* Если же что-то выбрано, то наш текст становится невидимым и встает сверху выбранного параметра, чтобы при клике на него можно было заного открыть селект, что не было реализовано прошлым автором */

.select input[name="list"]:checked ~ #text
{
  background: transparent;
  color: transparent;
  z-index: 2;
}

/* Стилизация выключенного селекта */

#select:disabled ~ .items #text
{
  background: #eee;
}

/* Стилизация блока с опциями. min-height сделана для фикса высоты при абсолютном позиционировании, overflow же сделан для фиксированной высоты(см. ниже) */

.items
{
  display: block;
  min-height: 30px;
  position: absolute;
  border: 2px solid #ddd;
  overflow: hidden;
  width: 160px;
  cursor: pointer;
}

/* Если наш селект закрыт, то он имеет высоту 30px(сделано для того, чтобы слишком большие надписи не растягивали его в высоту)  */

#select:not(:checked) ~ .items
{
  height: 30px;
}

/* Все лейбелы(названия опций) изначально скрыты */

.items label
{
  border-top: 2px solid #ddd;
  display: none;
  padding-left: 10px;
  background: white;
}

/* Тут много объяснять не надо - просто выделение при наведении */

.items label:hover
{
  background: #eee;
  cursor: pointer;
}

/* Опять же фикс из-за абсолютного позиционирования */

#select:checked ~ .items
{
  padding-top: 30px;
}

/* Если наш селект открыт, то надо сделать все опции видимыми */

#select:checked ~ .items label
{
  display: block;
}

/* Если какая-либо опция была выбрана, то сделать ее видимой(при выборе селект автоматически закроется) */

.items input:checked + label
{
  display: block!important;
  border: none;
  background: white;
}

/* При открытии селекта создать подложку во весь экран, при клике на которую селект закроется, а значение останется пустым. background сделан для наглядности */

#select:checked ~ .bg
{
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  z-index: 0;
  background: rgba(0,0,0,0.4);
}


Вот она — самая интересная часть, в которой надо осмыслить как смена выбора инпута (все инпуты с типом радио имеют одинаковое имя => мы можем выбрать только один из них) влияет на наш селект. Еще одной особенностью этого варианта является возможность отключить селект, используя атрибут disabled на #select.

Готовый пример вы можете найти здесь.

Вот, собственно, и все. Парочка минусов предыдущего автора пофикшены, так что это решение, полагаю, на данный момент можно считать идеальным css-вариантом селекта. Надеюсь, мое решение кому-нибудь пригодится.

Комментарии (11)


  1. HawkeyePierce89
    07.03.2019 10:55

    Грустно это всё, на самом деле. Что вроде бы фронтенд весь такой идёт вперёд семимильными шагами в светлое (не факт, но допустим) будущее, а такая давно нужная и необходимая штука, как стилизация нативного селекта до сих пор всеми игнорируется и никто ничего не думает по этому поводу из тех, кто отвечает за разработку веб-стандартов.

    И приходится делать вот такие, если уж говорить прямо, костыли. Нет, подход интересный, но только на поиграться, потому что такую штуку использовать как замену нативного select нельзя.


  1. dpr
    07.03.2019 11:21

    Такое решение может пригодится разве что для списков с небольшим количеством опций выбора. Две-три, максимум пять. Длинные списки будут сильно уступать нативным в удобстве использования на мобильных телефонах.


  1. artalar
    07.03.2019 11:23
    -1

    Спасибо!
    Может быть опубликуете тут? Было бы удобно: poet.codes


    1. Zibx
      07.03.2019 11:38

      Что было бы там удобно? Как вообще этим пользоваться?


      1. artalar
        07.03.2019 11:47

        Отдельно код, отдельно комментарии, при этом все удобно связанно: poet.codes/e/KMXQEO2gquN (наведите мышкой на выделенные слова).

        Внизу главного лендинга есть видео как это делать. Что там может быть не понятно?


        1. Zibx
          07.03.2019 14:02

          Не понятно кому от этого станет удобно. В статье уже есть и код и интерактивный пример с которым можно дополнительно поиграть.


          1. artalar
            07.03.2019 14:05

            Код с большим количеством комментариев сложнее читать, формат `комментарии — линк — код` мне кажется удобнее и вкратчивее.


  1. sfi0zy
    07.03.2019 11:34

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

    P.S.: Демки с codepen можно вставлять прямо в статью.


    1. JustDont
      07.03.2019 12:52

      Вот да. Если у вас решение «на чистом цсс», но там всё равно нифига не работает (клавиатура, читалка, да вообще просто если список будет длинный, то всё умрёт) — то такие штуки в целом бессмысленны. Тут уже надо брать JS и делать полную имитацию нативного селекта с поддержкой всего, в них по крайней мере есть практический смысл (да и море уже готовых 3rd-party реализаций).


    1. artalar
      07.03.2019 13:24

      А, разве, проставить специальные атрибуты будет не достаточно?


  1. ThisMan
    07.03.2019 13:47

    Нативный селект умеет правильно позиционировать выпадашку, в зависимости от того хватает для нее места или нет. Тут так не получится, и да, для одного селекта столько кода? Нет уж спасибо, где мой старый добрый js?