Заметки с тегом «a11y»

Доступность SVG-пиктограмм

Когда вы используете inline-SVG без текстового пояснения (например, как пиктограмму на кнопке), то неплохо было бы снабдить это изображение заголовком. В SVG-документе есть описательные теги: <title>, <desc>, <metadata>. Для наших целей хорошо подходит тег <title>.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Document title</title>
</head>
<body>
  <p>This is a test page</p>

  <button>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" width="32" height="32">
      <title>Next page</title>
      <polygon points="15,7 11,11 20,20 11,29 15,33 28,20"/>
    </svg>
  </button>

  <p>Actually, there is no next page here.</p>

</body>
</html>

Содержимое <title> не отображается, но может использоваться вспомогательными технологиями.

Содержимое <title> не отображается, но может использоваться вспомогательными технологиями

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

Таким образом, нарушая все запреты о наличии только одного тега <title> на странице, мы получили абсолютно валидный документ с несколькими тегами.

На странице с валидной разметкой могут присутстовать несколько тегов <title>

Комментарии к заметке: 4

Accessibility. Статьи, презентации и инструменты

Недавно Варя Степанова спросила у меня:

Всем могу посоветовать добавить в закладки большой сборник ссылок, который подготовил Владимир Старков. В феврале 2013 года он выступал на UWDC и FrontTalks с докладами о доступности веб-сайтов.

От себя добавлю, что когда я оптимизировал свой блог и проходил краткий курс по «Accessibility», то очень помогли следующие материалы:

Для Хрома есть специальное расширение — Accessibility Developer Tools. С помощью него можно в инспекторе узнать как будет зачитано содержимое того или иного блока без использования скринридера. Так же это расширение делает аудит открытой страницы, указывает потенциальные проблемы и пути их решения.

Оставте свой комментарий

Данные, которые скрыты от пользователя

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

<title> и <meta name="description">

Эти мета-данные были одними из самых первых, которые помогали роботам понять суть страницы.

В теге <title> размещают заголовок страницы. Для каждой страницы должен быть указан заголовок и лучше всего, если он будет уникальным для всего сайта. На размер строки заголовка нет явных ограничений, но из практических соображений не стоит делать его длиннее 70-80 символов.

В атрибуте content тега <meta name="description"> размещают кратное описание страницы. Длину стоит ограничить 150-160 символами потому, что оно активно используется для построения сниппета в поисковой выдаче и более длинное описание будет автоматически укорочено. Явного ограничения в спецификациях нет. Содержимое, как и в случае с заголовком страницы, должно быть уникальным для всего сайта.

Ещё одним классическим тегом считается <link rel="canonical">. Он нужен для того, чтобы сообщить роботам какая из страниц с одинаковым содержимым, если таковые имеются, считается основной. Адрес задаётся в атрибуте href.

Протокол Open Graph

С появлением социальных сетей и кнопок «Like» простого заголовка и описания стало недостаточно. Социальные сети хотели отображать у себя на страницах картинку, видео или аудио материалы, которые отметили пользователи.

Facebook разработал на основе RDFa упрощенный протокол Open Graph, который работает в одном пространстве имён og и имеет ограниченный набор типов данных (музыка, видео, статья, книга, персона и сайт).

<html prefix="og: http://ogp.me/ns# article: http://ogp.me/ns/article#">
<head>
  <title>Больше семантики для логотипа :: Хранитель заметок</title>
  <meta property="og:title" content="Больше семантики для логотипа">
  <meta property="og:type" content="article">
  <meta property="og:url" content="http://noteskeeper.ru/945/">
  <meta property="og:image" content="http://noteskeeper.ru/wp-content/themes/nk/i/nk-logo-200.png">
  <meta property="og:description" content="Разметке логотипа можно добавить семантики с помощью microdata. Тип Brand позволяет указать логотип для продуктов и людей">
  <meta property="og:site_name" content="Хранитель заметок">
  <meta property="article:published_time" content="2013-08-26T03:31:18+00:00">
  <meta property="article:section" content="Вёрстка">
  <meta property="article:tag" content="microdata">
  ...

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

Microdata и RDFa

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

Оба формата используют дополнительные атрибуты HTML-элементов, чтобы указать тип объекта и его свойства. Значения свойств за некоторым исключением берётся из текстового содержимого элемента.

RDFa позволяет указывать значение свойства с помощью атрибута content у любого элемента:

<span property="dc:modified"
  content="2013-09-16T15:00:00+00:00"
  datatype="xsd:dateTime">16 сентября 2013 года</span>

В то время как парсерам Microdata требуются дополнительные элементы <meta> или <link>:

<span>16 сентября 2013 года</span>
<meta itemprop="dateModified" content="2013-09-16T15:00:00+00:00">

Разметка с помощью Microdata или RDFa позволяют сообщить роботам о семантике контента независимо от того какими тегами он размечен в HTML. Более того, форматы позволяют обозначить данные, невидимые пользователю в силу внешнего дизайна страницы.

“Shaken, not stirred”

При всём разнообразии мета-данных, которые можно впихнуть на страницу, складывается ситуация, когда одни и те же данные будут повторяться в HTML-коде несколько раз.

Например, описание страниц, которое фигурирует во всех форматах, можно передать в одном теге:

<meta content="Разметке логотипа можно добавить семантики с помощью microdata. Тип Brand позволяет указать логотип для продуктов и людей"
  name="description"
  property="og:description"
  itemprop="description"
  id="_articleDescription">

В этом примере свойство description типа http://schema.org/Article подключается в объект с помощью ссылки по id в атрибуте itemref. Если это позволяет разметка, то тип самого важного объекта на странице можно объявить у корневого элемента документа. Тогда все теги meta естественным образом окажутся в области видимости этого объекта.

<html prefix="og: http://ogp.me/ns# article: http://ogp.me/ns/article#" itemscope itemtype="http://schema.org/Article">
<head>
  <title>Больше семантики для логотипа :: Хранитель заметок</title>
  <meta property="og:title" content="Больше семантики для логотипа" itemprop="name">
  <meta property="og:type" content="article">
  <meta property="og:url" content="http://noteskeeper.ru/945/" itemprop="url">
  <meta property="og:image" content="http://noteskeeper.ru/wp-content/themes/nk/i/nk-logo-200.png" itemprop="image">
  <meta property="og:description" content="Разметке логотипа можно добавить семантики с помощью microdata. Тип Brand позволяет указать логотип для продуктов и людей" itemprop="description">
  <meta property="og:site_name" content="Хранитель заметок">
  <meta property="article:published_time" content="2013-08-26T03:31:18+00:00" itemprop="dateCreated">
  <meta property="article:section" content="Вёрстка" itemprop="articleSection">
  <meta property="article:tag" content="microdata" itemprop="keywords">
  ...

Так как привязка нового объекта к «родителю» осуществляется только наличием у него атрибута itemprop, то при его отсутствии новый объект помещается на самый верхний уровень иерархии. Получается, что на странице можно безопасно размещать несколько независимых объектов.

Атрибуты role и aria-*

Особняком стоят атрибуты Web Accessibility Initiative (WAI). Они используются вспомогательными технологиями для обеспечения доступности контента для пользователей с ограниченными возможностями.

Скринридерам часто приходится специально сообщать о назначении элемента (атрибут role), его названии (атрибуты aria-label, aria-labelledby, aria-describedby) или состоянии, так как у него нет другого способа передать эту информацию пользователю.

Заключение

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

Заголовок для поля ввода и его доступность

Подпись или заголовок для поля ввода размечается тегом <label>. Наличие связанного с ним элемента <input>, <textarea> или <select> не является обязательным условиям. Хотя, при отсутствии такого элемента, сам заголовок приобретает другой смысл и может быть размечен другим тегом.

<label> может быть как обёрткой для поля ввода, так и быть связанным с ним с помощью атрибутов. Вот несколько типовых вариантов разметки:

Вариант №1:

<label>
  Фамилия <input type="text" name="family-name">
</label>

Вариант №2:

<label for="family-name-field">Фамилия</label>
<input id="family-name-field" type="text" name="family-name">

Клик по надписи в обоих случаях приведёт к том, что связанное с этой надписью поле ввода получит фокус. В первом случае связь инпута и лейбла определяется явно (фокус получает первое дочернее поле ввода). Во втором случае связь осуществляется с помощью атрибута id у поля ввода и аналогично значения у атрибута for лейбла.

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

Подсказка в поле ввода

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

<input type="text" placeholder="Фамилия" name="family-name">

Но не стоит путать подсказку и подпись.

Доступность

Но совсем отказываться от <label> нельзя из-за того, что вспомогательные технологи не зачитывают подсказки. Тег нужно оставить в разметке, но скрыть его от пользователя.

<label for="family-name-field" class="hidden">Фамилия</label>
<input id="family-name-field" type="text"
    placeholder="Фамилия" name="family-name">

Скрыв подпись к полю ввода с помощью стиля display: none, мы не решили проблему с доступностью текста подписи. Вспомогательные технологии игнорируют элементы, к которым применён такой стиль.

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

Способ №1

<label>
  <span class="hidden">Фамилия</span>
  <input type="text" placeholder="Фамилия" name="family-name">
</label>

Текст подсказки оборачивается в дополнительный элемент, который уже скрывается стилем display: none.

Скринридеры понимает, что <label> видим для пользователя и зачитывает его содержимое, не обращая внимания на то, что оно оказывается скрытым.

Способ №2

<label id="family-name-label"
    for="family-name-field" class="hidden">Фамилия</label>
<input aria-labelledby="family-name-label"
    id="family-name-field" type="text"
    placeholder="Фамилия" name="family-name">

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

Как вариант, явно указать подпись можно с помощью атрибута aria-label.

Способ №3

<label for="family-name-field" class="visuallyhidden">Фамилия</label>
<input id="family-name-field" type="text"
    placeholder="Фамилия" name="family-name">

Класс visuallyhidden выключает элемент из потока и делает его размеры 1×1 пиксель. Формально подсказка остаётся видимой и зачитывается ридерами, но фактически её не видно на экране.

У всех способов есть свои плюсы и минусы:

  • первый вариант оказался в итоге самым компактным и универсальным, но появился дополнительный элемент в разметке;
  • второй вариант самый громоздкий (требуется слишком много атрибутов и два уникальных идентификатора), но в то же время и самый надёжный;
  • третий вариант выглядит хорошим компромиссом, если не позаботиться о том, чтобы id инпута не повторялся на странице.
Комментарии к заметке: 2

Эксперимент: Доступность vs. Поисковая оптимизация

Дано: Макет облака тегов

Макет облака тегов

Задача: Сверстать его. Помимо визуального отображения обеспечить доступность виджета для скринридеров. Скрыть от поисковых роботов.

Очевидное решение

Изначально виджет был свёрстан примерно так:

<section class="widget tags-cloud">
  <h4 class="widget__title">Облако тегов</h4>
  <ul class="tags-cloud__list">
    <li class="tags-cloud__item tag-item tag-item_rank_9">
      <a class="tag-item__link" href="http://noteskeeper.ru/tag/jquery/">
        jquery
      </a>
    </li>
    <li class="tags-cloud__item tag-item tag-item_rank_8">
      <a class="tag-item__link" href="http://noteskeeper.ru/tag/css/">
        css
      </a>
    </li>
    <li class="tags-cloud__item tag-item tag-item_rank_2 tag-item_position_last">
      <a class="tag-item__link" href="http://noteskeeper.ru/tag/html5/">
        html5
      </a>
    </li>
  </ul>
</section>

Облако тегов — это, очевидно, список. Пункты списка выводятся как строчные элементы, а разделители (запятые) после каждого тега расставляются через CSS-свойство content.

В таком виде виджет просуществовал достаточное количество времени. Сайт индексировался поисковыми роботами. И я заметил, что поисковики активнее выдают страницы-концентраторы статей (архивы), на которые ведут ссылки из облака тегов, чем сами страницы со статьями. По этому таки страницы я закрыл от индексации <meta name="robots" content="noindex, follow">, а самим ссылкам добавил атрибут rel="nofollow".

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

Сниппет, никак не относящийся к содержанию статьи

Поэтому я решил каким-либо образом скрыть содержимое этого блока от поисковых роботов.

Улучшения

Итак, для начала назначим контейнеру виджета роль (landmark), чтобы сообщить вспомогательным технологиям назначение этого блока.

<section class="widget tags-cloud" role="navigation">

Ранее заголовок блока был скрыт с помощью display: none. Но это так же делало его невидимым и для вспомогательных технологий. Чтобы его скрыть только при отображении на экране применим класс visuallyhidden.

<h4 class="widget__title visuallyhidden">Облако тегов</h4>

Для скринридеров ссылки нужно снабдить атрибутом title, но его содержимое не должно повторять содержимое тега, а дополнять его. Это связано с тем, что они зачитывают и то, и другое при навигации по элементам.

Тут возникла идея вообще избавиться от текста внутри ссылки. По сути, нам это очень даже играет на руку. Ведь мы хотим скрыть его от поисковиков. В итоге получилась такая разметка:

<section class="widget tags-cloud" role="navigation">
  <h4 class="widget__title visuallyhidden">Облако тегов</h4>

  <span class="tags-cloud__item tag-item tag-item_rank_9"><a
    class="tag-item__link" rel="nofollow"
    href="http://noteskeeper.ru/tag/jquery/"
    title="Заметки с тегом &laquo;jquery&raquo;"><span
    data-name="jquery" class="tag-item__title"></span></a></span>

  <span class="tags-cloud__item tag-item tag-item_rank_8"><a
    class="tag-item__link" rel="nofollow"
    href="http://noteskeeper.ru/tag/css/"
    title="Заметки с тегом &laquo;css&raquo;"><span
    data-name="css" class="tag-item__title"></span></a></span>

  <span class="tags-cloud__item tag-item tag-item_rank_2 tag-item_position_last"><a
    class="tag-item__link" rel="nofollow"
    href="http://noteskeeper.ru/tag/html5/"
    title="Заметки с тегом &laquo;html5&raquo;"><span
    data-name="html5" class="tag-item__title"></span></a></span>

</section>

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

Ключевые стили виджета:

.tag-item {
  /* чтобы запятая не отделялась от названия тега */
  white-space: nowrap;
}
.tag-item:after {
  /* запятая после каждого названия */
  content: "\2c";
}
.tag-item_position_last:after {
  /* у последнего тега нет запятой */
  content: "";
}
.tag-item__link {
  /* без этого ссылка не кликабельна */
  display: inline-block;
}
.tag-item__title:after {
  /* выводит содержимое атрибута на экран */
  content: attr(data-name);
}

Так как у элементов списка нет текста, то и от самого списка пришлось избавиться.

Скринридер VoiceOver при переходе от тега к тегу зачитывает то, что выводится из атрибута и с некоторой паузой произносит содержимое атрибут title. Это звучит вполне естественно и не кажется повторением одного и того же. Проверить в других скринридерах пока не удалось. Если они окажутся не такими умными, то будут зачитывать только атрибут title.

Так же не понятно пока как к этим изменениям отнесутся поисковики. Буду ждать обновление индекса.

Комментарии к заметке: 9