Frontender Magazine

Семантичный CSS с умными селекторами

«Задача всегда диктует внешний вид. Это закон.» Эти слова принадлежат архитектору и «отцу небоскребов» Луису Салливану (Louis Sullivan). Это золотое правило для архитекторов, которые не хотят чтобы сотни людей были раздавлены тяжестью колоссального строения. В дизайне на первом месте всегда должно быть выполнение задачи, которое влечет за собой определённый внешний вид. Если бы на первом месте для архитектора был внешний вид небоскрёба, красивым его сделать было бы намного проще, однако ценой этому стала бы его безопасность.

Это что касается архитекторов. А как насчёт фронтэнд архитекторов, хоть нас часто и не считают архитекторами всерьез? Должны ли мы следовать этому правилу или им можно пренебречь?

С появлением объектно-ориентированного CSS (OOCSS) стало очень модно «разделять семантику представления и семантику разметки». Используя названия классов, которые не имеют какого-либо определённого значения, можно разделить управление документом и его внешним видом.

обдумывание

В этой статье мы изучим альтернативный подход к написанию CSS, который предусматривает сочетания семантики документа и визуального дизайна при любой возможности. Мы рассмотрим как, используя «умные» селекторы, можно задействовать функциональную суть семантического HTML, чтобы получить правильную разметку. Если у вас все в порядке с кодом, то и дизайн у вас получится такой, как нужно.

Если вы как и я не можете сосредоточиться более чем на одном занятии или мысли за раз, надеюсь применение изложенных в этой статье идей поможет упростить ваш рабочий процесс и поможет с большей легкостью переключаться с проекта на проект. К тому же, в заключительной части мы рассмотрим практическую стратегию: мы создадим CSS-букмарклет, содержащий умные селекторы по атрибуту для тестирования HTML и обнаружения ошибок с помощью псевдо-содержимого.

Умные селекторы

С изобретением таблиц стилей у нас появилась возможность физически разделять разметку документа (HTML) и код, отвечающий за его представление (CSS). Это помогает писать более грамотный и соответствующий стандартам код ровно настолько, насколько изобретение пульта дистанционного управления влияет на качество телевидения. Зато таблицы стилей сделали веб-разработку более удобной. Благодаря возможности стилизировать несколько элементов с помощью одного селектора (например, p для абзацев), необходимость сохранения целостности представления веб-сайта и его поддержки стала существенно менее пугающей.

Чушь

Селектор p является примером самого простого умного селектора. Селектор p является умным потому, что обладает встроенным пониманием своего места в семантической классификации. Он без вмешательства автора знает как обозначить абзац и как его стилизировать, это простой, но эффективный селектор, особенно если вспомнить об автоматически генерируемых абзацах в редакторах WYSIWYG.

Итак, если это умный селектор, какой селектор тогда неумный? Любой селектор, при использовании которого разработчику необходимо вмешаться и внести в документ изменения чтобы добиться стилистического выделения, можно считать неумным. Класс является классическим примером неумного селектора, потому что он не входит в перечень селекторов которые принято считать семантическими. Классам можно присваивать осмысленные названия и рационально располагать их в документе, но они не настолько умны чтобы позаботиться о своем представлении, так же как и браузеры недостаточно умны чтобы позаботиться о представлении классов вместо вас.

Неумные селекторы увеличивают количество времени на разработку веб-сайта, потому, что для каждого отдельного элемента их нужно указывать в индивидуальном порядке. Если бы у нас не было тегов p, для создания абзацев нам пришлось бы использовать неумные селекторы, возможно с классом .paragraph для каждого из них. Одним из недостатков такого положения дел является то, что CSS утрачивает гибкость, то есть его нельзя применить в HTML не прописав предварительно классы в соответствующих местах.

paragraph

Иногда неумные селекторы кажутся нам необходимыми, или по крайней мере более простыми в применении, и только некоторые отваживаются положиться исключительно на умные селекторы. Однако, иногда применение неумных селекторов доходит до абсурда и может привести к дисбалансу между структурой и представлением документа. Я расскажу о том, как часто применение неумного селектора .button доходит до абсурда.

Да здравствует разнообразие

Умные селекторы не ограничиваются базовыми элементами, предложенными в спецификации HTML. Можно построить сложный умный селектор проведя различие между базовыми элементами с помощью комбинации контекстного и функционального определения . Некоторые элементы, например <a>, обладают множеством функциональных различий, которые можно учитывать и использовать. Другие элементы, например <p>, редко отличаются по функциях, однако играют немного разные роли в зависимости от контекста.

header p {
   /* стиль для вступительных абзацев */
}

footer p {
   /* стиль для заключительных абзацев */
}

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

иерархия

Разумеется, некоторые приверженцы OOCSS относятся к дочерним селекторам с подозрительностью и рьяно настаивают на разметке похожей на пример ниже, которая соответствует документации БЭМ «Определение».

<ul class="menu">
  <li class="menu__item">…</li>
  <li class="menu__item">…</li>
</ul>

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

Атрибуты гиперссылок

Даже те кто отстаивает необходимость концептуального разделения CSS и HTML, с готовностью признают что некоторые атрибуты (даже большинство из них, кроме классов и пользовательских атрибутов данных) влияют на внутреннее функционирование документа. Без href ваша ссылка не будет никуда вести. Без type браузер не будет знать какой тип input нужно отобразить. Без title ваш abbr с текстом «ООП» может обозначать и Общество Охраны Памятников, и Организацию Освобождения Палестины.

Некоторые из этих атрибутов нужны, чтобы улучшить семантику документа, другие отвечают за корректное отображение и работу элемента. Если они отсутствуют, их нужно добавить; а если они включены в элемент, почему бы этим не воспользоваться? Нельзя прописать CSS без HTML.

Атрибут rel

Атрибут rel появился в качестве стандарта для указания зависимости ссылок, способа описания определённого предназначения ссылки. Дело в том, что не у всех ссылок одинаковые функции. Благодаря WordPress rel="prev" и rel="next" являются наиболее часто употребляемыми значениями, которые помогают описать взаимосвязь отдельных страниц блога. С семантической точки зрения, тег a с атрибутом rel остается тегом a, но у нас есть возможность его конкретизировать. В отличии от классов, эта особенность имеет семантическую ценность.

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

[rel="prev"] {
  /* стиль для ссылок «предыдущая страница» */
}

[rel="next"] {
  /* стиль для ссылок «следующая страница» */
}

Такие селекторы атрибутов поддерживаются всеми браузерами кроме самых древних, так что нет причины не использовать их во всех случаях, когда в наличии есть атрибуты. Когда речь идет о специфичности, они обладают тем же весом, что и классы. Так что здесь тоже проблемы нет. Однако, я помню о рекомендации разграничивать семантику разметки и представления. Я не хочу отказываться от атрибутов rel (хотя бы потому что Google их использует), так что я лучше добавлю ничего не значащий атрибут и использую его для стилизации элемента.

<a href="/previous-article-snippet/" rel="prev" class="prev">предыдущая страница</a>

Первым делом нужно отметить, что в примере выше единственной частью элемента, которая не несет в себе семантической информации, является класс. Другими словами, класс — это единственная часть документа, которая в функциональном плане для него никакой ценности не имеет. На практике это значит, что только класс нарушает само правило разделения, для соблюдения которого он был добавлен: он присутствует в разметке документа физически, хоть никак не влияет на его структуру.

Ладно, это все абстрактные размышления; как насчёт влияния на обслуживание сайта? Допустим, что мы используем class для присвоения стиля, давайте посмотрим, что произойдет, когда в связи с правками или перепроектированием сайта некоторые атрибуты будут удалены. Представим что мы добавили перед текстом ссылки [rel="prev"] стрелку с помощью псевдо-элемента:

.prev:before {
  content: '\2190'; /* код для стрелки ("←") */
}

стрелка

Удаление класса повлечёт за собой удаление псевдо-контента, что (само собой) в свою очередь повлечёт удаление стрелки. Без стрелки ничто не говорит о связи ссылки с предыдущей статьей. После тех же действий стрелка, привязанная к атрибуту rel останется нетронутой: класс продолжит управлять представлением, неизменно скрывая отсутствие объявленной взаимосвязи в разметке. Лишь привязывая стиль напрямую к семантическому атрибуту, который отвечает за соответствующую функцию элемента, можно сохранить честность и точность кода. Только если что-либо имеет какую-либо функцию в документе, оно имеет право в нем присутствовать.

Подстроки атрибута

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

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

Готовя эту статью я создал экспериментальный прототип под названием Auticons. Auticons — это иконочный шрифт и набор CSS-правил, которые позволяют стилизировать ссылки автоматически. Все селекторы в файле CSS являются селекторами атрибута и задают стили для правильно прописанных ссылок, без использования классов.

auticons

В большинстве случаев Auticons обращается к значению href чтобы определить функцию ссылки. Можно стилизировать элементы в зависимости от того с чего начинаются или заканчиваются значения их атрибутов, а также в зависимости от того какую подстроку содержит их значение. Ниже приведены некоторые распространённые примеры.

Защищённый протокол

Каждый правильно прописанный (т.е. абсолютный) URL начинается с последовательности URI, после которой идёт двоеточие. Самым популярным в сети является http:, но mailto: (для SMTP) и tel: (служит для указания телефонных номеров) также распространены. Если мы знаем как должно начинаться значение href у ссылки, можно использовать это семантическое условие для привязки стиля. В следующем примере для защищённых страниц мы используем компаратор ^=, который значит «начинается с».

a[href^="https:"] {
   /* стилевые свойства для ссылок на защищённые страницы */
}

замочек

В Auticons ссылки на защищённые страницы декорируются иконкой навесного замка согласно определённому семантическому шаблону, с идентификацией по атрибуту href. Это даёт следующие преимущества:

Этот селектор становится действительно умным, если его использовать для динамического контента. Поскольку ссылки на защищённые страницы являются таковыми даже абстрактно от документа, селектор атрибута может предвидеть их появление: как только редактор добавляет какой-либо контент, содержащий ссылку на защищённую страницу, она сразу принимает соответствующий облик. Не нужно знать что такое классы или вообще особо вникать в HTML, даже простая разметка Markdown будет стилизирована должным образом:

[Ссылка на защищённую страницу](https://payment.example.com/)

Учтите, что использование префикса [href^="https:"] не является безотказным, поскольку не все страницы использующие протокол HTTPS действительно являются защищёнными. Тем не менее, он является настолько же подверженным ошибкам, насколько и браузеры. Большинство браузеров отображают иконку навесного замка в адресной строке при открытии страницы HTTPS.

PayPal

Типы файлов

Как было упомянуто ранее, можно стилизировать ссылки согласно окончанию значения href. На практике это значит, что можно использовать CSS чтобы определить на файл какого типа ведет ссылка. Auticons поддерживает .txt, .pdf, .doc, .exe и много других. Вот пример для .zip, в котором окончание href определено с помощью $=:

[href$=".zip"]:before, 
[href$=".gz"]:before {
   content: '\E004'; /* юникод для иконки архива */
}

Сочетание

Вы ведь знаете, что разработку можно сделать полностью объектно-ориентированной и для задания стилей элементов использовать набор из нескольких классов? То же можно сделать и с селекторами атрибутов. Давайте сравним:

/* CSS для подхода с использованием классов */

.new-window-icon:after {
   content: '[new window icon]';
}

.twitter-icon:before {
  content: '[twitter icon]';
}

/* CSS для подхода с использованием селекторов атрибутов */

[target="_blank"]:after {
   content: '[new window icon]';
}

[href*="twitter.com/"]:before {
  content: '[twitter icon]';
}

(Обратите внимание на компаратор *=, который значит «содержит». Стиль будет применён если строка значения содержит подстроку twitter.com/)

<!-- HTML для подхода с использованием классов -->

<a href="http://twitter.com/heydonworks" target="_blank" class="new-window-icon twitter-icon">@heydonworks</a>

<!-- HTML для подхода с использованием селекторов атрибутов -->

<a href="http://twitter.com/heydonworks" target="_blank">@heydonworks</a>

twitter

Любому редактору, которому нужно добавить ссылку на страницу в Twitter, нужно знать только две вещи: URL-адрес (он наверняка уже знает с каким аккаунтом имеет дело) и как сделать, чтобы ссылка открывалась в новой вкладке (это можно быстро узнать если поискать в Google).

Наследование

Маленькая нерешенная проблемка: что если наша ссылка не содержит ни один из специальных атрибутов? Что если ссылка — это простая старая ссылка без дополнительной информации? Такой селектор легко запомнить и фанатики производительности в восторге от того, что проще него не придумаешь.

a

Поязвили и хватит, могу вас уверить, что наследование в каскаде работает для селекторов атрибута точно так же, как для классов. Для начала стилизируйте базовый a — возможно в целях доступности стоит применить правило text-decoration: underline; затем постепенно улучшайте внешний вид, ссылки используя доступные селекторы атрибута. Браузеры вроде Internet Explorer (IE) 7 не поддерживают псевдо-контент вообще. Благодаря наследованию ссылки хотя бы будут выглядеть как ссылки.

a {
  color: blue;
  text-decoration: underline;
}

a[rel="external"]:after {
   content: '[icon for external links]';
}

Кнопки должны быть кнопками

В следующей части мы подробно рассмотрим создание букмарклета CSS для выявления ошибок в коде. Перед тем как к этому приступить, разберёмся как вообще абсурдные селекторы могут попасть на наш сайт.

Приверженцам OOCSS нравятся классы, потому что их можно использовать несколько раз. Следовательно, .button более предпочтителен чем #button. Однако я могу предложить селектор, который еще лучше подойдёт для стилизации кнопки. Его название очень просто запомнить.

button

Элемент <button> служит для представления кнопки.

W3C Wiki

Topcoat — это построенный на основе BEM UI-фреймворк от Adobe, который следует принципам OOCSS. В Topcoat CSS с различными стилевыми правилами для кнопок занимает больше 450 строчек, если учитывать блоки комментариев. В каждом из этих блоков рекомендуется применять стиль для вашей кнопки в духе этого примера:

<a class="topcoat-button">Кнопка</a>

Этот пример не является кнопкой. Никак нет. Если бы это была кнопка, она была бы прописана в разметке с помощью <button>. По сути, если бы она была прописана как кнопка, можно было бы ожидать что она будет по умолчанию выглядеть как кнопка в любом браузере известном человечеству даже если для нее не были бы применены никакие стили CSS. Но это не кнопка; она прописана с использованием <a>, что делает её ссылкой, более того, ссылкой без href, что значит, что это даже не ссылка. Технически, это всего лишь указатель места вставки ссылки, которую вы ещё не прописали.

акула

Костюм акулы не делает собаку акулой. (Фото: reader of the pack)

Примеры из CSS в Topcoat являются только примерами, но замысел, что предназначение элемента определяет не HTML, а класс, дезориентирует. Никакое количество изменений названия класса с применением «продуманной расстановки дефисов» не может оправдать предложение превратить неумный селектор в абсурдный и просто-напросто неправильно написать код.

Обновление: Уже после того как была написана эта статья в Topcoat.io эти примеры были заменены примерами с <button>. Это прекрасно! Однако, мне все ещё не нравится, что в примерах пропагандируется использование класса .is-disabled вместо более правильного атрибута disabled. Чтобы ознакомиться с мнением обеих сторон, взгляните на мою дискуссию с представителем Topcoat в комментариях. Больше примеров неудачной подстройки веб-стандартов под OOCSS можно найти на semantic-ui.com. «Стандартная кнопка» в их примерах является элементом <div> с пустым <i>.

Ничего не вижу, ничего не слышу

«Если что-либо выглядит как утка, плавает как утка и крякает как утка, скорее всего это и есть утка.»

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

Тем не менее, некоторые продолжат настаивать на использовании a в качестве основы для кнопок. В конце концов, ссылки (немного) проще стилизировать без последствий для контекста. Если ваш выбор — элемент a, есть только один способ максимально приблизить его к настоящей кнопке с точки зрения доступности. Вы наверное уже догадались: нужно использовать ещё один осмысленный атрибут роли WAI ARIA. Чтобы убедиться, что ссылка не просто так выглядит как кнопка, примените следующий селектор атрибута.

[role="button"] {
   /* семантический CSS для видоизменённых элементом, которые объявлены «кнопкой» для вспомогательных технологий */
}

Контроль качества селекторов атрибутов

«CSS даёт столько власти атрибуту класса, что разработчики могут поддаться искушению создать свой собственный "язык разметки" состоящий из элементов, которые не имеют почти ничего общего с представлением (таких как DIV и SPAN в HTML) и предусматривающий присвоение стилей с помощью атрибута "class". Разработчикам следует избегать этого, так как у всех структурных элементов языка разметки часто есть общепринятые значения.»

– «Селекторы», CSS Level 2, W3C

У нас есть два элемента, a и button, чтобы семантически разграничить два абсолютно разных типа функциональных взаимодействий. В то время как гиперссылка обозначает возможность перехода куда-либо, кнопка выступает инициатором события или действия. Суть первого в переходе, второго — в трансформации. Первое способствует разделению, второе — взаимодействию.

Чтобы убедиться, что мы не делаем ничего безрассудного и не путаем ссылки с кнопками, мы создадим CSS-букмарклет, который с помощью умных селекторов по атрибуту будет проводить проверку на валидность и качество двух соответствующих элементов.

Вдохновение было навеяно частично постом Эрика Мейера (Eric Meyer), некоторые идеи были взяты из DiagnostiCSS. В этой таблице стилей объединены селекторы атрибута и селектор :not (или же псевдо-класс отрицания), чтобы определить проблемы в HTML. В отличии от других реализаций, наше уведомление об ошибке будет выводиться на экран с помощью псевдо-контента. Каждая ошибка будет написана шрифтом Comic Sans на розовом фоне.

Когда мы соединяем вместе функцию и форму, в результате видим, что уродливая разметка ведёт к уродливому CSS. Считайте это местью разработчику за надругательство над разметкой. Чтобы попробовать, как все работает, перетащите revenge.css в закладки браузера и щёлкните по закладке, чтобы запустить его на любой странице на ваш выбор. Примечание: на данный момент букмарклет не работает для страниц с протоколом https.

REVENGE.CSS

Перетащите на панель закладок

Правило 1

«Если элемент является ссылкой, у него должен быть атрибут href»

a:not([href]):after {
   content: 'Это должна быть ссылка или кнопка? Она никуда не ведёт!';
   display: block !important;
   background: pink !important;
   padding: 0.5em !important;
   font-family: 'comic sans ms', cursive !important;
   color: #000 !important;
   font-size: 16px !important;
}

Примечание: В этом примере мы проверяем не значение атрибута, а само существование атрибута, т.е. соответствует ли условию [href] элемент, у которого есть атрибут href. Эта проверка должна осуществляться только для гиперссылок, для этого используется префикс a. Правило можно прочитать так: «Для каждого элемента a, который не содержит атрибут [href], должен быть добавлен псевдо-контент с сообщением об ошибке.»

Правило 2

«Если элемент является ссылкой и содержит атрибут href, у него должно быть валидное значение»

a[href=""]:after, a[href$="#"]:after, a[href^="javascript"]:after {
   content: 'Эта ссылка должна быть кнопкой? Она никуда не ведёт!';
   /*… уродливый стиль …*/
}

Примечание: Если href пуст, заканчивается символом # или в нем используется JavaScript, скорее всего мы имеем дело с кнопкой, для которой не используется правильный элемент button. Обратите внимание, что я использую формулировку «начинается с javascript». Обычно чтобы оставить href пустым пишут javascript:void(0), но мы не можем рассчитывать что так прописано во всех случаях без исключения (возможно отсутствие или присутствие пробела после двоеточия, например).

Правило 3

«Если используется класс button, то элемент должен быть кнопкой, по крайней мере с точки зрения доступности»

.button:not(button):not([role="button"]):not([type="button"]):not([type="submit"]):not([type="reset"]):after,
.btn:not(button):not([role="button"]):not([type="button"]):not([type="submit"]):not([type="reset"]):after, 
a[class*="button"]:not([role="button"]):after {
   content: 'Если вы хотите чтобы этот элемент выглядел как кнопка, сделайте его кнопкой, черт побери!';
   /*… уродливый стиль …*/
}

Примечание: В этом примере мы демонстрируем как можно составить цепочку отрицаний проводя проверку атрибутов. Каждый селектор читается так: «Если для элемента применён класс указывающий на то, что перед нами кнопка, но это не элемент button, и для него не указана соответствующая роль, чтобы он был кнопкой с точки зрения доступности, если это не input, который используется как кнопка, значит… кое-кто врёт». Мне пришлось использовать [class*="button"] чтобы уловить множество разновидностей классов на Topcoat (всего 62!), которые не подходят для превращения ссылки в настоящую кнопку. Я заметил, что некоторые разработчики используют button-container и ему подобные для родительских контейнеров, потому добавил оговорку a чтобы избежать некорректных результатов. Вы можете признавать класс .btn, который используется в Twitter Bootstrap, однако вы должны знать (если внимательно читали документацию) что являются ли ссылки или кнопки кнопками при его использовании — спорный вопрос.

Правило 4

«Если для элемента указано role="button", он должен на что-либо ссылаться при отключённом JavaScript»

a[role="button"]:not([href*="/"]):not([href*="."]):not([href*="?"]):after {
   content: 'Используйте резервную ссылку или элемент button.';
   /*… уродливый стиль …*/
}

Примечание: Можно быть вполне уверенным, что если href не содержит /, . (обычно перед расширением файла) или ? (начало строки запроса), скорее всего он липовый. Если вы хотите, чтобы ссылки вели себя как кнопки и возвращали return: false когда JavaScript включён — ладно, но когда JavaScript отключён они должны вести на какую-то страницу. Кстати, это единственная уважительная причина, чтобы не использовать вместо ссылки <button>.

Правило 5

«Гиперссылку нельзя отключить»

a.button[class*="disabled"]:after, 
a.btn.disabled:after,
a[class*="button"][class*="disabled"]:after {
   content: 'Ссылки нельзя отключать. Используйте элемент button с disabled="disabled".';
   /*… уродливый стиль …*/
}

Примечание: Даже старые браузеры понимают атрибут disabled, так что используйте его с подходящим элементом в соответствии со стандартами. Для атрибутов можно применять конкатенацию точно так же как для классов: в части с последними тремя селекторами говорится: «Если мы имеем дело с ссылкой, которая содержит подстроку button и подстроку disabled, должно быть выведено сообщение об ошибке». В таблице стилей Twitter Bootstrap используется вторая форма, .btn.disabled, но не с префиксом a. Ошибкой считается только использование disabled для ссылок.

Правило 6

«У кнопок в формах должен быть прямо указан тип ввода»

form button:not([type]):after {
  content: 'Это кнопка отправки информации, кнопка обнуления полей или что? Используйте type="submit", type="reset" или type="button"';
}

Примечание: Нам нужно определить указан ли тип ввода для кнопок в формах потому что в таком контексте без чётко указанного типа некоторые браузеры интерпретируют кнопки как type="submit". Мы должны быть полностью уверены, что кнопке не будет присвоена функция отправки информации на сервер если она должна отвечать за какое-либо другое действие.

Правило 7

«И ссылки, и кнопки должны содержать какой-либо контент или ARIA-метку»

a:empty:not([aria-label]):not([aria-labelledby]):after, 
button:empty:not([aria-label]):not([aria-labelledby]):after, 
button:not([aria-label]):not([aria-labelledby]) img:only-child:not([alt]):after, 
a:not([aria-label]):not([aria-labelledby]) img:only-child:not([alt]):after {
   content: 'Все кнопки и ссылки должны содержать текстовый контент, изображение с текстом alt или метку ARIA';
   /*… уродливый стиль …*/
}

Примечание: Кнопки и ссылки, которые не содержат никакой информации о своём предназначении (в текстовой или графической форме) являются неправильными. Последние два селектора наверное самые сложные из всех, которые я когда-либо написал. В версии для ссылки селектор можно прочитать как-то так: «Если мы имеем дело с ссылкой, которая не имеет атрибут aria-label или aria-labelledby и содержит только изображение в качестве содержимого, но у этого изображения нет атрибута alt, нужно вывести уведомление об ошибке.» Также обратите внимание на использование селектора :empty. Можно утверждать что парные теги не должны оставаться пустыми.

Аплодисменты тому, кто первым используя revenge.css обнаружит где я нарушил своё же правило описанное в этой статье. Поверьте, ошибка имеет место без сомнений.

Заключение

Я использую селекторы и приёмы описанные выше не для того чтобы попробовать что-то новое или получить новый материал для статьи. Селекторы атрибутов сами по себе ничем новым не являются. IE 6 — единственный браузер, который их не поддерживает. Я использую их потому что у меня нет времени и ментальных ресурсов на раздельное «сочинение» HTML и CSS. Мой мозг для этого недостаточно хорош. Я стилизирую заголовки страницы с помощью [role="banner"], а не .page-header потому что только так я буду знать, видя желаемый визуальный эффект, что я правильно использовал ориентир для навигации. Как еще за этим можно уследить? Нельзя полагаться на тестирование, потому что на этом этапе уже, как правило, поздно.

Нет такого понятия как семантичный CSS. Есть семантичный HTML и его представление. В этой статье я постарался это продемонстрировать, соединяя напрямую функцию и форму веб-страниц, вы получаете множество возможностей. С одной стороны, вы можете использовать селекторы, которые задействуют визуальные мотивы только в определённой разметке. С другой стороны, вы получаете возможность проверить разметку на плохой код и разрушить визуальный дизайн из-за уродливой реальности, на которой он основан.

Селекторы в вашем арсенале не всегда будут одинаково наделены семантикой и умны. Классы часто необходимы для полифилов очень нужных элементов или атрибутов, которые еще не внесены в стандарты. Именно так .footer стал <footer> и type="text" (с кучей JavaScript) стал type="url". В других случаях они помогают поддерживать несемантическую разметку с сеточными фреймворками и им подобными.

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

Жизнь слишком коротка для этого.

Если вы заметили ошибку, вы всегда можете отредактировать статью, создать issue или просто написать об этом Антону Немцеву в skype ravencry.

Heydon Pickering
Автор:
Heydon Pickering
Сaйт:
http://www.heydonworks.com/
GitHub:
Heydon
Twitter:
@heydonworks
Наталья Фадеева
Переводчик:
Наталья Фадеева
вКонтакте:
natatik_l
Twitter:
@very_busy_girl
GitHub:
NatalieF

Комментарии (11 комментариев, если быть точным)

Автар пользователя
mistakster

Читал недавно эту статью в оригинале и меня не покидала мысль о том, что автор живёт в каком-то своём мире с радугами и единорогами. Делать разметку, учитывая все аспекты её применения сразу, — это очень круто, долго и дорого. Сейчас объясню почему я так считаю. 1. Все блоки оказываются в жёстком контексте. Нужно его куда-то передвинуть и привет — пошли в CSS менять правила. 2. Элементы блока выглядят одинаково, но размечены по разному. Например, заголовок статьи на странице размечен <h1>, в группе статей —<h2> из-за того, что <h1> размечен заголовок этой группы, и на какой-то другой странице он вообще размечен как <p>, «потому что SEO». В итоге, вместо одного класса нужно, по мнению автора, перечислять кучу селекторов с каскадами. Так получается? Ведь теги умные, а классы не семантичные. 3. А давайте верстальщик сразу в прототипе будет делать микроразметку и к ней привязывать оформление! [itemprop=name] { font-size: 30px; font-weight: bold; } ПРОФИТ!

Список можно ещё продолжать и продолжать.

Зря всё-таки он бочку покатил на классы. Когда семантика (я имею в виду теги, микроразметку, атрибуты role и aria-*) отделена от оформления — это очень хорошо. Разработчики легко могут сделать прототип, потом «натянуть» вёрстку на вьюхи и только в самом конце расставить все эти важные атрибуты, обеспечив доступность контента, и ничего не сломать в визуальной части.

Сейчас мы можем писать гораздо меньше классов, чтобы правильно стилизовать контент. Селекторы по атрибутам, псевдо-классам позволяют эффективнее решать поставленные задачи. Совсем недавно я ставил эксперимент по работе с кастомными тегами, используя их в качестве модификаторов. А кастомные теги нужны были для того, чтобы можно было применять селектор :last-of-type.

А вот за revenge.css большое ему спасибо. Очень не хочется иногда тратить время на QA методом «Find, Find next» в редакторе. А букмарклетом проверить ошибки милое дело.

Автар пользователя
SilentImp

Я думаю, что ты не совсем прав. У тебя в прототипе есть кнопка. Она перестает быть кнопкой от того, что она в прототипе или перестанет быть ей в релизе? Маловероятно.

Автор безусловно перегибает палку … но и здравое зерно в этой статье есть. У нас есть теги, которые несут определенный семантический вес. Имеет смысл их использовать. У нас есть атрибуты, типа role, src или href … и мы определенно можем использовать их для формирования представления документа.

Автар пользователя
SilentImp

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

Автар пользователя
mistakster

У нас есть теги, которые несут определенный семантический вес.

Отлично. Но это не повод однозначно завязываться на них для оформления. Смотри мой пример с заголовками.

У нас есть атрибуты, типа role, src или href … и мы определенно можем использовать их для формирования представления документа.

В них кроется кое-какая проблема. src и href могут ссылаться куда угодно. Адреса ресурсов могут меняться со временем. Мне кажется, что кроме как для весёленьких пиктограмм рядом со ссылкой, и не придумать применение. А?

С role всё ещё сложнее. Нужно уметь применять этот атрибут. То есть новички сразу идут лесом. Хвала богам, вставить role="banner" или role="main" не составит особого труда. А что дальше? Каскады? А как различать блоки с role="navigation", которых может быть десяток на странице?

Всё-таки, доступность для вспомогательных технологий — это не приоритетная задача. Она требует дополнительных расходов при создании проекта. А заказчику нужно быстрее запустить проект, минимизировав расходы. Не трудно догадаться, что эта фича останется за бортом или, в лучшем случае, разработчик добавит её факультативно.

Ты стабильно знаешь, что вот этот блок будет новостной лентой, это телом статьи а тут будет хидер и футер.

Всё верно. Я даже могу представить себе как вместо (или вместе с) class можно использовать role. Благо в документе Accessible Rich Internet Applications можно найти, скорее всего, подходящее значение. И я даже могу представить себе, что мы убьём двух зайцев одним выстрелом — сделаем оформление и улучшим семантику в документе. Но выглядит это шагом назад. Не успели порадоваться легкости управления проектом, свёрстанным на простых селекторах, как автор предлагает смешать нам данные с их представлением и вернуться к медлительным каскадам. Not OK.

Автар пользователя
SilentImp

Но это не повод однозначно завязываться на них для оформления. Смотри мой пример с заголовками.

Полностью согласен с тобой. Тут уместно «и», а не «вместо».

Мне кажется, что кроме как для весёленьких пиктограмм рядом со ссылкой, и не придумать применение. А?

Отделение внешних/внутренних ссылок, безопасного соединения, маркировка ссылок на популярные ресурсы, к примеру соц. сети. Да, это веселенькие пиктограмы или еще какая то маркировка. Но разве она не приносит пользы?

С role всё ещё сложнее. Нужно уметь применять этот атрибут

Угу. Надо. То же самое касается HTML, CSS, туалетной бумаги… Правильное и продуманное использование role может сделать веб лучше. И для роботов и для людей с ограниченными возможностями.

Всё-таки, доступность для вспомогательных технологий — это не приоритетная задача. Она требует дополнительных расходов при создании проекта.

Я думаю это очень сильно зависит от процесса разработки. Так же, как, например, адаптивный дизайн. В зависимости от того, как построен процесс он может существенно удорожить процесс разработки и с точки зрения денег, и с точки зрения времени … а может сделать его даже более быстрым. Какая разница привяжем мы стиль к role или class? Но да, классами умеют пользоваться намного больше людей чем атрибутами и, как факт, 95% разработчиков, что бы процесс не удорожался, придется его существенно изменить. Но это возможно и не более сложно чем для адаптивного дизайна. Мне кажется что это стоит записать себе в todo и хорошенько обдумать/поэксперементировать с этим.

автор предлагает смешать нам данные с их представлением

Поясни пожалуйста. Я не вижу где именно мы тут смешиваем данные и представление. Представление в CSS. Данные в HTML. И я не понимаю, каким образом привязка по атрибуту может их смешать. Чем она отличается от привязки по классу? Разве что скоростью … но о каких порядках мы тут говорим? Тот же Гарри, на которого ты ссылаешься, признает, что на сегодняшний день производительность селекторов это даже не второстепенный вопрос при разработке сайтов и она играет роль только на ооооочень высоконагруженных проектах. Я думаю если мы используем тот же sass/less при разработке это сказывается в 100 крат значительнее за счет неоптимальных здоровенных каскадов, чем если мы используем селектор по атрибутам. А есть еще масса псевдо-селекторов, которые в сравнении с классом ни разу не быстрые. Предлагаешь отказаться?

Автар пользователя
mistakster

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

Людей, разбирающихся в тонкостях спецификации ARIA, не так-то и просто найти. Более того, процесс добавления атрибутов очень муторный и требует постоянной валидации с помощью вспомогательных технологий. Получается, что разработчик должен ещё уметь эффективно пользоваться скринридерами, помнить их шорткаты, понимать как производится навигация по странице. Не слишком-ли сложно для того, чтобы стилизовать элементы на странице?

Обеспечить доступность контента — это всё-таки отдельная задача, не связанная с оформлением. Там и без того много всяких подводных камней.

Расскажу короткую историю про мой блог. На заголовках статей есть градиенты (постоянные или по наведению указателя мышки). Так как CSS маски ещё не достаточно распространены, то сделать такие градиенты для всех браузеров можно только через раскраску каждой буквы. Нагенерить спанов не проблема. Проблемы начинаются, когда скринридер попытается зачитать такой раскрашенный заголовок. Для Voice Over каждая буква представляется отделённой друг от друга. К счастью, мне удалось решить эту проблему, явно указав текст заголовка в aria-label, а все цветные спаны скрыв через aria-hidden. И пришёл я к этому решению далеко не сразу, а методом проб и ошибок, потратив кучу времени.

После этого, я очень осторожно отношусь к фразам «Атрибуты role — это очень просто». Это совсем не просто.

Я не вижу где именно мы тут смешиваем данные и представление.

HTML теги и их контент — это данные, ARIA атрибуты и их контент — это данные, атрибуты микроразметок — это тоже данные. Это всё имеет значение, т.е. обладает семантикой.

Значения классов (за исключением, пожалуй, микроформатов) — это не данные. Туда мы пишем всё, что может обеспечить декорирование конкретного элемента.

Разделяя эти слои мы максимально упрощаем жизнь, процесс разработки, поддержку кода и т.п.

если мы используем тот же sass/less при разработке, это сказывается в 100 крат значительнее за счет неоптимальных здоровенных каскадов

В LESS можно писать стили и без каскадов:

.block { background: white; &__element { font-size: 15px; &_state_active { color: red; } } }

После компиляции получим:

.block { background: white; } .block__element { font-size: 15px; } .block__element_state_active { color: red; }

В SASS тоже что-то подобное есть.

Никаких длинных названий и копипасты, всё структурировано и на выходе только простые селекторы (максимум каскад от модификатора блока).

Автар пользователя
voischev

Здравое зерно тут только в том что нужно использовать теги для их назначения. Видимо автору набило оскомину использование разработчиками <a> где можно и нельзя. Я его поддерживаю в том что нужно придерживаться некой логике. Думать нужно, но он перегибает палку.

Вообще в статье больше рассуждений чем практики.

Автар пользователя
hVostt

Статья заставляет задуматься. Это очень хорошо. И не так важно, специально автор перегибал палку, или нет. Действительно, существует множество семантики, которую можно использовать для стилизации, от этого станет только лучше. Автор пытается донести, что классы используются часто там, где они не нужны. А что до вредного SEO, и прочих случаев, входящих в противоречие с семантикой (а не гнать ли их в шею, а?), то автор и не предлагает отказаться от классов. Для той же сетки, вообще ничего нет семантичного. Да и не нужно, честно говоря.

Автар пользователя
LakeVostok

mistakster, не знаю, увидите ли моё сообщение, но у вас в блоге с aria явный перебор. К примеру к тегу article добавлять role="article" бессмысленно, поскольку он уже это и подразумевает, это из спецификации, конкретную строку лень искать

Автар пользователя
iamstarkov

@LakeVostok /cc @mistakster

Автар пользователя
mistakster

@LakeVostok, спасибо за ваше замечание. Вы в чём-то правы, но не на 100%.

https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_article_role

The ARIA article role is similar to the HTML5 article element; however the article element should still be given the ARIA role of article, since not all assistive technologies support HTML5 yet.

Дублирование ничего плохого не несёт в себе в этом случае.