Сколько я не мучил поисковик, а решения этого вопроса так и не нашлось. Конечно, всегда можно использовать JS и это нормально, но иногда В заголовке я несколько приврал: всем известно, что select полностью кастомизировать нельзя, поэтому мы будем имитировать select. Сделаем мы это с помощью нескольких radio, нескольких label и одного div. Не так уж и много, правда?
Структура
<!-- Вот эта штука и заменит нам select --> <label class="selectGeneral" placeholder="select your OS..."> <!-- Да, да, placeholder тоже поддерживается --> <!-- Если этот radio выбран - select открыт, если нет - select закрыт --> <input type="radio" name="OS"> <!-- Это wrapper для вариантов выбора --> <div> <!-- Группа radio и есть аналог оригинальных option --> <input type="radio" name="OS" value="linux" id="OS[linux]" > <!-- Аналог видимой части option --> <label for="OS[linux]">linux</label> <input type="radio" name="OS" value="windows" id="OS[windows]" > <label for="OS[windows]">windows</label> <input type="radio" name="OS" value="other" id="OS[other]" > <label for="OS[other]">other</label> </div> </label>
Корневая label будет всегда видимой частью нашего альтернативного select. При клике на нее будет переключаться основной radio, который и отвечает за состояния открыт/закрыт у этой конструкции. Placeholder и традиционная стрелочка будут реализованы через псевдоэлементы :before и :after у корневой label. Все остальное, кроме wrapper (тот самый единственный div), по умолчанию скрыто. Почему мы не скрываем wrapper? Потому что в нем находится выбранный элемент (если такой есть), а он должен быть виден всегда.
Основная часть
label.selectGeneral { display: block; position: relative; } /** Это обещанный placeholder **/ label.selectGeneral:before { content: attr(placeholder); /** Взять текст из атрибута placeholder **/ display: inline-block; position: absolute; top: 0; left: 0; z-index: -1; max-width: 100%; text-align: left; white-space: nowrap; /** Не переносить слова **/ color: #adadad; overflow-x: hidden; /** Скрыть лишнее **/ } /** А это стрелочка **/ label.selectGeneral:after { content: "<>"; display: inline-block; position: absolute; top: 0; right: 0; text-align: center; background-color: #ffffff; transform: rotate(90deg); } label.selectGeneral input, label.selectGeneral label { display: none; } label.selectGeneral div { min-width: 100%; max-height: 500px; /** Ограничения на высоту списка выборов **/ overflow-x: hidden; }
Осталось добавить немного магии — реализовать поведение всего этого добра. Магия будет основана на соседних селекторах и :checked у radio. Выбранный элемент виден всегда и при закрытом состоянии select перекрывает собой placeholder. При открытии select, показываются все остальные элементы для выбора, а wrapper, в который они вложены, немного съезжает вниз, что-бы было видно placeholder и пользователь не забыл, что же он, собственно, выбирает.
Поведение
/** Если наш альтернативный select открыт, то wrapper **/ label.selectGeneral input[type="radio"]:checked ~ div { position: absolute; /** приобретает абсолютную позицию **/ top: <высота label.selectGeneral>; /** и смешается немного вниз, открывая placeholder **/ overflow-y: auto; } /** Все label внутри wrapper'а при открытом select **/ label.selectGeneral input[type="radio"]:checked ~ div > label, /** И выбранный вариант **/ label.selectGeneral input[type="radio"]:checked + label { display: block; /** должны быть видимыми **/ } /** Подсветим элемент на который наведена мышь при открытом select **/ label.selectGeneral input[type="radio"]:checked ~ div > label:hover { background-color: #ffa834; } /** При закрытом select, нужно делегировать событие клика мышью с выбранного элемента родительскому label **/ label.selectGeneral input[type="radio"]:not(:checked) ~ div > input[type="radio"]:checked + label { position: relative; z-index: -1; }
В конце применен трюк с z-index, который позволяет расположить дочерний элемент ниже (глубже по z-оси) родительского. Этот замечательный факт позволяет делегировать реакцию на клик по выбранному элементу нашему select'у, что бы он раскрылся.
Рабочий пример можно лицезреть тут.
Из плюсов подхода можно отметить:
- Кроссбраузерность — это работает везде, где работают label
- Относительная легкость — код не перенасыщен лишними элементами
- Возможность полной кастомизацией — стилизуется каждая мелочь
- Гибкость — не придется дописывать новых стилей при добавлении пунктов выбора
Конечно же, есть и минусы, куда же без них:
- Отсутствие деградации — если не поддерживается, то стандартный select не спасет ситуацию (как подсказывают комментаторы — очень важный пункт для портативных устройств)
- Легкость легкостью, а дополнительный код все таки будет
- Невалидный код — div внутри label и атрибут placeholder у нее же, это не по стандарту
- Это не совсем минус, но эта штука не захлопывается сама при клике в любом месте экрана
Не думаю, что кто-то станет использовать это в production, но подход явно имеет право на жизнь.
UDPATE: Изменен код — добавлена функция автоматического закрытия select при выборе.