От автора: Элементы формы! Сколько мучений доставляет их оформление, не так ли? Так и хочется заменить их все какой-нибудь своей разметкой и CSS. Но проблема кроется в том, что весь набор элементов div и span будет бесполезен с семантической точки зрения, и у него будут отсутствовать те поведенческие свойства, которые делают доступным стандартный элемент input с типом radio.
1 2 3 4 |
<div class="radio-label"> <div class="radio-input" data-checked="false" data-value="accessible"></div> Доступность? </div> |
Это всего лишь одинокий кусочек текста, спрашивающий про доступность. Действительно печально. Чтобы этот код даже просто начал работать, как надо, нам потребуется применить всевозможные вспомогательные технологии WAI-ARIA. Как поется в бессмертной песне британской рок-группы Iron Maiden – «Стоит ли шутить с безумием?»
1 2 3 4 5 6 |
<div class="radio-label" id="accessible-radio"> <div class="radio-input" data-checked="false" data-value="accessible" aria-labelledby="accessible-radio" role="checkbox" aria-checked="false"> </div> Доступность? </div> |
Наш пример по-прежнему сто процентов недоступный, потому что нам предстоит еще, следуя стандарту, наделить его всеми традиционными видами поведения и сделать активными все необходимые сочетания клавиш. А для этого потребуется использовать атрибут tabindex и, в изобилии, код на JavaScript — и вы знаете, что? Я даже не собираюсь пробовать идти данным путем.
То, что у меня получилось сделать, вы можете посмотреть в демо-примере на сайте CodePen. А дальше последует объяснение данной техники.
Примечание: Если до этого вы не использовали клавиатуру для взаимодействия с радиокнопками, то знайте, что вы можете переключиться на активную кнопку с помощью клавиши TAB, а поменять ее можно с помощью стрелок Вверх и Вниз. Это стандартное поведение, предусмотренное браузером, а не эмуляция на JavaScript.
Используйте то, что уже есть
Если подходить к решению вопроса с точки зрения доступности, то нужно рассматривать HTML в качестве интерфейса, а CSS — просто как внешнее оформление, как брендирование. Соответственно, нам нужно найти способы получения контроля над эстетикой пользовательского интерфейса, не возлагая надежды на переработку разметки, которая означает отклонение от стандартов.
Что мы знаем о радиокнопках?
Первое, что нам известно о радиокнопках, это то, что они могут находиться либо в отмеченном состоянии, либо нет. Не думайте сейчас о технологии ARIA, это просто HTML атрибут checked.
1 2 3 4 5 6 7 8 9 10 11 12 |
<label for="accessible"> <input type="radio" value="accessible" name="quality" id="accessible">доступный </label> <label for="pretty"> <input type="radio" value="pretty" name="quality" id="pretty"> симпатичный </label> <label for="accessible-and-pretty"> <input type="radio" value="pretty" name="quality" id="accessible-and-pretty" checked> доступный и симпатичный </label> |
По счастливой случайности мы можем выразить отмеченное состояние с помощью CSS псевдо-класса :checked :
1 2 3 |
[type="radio"]:checked { /* здесь пишутся свойства */ } |
Меньше радует то, что мы можем прописать не так уж много свойств, которые будут работать — особенно одновременно хорошо во всех браузерах. Радиокнопки упорно отказываются подчиняться нашей воле.
Селектор соседних элементов
Я люблю селектор соседних элементов с той страстью, которую мужчина, пожалуй, не должен тратить на CSS селекторы. Данный селектор позволяет мне стилизовать элементы в соответствии с сущностью элементов, предшествующим им. Это очень мощный инструмент в отношении наших радиокнопок, потому что он позволяет нам переложить внешнее оформление различных состояний на те элементы, которые легко поддаются изменению.
1 2 3 |
[type="radio"]:checked + span { /* стили для элемента span, идущего после отмеченной радиокнопки */ } |
Мы, конечно, должны будем добавить в нашу разметку элементы span, но еще более тяжкая участь могла бы выпасть на долю HTML.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<fieldset> <legend>Radio Control Quality</legend> <label for="accessible"> <input type="radio" value="accessible" name="quality" id="accessible"> <span>доступный</span> </label> <label for="pretty"> <input type="radio" value="pretty" name="quality" id="pretty"> <span>симпатичный</span> </label> <label for="accessible-and-pretty"> <input type="radio" value="pretty" name="quality" id="accessible-and-pretty" checked> <span>доступный и симпатичный</span> </label> </fieldset> |
На самом деле мы не собираемся оформлять текст, находящийся внутри элемента label, но мы должны создать необходимые взаимосвязи, чтобы убрать визуальный отклик с элемента input. А оформление радиокнопки, по сути, переместится на псевдо-элемент ::before у тега span.
Чтобы спрятать радиокнопку, нужно лишь применить правильную технику, например, такую, как та, что была найдена мной в CSS коде HTML5 шаблона Boilerplate:
1 2 3 4 5 6 7 8 9 |
[type="radio"] { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } |
Да, но если она будет спрятана, то как же кому-то удастся кликнуть по ней? После перемещения радиокнопки внутрь тега label, браузеры превращают тег label в такой же переключатель. Это хорошая практика, т.к. в любом случае она расширяет «область попадания» и без того маленького элемента управления.
Оформление
Как уже упоминалось ранее, мы будем использовать псевдо-контент, чтобы подделать нашу «радиокнопку». И это позволит нам использовать оформление текста у элемента label отдельно.
1 2 3 4 5 6 7 8 9 10 11 12 |
[type="radio"] + span::before { content: ''; display: inline-block; width: 1em; height: 1em; vertical-align: -0.25em; border-radius: 1em; border: 0.125em solid #fff; box-shadow: 0 0 0 0.15em #000; margin-right: 0.75em; transition: 0.5s ease all; } |
Обратите внимание на использование свойств border и box-shadow для создания концентрических кругов. Стиль отмеченной кнопки плавно изменяется за счет растяжения радиуса тени и проявления зеленого цвета. Что-то наподобие этого обычно задается при использовании переменных в Sass.
1 2 3 4 |
[type="radio"]:checked + span::before { background: green; box-shadow: 0 0 0 0.25em #000; } |
Никогда не забывайте
Все, что осталось, — это добавить стилей для фокуса, чтобы те, кто пользуется клавиатурой, могли увидеть, какой элемент выбран. Было бы достаточно добавления свойства outline со значением thin dotted для элемента span, но я предпочел добавить стрелку в кодировке Unicode, указывающую на элемент, с помощью псевдо-класса ::after. Такое оформление является более выразительным, чем то, которое предоставляется производителями браузеров по умолчанию. Оно помогает улучшить доступность в состоянии фокуса.
1 2 3 4 5 6 |
[type="radio"]:focus + span::after { content: '\0020\2190'; font-size: 1.5em; line-height: 1; vertical-align: -0.125em; } |
Браузер IE8
IE8 — это, конечно, проблема, т.к. он не поддерживает ни псевдо-класс checked, ни свойства box-shadow и border-radius, с помощью которых мы оформили наши кнопки. Поддержка селекторов может быть улучшена с помощью такой библиотеки, как, например, Selectivizr, а стили могут быть добавлены различными способами (возможно, с помощью фонового изображения?), но лично я предпочитаю следовать принципам отказоустойчивости (graceful degradation). Препроцессоры Sass или LESS могут быстро изолировать проблематичные участки кода.
Обратите внимание, что такие улучшения для тега label, как свойство cursor: pointer, применяются ко всем браузерам.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
/* По одной радиокнопке на строке */ label { display: block; cursor: pointer; line-height: 2.5; font-size: 1.5em; } :not(.lt-ie9) { /* стили для скрытия элемента в HTML5 шаблоне Boilerplate */ [type="radio"] { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } [type="radio"] + span { display: block; } /* стили для обычного, неотмеченного состояния */ [type="radio"] + span::before { content: ''; display: inline-block; width: 1em; height: 1em; vertical-align: -0.25em; border-radius: 1em; border: 0.125em solid #fff; box-shadow: 0 0 0 0.15em #000; margin-right: 0.75em; transition: 0.5s ease all; } /* стили для отмеченного состояния с использованием псевдо-класса :checked */ [type="radio"]:checked + span::before { background: green; box-shadow: 0 0 0 0.25em #000; } /* никогда не забывайте про стили для состояния фокуса */ [type="radio"]:focus + span::after { content: '\0020\2190'; font-size: 1.5em; line-height: 1; vertical-align: -0.125em; } } |
Заключение
Вот вы и получили решение для создания собственных радиокнопок, которое использует полный набор вот этого…
HTML
CSS
… и ничего из этого:
JavaScript
WAI-ARIA
«Изобретение велосипеда»
«Танцы с бубном»
И, естественно, данная техника может также применяться для элементов checkbox, но вы должны внимательно продумать оформление для выбранного (отмеченного) состояния. Итак, что вы думаете? Обычные символы в Unicode? Иконочный шрифт? Фоновое изображение? Или, может быть, фигура, созданная на чистом CSS?
Автор: Heydon Pickering
Источник: //www.sitepoint.com/
Редакция: Команда webformyself.