[Разработка веб-сайтов, CSS, HTML, Usability, Accessibility] HTMHell — адовая разметка (перевод)

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
30-Апр-2021 01:30

Приветствую. Представляю вашему вниманию перевод заметок с сайта HTMHell - коллекции плохих примеров HTML-кода, взятых из реальных проектов.Каждая заметка включает сам фрагмент плохого кода, который дополняется объяснениями, в чём именно ошибки и почему так лучше не делать. А в заключение предлагается вариант, который считается более корректным.Так как я объединил в единую статью заметки, которые изначально являются независимыми и самодостаточными, во время перевода позволил себе иногда немного отходить от стилистики изначальных формулировок, пытаясь сделать текст менее "сухим" и более последовательным.1. Кнопка, замаскированная под ссылкуПлохой код
<button role="link" title="Name of website" tabindex="0">
  <img alt="Name of website" src="logo.jpg" title="Name of website">
</button>
Ошибки и что следует исправить
  • Пример неправильного использования элемента <button>. Для ссылок на другую страницу или сайт следует использовать элемент <a> . Не пренебрегайте семантикой HTML-тегов, если только в этом нет явной потребности
  • Благодаря элементам <a>, на страницы можно ссылаться и без использования JavaScript
  • Атрибут title описывает содержимое элемента в виде всплывающей подсказки и для элементов <button> указывать его излишне
  • В атрибуте tabindex также нет необходимости, ведь при переключении между элементами с помощью клавиатуры кнопки получают фокус по умолчанию
Хороший код
<a href="https://">
  <img alt="Name of website" src="logo.jpg">
</a>
2. Элемент с атрибутом role="button"Плохой код
<div tabindex="-1">
  <div role="button">
    <svg width="28" height="24"> … </svg>
  </div>
</div>
Ошибки и что следует исправить
  • Нет необходимости пытаться задать семантику <div>-элемента с помощью атрибута role, ведь вместо этого достаточно просто использовать элемент <button>
  • При использовании <button> не понадобится и атрибут tabindex . HTML-кнопки по умолчанию могут получать фокус
  • На <div>-элементах событие клика вызывается только непосредственно кликом мыши. На элементах же <button> это происходит ещё и при нажатии на кнопки Enter или Space на клавиатуре
  • SVG-иконки, расположенной внутри нашей псевдокнопки не хватает текстовой альтернативы на случай, если SVG не отобразится
Хороший кодЧтобы предназначение кнопки было понятно не только визуально, но и оставалось доступным для пользователей скринридеров, внутрь добавляется описательный текст.
<button>
  <span class="sr-only">Send</span>
  <svg width="28" height="24" aria-hidden="true"> … </svg>
</button>
Тексту присваивается класс .sr-only с набором свойств, делающим его скрытым только визуально
.sr-only {
  position: absolute;
  white-space: nowrap;
  width: 1px;
  height: 1px;
  overflow: hidden;
  border: 0;
  padding: 0;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  margin: -1px;
}
3. Картинки - кнопкиПлохой код
<img src="/images/edit.gif" onclick="openEditDialog(123)">
<img src="/images/delete.gif" onclick="openDeleteDialog(123)">
Ошибки и что следует исправить
  • Элемент <img> предназначен отнюдь не для выполнения JavaScript, а для показа изображений
  • Как и на упомянутом ранее <div>, на элементе img> событие клика вызывается только непосредственно кликом мыши. Если бы вместо него использовался элемент <button> , это происходило бы ещё и при нажатии кнопок Enter или Space на клавиатуре
  • Для самого изображения не задана текстовая альтернатива (атрибут alt ). Из-за этого скринридеры могут озвучивать название самого файла изображения, что далеко не всегда информативно
Хороший кодРешение №1: Использовать кнопки, а к помещённым внутрь кнопок изображениям добавить атрибут alt
<button onclick="openEditDialog(123)">
  <img src="/images/edit.gif" alt="Edit product XY">
</button>
<button onclick="openDeleteDialog(123)">
  <img src="/images/delete.gif" alt="Delete product XY">
</button>
Решение №2: Использовать кнопки и вместо добавления атрибута alt к изображениям, добавить описание в текстовые элементы
<button onclick="openEditDialog(123)">
  <span class="sr-only">Edit product XY</span>
  <img src="/images/edit.gif" alt="">
</button>
<button onclick="openDeleteDialog(123)">
  <span class="sr-only">Delete product XY</span>
  <img src="/images/delete.gif" alt="">
</button>
Изображения с пустым атрибутом alt недоступны для пользователей скринридеров, что нам и нужно, ведь название кнопок мы добавили в виде отдельного текста, визуально скрытого с помощью набора свойств упомянутого выше класса .sr-only
.sr-only {
  position: absolute;
  white-space: nowrap;
  width: 1px;
  height: 1px;
  overflow: hidden;
  border: 0;
  padding: 0;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  margin: -1px;
}
4. Ссылка с кнопкой внутриПлохой код
<a href="https://example.com">
  <button>Example</button>
</a>
Ошибки и что следует исправить
  • Вкладывая кнопку внутрь ссылки, вы подаёте сразу два сигнала: это кнопка, но также это и ссылка
  • Если вы не уверены, когда нужно использовать элемент <a>, а когда <button>, рекомендую посмотреть видео "The Links vs. Buttons Showdown" от Marcy Sutton
Хороший код
.button {
  /* используйте CSS, чтобы задать ссылке вид кнопки */
}
<a href="https://example.com" class="button">Example</a>
5. Кнопкоподобная ссылкаПлохой кодКонтекст: это ссылка, стилизованная под кнопку. Ведёт она на форму, расположенную на этой же странице
<a href="#form" role="button" aria-haspopup="true">   Register   </a>
Ошибки и что следует исправить
  • Добавляя к ссылке role="button" , вы сообщаете, что это кнопка, хотя она ведёт себя как ссылка. Не меняйте семантику элементов, только если в этом нет серьезной необходимости
  • Атрибут aria-haspopup="true" призван сообщать вспомогательным устройствам, что данный элемент вызывает попап, но в нашем случае этого не происходит
  • Внутренний отступ padding следует добавлять к элементам через CSS, а не с помощью  
Хороший код
.button {
  /* с помощью CSS задайте ссылке вид кнопки  */
}
<a class="button" href="#form"> Register </a>
6. Ссылка с void-оператором в значении атрибута "href"Плохой код
<a href="javascript:void(1)" onClick='window.location="index.html"'>Link</a>
Ошибки и что следует исправить
  • Если JavaScript-код не загрузился или не может быть выполнен, использующая его ссылка просто перестанет работать
  • Да и для ссылки на другую страницу нет необходимости использовать JavaScript. Адрес можно указать в атрибуте href, который 100% поддерживается всеми браузерами и будет корректно работать
  • Такая ссылка будет работать только при клике левой кнопкой мыши. Открыть её в новой вкладке/окне щелчком скролла или через конктекстное меню не удастся
Хороший код
<a href="index.html">Link</a>
7. Дубликаты "id" и табличная раскладкаПлохой код
<table>
   <tr id="body">
     <td id="body">
       <table id="body">
         <tr id="body_row">
           <td id="body_left">…</td>
           <td id="body_middle">…</td>
           <td id="body_right">…</td>
         </tr>
       </table>
     </td>
   </tr>
</table>
Ошибки и что следует исправить
  • Значениея атрибутов id должны быть уникальными, независимо от того, к какому тегу они добавляются
  • Данный код использует раскладку, основанную на таблицах (это при том, что дизайн сайта обновлялся в 2016 году). Избегайте разметки страниц с помощью таблиц, потому что эти элементы имеют вполне конкретное семантическое значение и не предназначены для этих целей
  • Текущую разметку следует заменить на семантические HTML5-теги. Это существенно сократит количество тегов и сделает код более понятным
  • При стилизации следует использовать новые технологии Flexbox и CSS Grid, но никак не элементы таблиц
  • Для значений атрибута IDдолжны быть использованы более семантические термины
Хороший код
<main id="body">
   <aside id="secondary_content"> </aside>
   <article id="primary_content"> </article>
   <aside id="tertiary_content"> </aside>
</main>
8. Якорная ссылка в роли кнопкиПлохой код
<a href="#" onclick="modal.open()">Login</a>
Ошибки и что следует исправить
  • Элементы <a> следует использовать для ссылки на другие ресурсы: такие как страница или PDF-документ
  • В нашем же случае задача элемента – вызвать JavaScript-действие на текущей странице. Для таких целей лучше подходит элемент <button> с атрибутом type="button" , потому что не имеет поведения по умолчанию и изначально предназначен для вызова действий в ответ на нажатие пользователем
  • Ещё одним серьёзным недочётом данного примера является то, что браузеры и устройства, которые не поддерживают JavaScript, не смогут получить доступ к содержимому модального окна
Хороший кодРешение №1: Использовать элемент <button>
<button type="button" onclick="modal.open()">Login</button>
Поскольку целью данного элемента является не навигация, а вызов действия на текущей странице, элемент <button> является семантически более подходящим.Решение №2: Ссылка на отдельную страницу
<a href="/login" onclick="modal.open()">Login</a>
Ещё одно решение - использовать элемент <a> , у которого в атрибуте hrefуказать путь на отдельную полноценную страницу, где пользователь сможет проделать те же действия, что и в модальном окне. А переход по указанному пути, который является поведением ссылки по умолчанию, заблокировать через JavaScriptТаким образом, получаем некий фолбэк для браузеров и устройств, которые не поддерживают JavaScript или если он по какой-то причине не сработалЭто пример прогрессивного улучшения при разработке9. Запрос согласия на хранение CookieПлохой код
<body>
  <header>…</header>
  <main>…</main>
  <footer>…</footer>
  <div class="cookie_consent modal">
    <p>We use cookies…</p>
    <div class="cookie_consent__close">
      <i class="fa fa-times"></i>
    </div>
    <div class="cookie_consent__ok">OK</div>
  </div>
</body>
Ошибки и что следует исправить
  • Окно с запросом разрешения хранить данные Cookie появляется сразу при входе на страницу. Конкретно для этого типа модального окна следует делать исключение и помещать в самом начале кода, чтобы сразу же после загрузки страницы оно получало фокус и было доступно для взаимодействия. Но в нашем примере оно располагается наоборот в конце
  • Кнопки данного окна, реализованные с помощью <div> элементов , а значит не получат фокус при переключении между элементами с помощью клавиатуры
  • Содержимое внутри <div>-кнопок семантически является просто текстом. Всё это не позволит вспомогательным технологиям вроде экранных читалок понять, что определённые элементы на самом деле являются кнопками
  • Как уже было указано ранее, в дополнение ко всему, на элементах <div> событие клика вызывается только непосредственно кликом мыши. Если бы всесто них использовались элементы <button>, это происходило ещё и при нажатии на кнопки Enter или Space на клавиатуре
  • Для иконки нет текстовой альтернативы, а значит её назначение можно определить только визуально
  • Font Awesome советует скрывать иконки от скринридеров, добавляя элементам <i> атрибут aria-hidden="true"
  • Font Awesome добавляет Unicode-содержимое через псевдоэлемент ::before. Некоторые вспомогательные технологии могут озвучивать его. Но в данном примере иконка будет названа "разы" (times), поскольку fa-times - это не "крестик", а "знак умножения". Обратите внимание: Talkback и VoiceOver в данном случае не озвучат вообще ничего
  • В завершение можно также добавить, что крайне полезным было бы иметь возможность закрыть модальное окно нажатием Escape
Хороший код
<body>
  <div class="cookie_consent modal">
    <h2 class="sr-only">Cookie notice</h2>
    <p>We use cookies…</p>
    <button class="cookie_consent__ok">OK</button>
    <button class="cookie_consent__close">
      <span class="sr-only">Close notification</span>
      <i class="fa fa-times" aria-hidden="true"></i>
    </button>
  </div>
  <header>…</header>
  <main>…</main>
  <footer>…</footer>
</body>
Для описательного текста кнопок мы снова используем набор свойств, называемых классом .sr-only , который скрывает его визуально, но оставляет доступным для скринридеров
.sr-only {
  position: absolute;
  white-space: nowrap;
  width: 1px;
  height: 1px;
  overflow: hidden;
  border: 0;
  padding: 0;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  margin: -1px;
}
Дополнительные материалы 10. Элемент как замена для Плохой код
<section id="page-top">
  <section data-section-id="page-top" style="display: none;"></section>
</section>
<main>
  <section id="main-content">
    <header id="main-header">
      <h1>...</h1>
      <section class="container-fluid">
        <section class="row">
          <article class="content col-sm-12">
            <section class="content-inner">
              <div class="content__body">
                <article class="slider">
                  <section class="slide"> … </section>
                </article>
              </div>
            </section>
          </article>
        </section>
      </section>
    </header>
  </section>
</main>
Ошибки и что следует исправить
  • Элементы секционного содержимого (<article>, <aside>, <nav>, <section>) – это разделы, которые потенциально можно как-то озаглавить
  • Секционные элементы вкладывать друг в друга можно, только это имеет смысл в ситуациях, когда содержимое внутренных элементов связано с содержимым родителя
  • В данном конкретном случае секционные элементы используются не для того, чтобы разбить содержимое на разделы, а лишь с целью стилизации. Значит большинство из них должны быть элементами <div>, не передающими семантическое значение
  • Когда пользователь переходит к элементу <section>, скринридеры могут озвучивать роль выбранного элемента – region. Устройства также могут предоставлять возможность быстрой навигации по этим разделам. Использование большого количества элементов <section> (в том числе, вложенных) может сделать интерфейс излишне сложным для пользователей экранных читалок
  • Важный момент, который следует понимать – элементы <section> не являются заменой <div>
  • Ещё одна ошибка в данном примере - неправильное использование элемента <header>. Обычно он содержит вводное содержимое для ближайшего родительского элемента <main> или другого секционного элемента. Если <header> не является вложенным, относится ко всей странице
  • Компонент карусели (слайдера) следует озаглавливать и связывать заголовок с главным элементом с помощью атрибута aria-labelledby чтобы позволить пользователям скринридеров легко его найти
Хороший код
<div id="page-top">
  <div data-section-id="page-top" style="display: none;"></div>
</div>
<main>
  <section id="main-content">
    <header id="main-header">
      <h1>...</h1>
    </header>
    <div class="container-fluid">
      <div class="row">
        <div class="content col-sm-12">
          <div class="content-inner">
            <section aria-labelledby="sliderheading" class="content__body">
              <h2 id="sliderheading" hidden>New Products</h2>
              <ul class="slider">
                <li class="slide"> … </li>
              </ul>
            </section>
          </div>
        </div>
      </div>
    </div>
  </section>
</main>
Дополнительные материалы 11. Триграмма небаПлохой код
<span class="nav-toggle"> ☰ Menu </span>
Ошибки и что следует исправить
  • Проблема данной реализации в том, что текст внутри тегов скринридеры могут озвучить как "триграмма неба меню". Причина кроется в символе "☰", который таким образом называется в unicode
  • Задача иконок - декоративное оформление, поэтому они должны быть скрыты от скринридеров. Рассмотрите добавление декоративных изображений с использованием CSS-свойства background
  • Так же, как на упомянутых ранее <div>, и <img>, на элементе <span> событие клика вызывается только непосредственно кликом мыши. Если вместо него использовать <button> , клик можно будет вызвать ещё и при нажатии на кнопок Enter и Space на клавиатуре.
  • Элемента <span> касаются и проблемы, связанные с невозможностью получения фокуса при переключении между элементами с помощью клавиатуры. А в нашем случае это особенно важно, ведь главная навигация открывается и скрывается именно с помощью данного элемента
  • Для улучшения доступности при открытии навигации к элементу следует добавлять атрибут aria-expanded для обозначения текущего состояния панели. Значением true – если панель открыта, false – если закрыта
Хороший код
<button class="nav-toggle" aria-expanded="false">
  <span aria-hidden="true">☰</span> Menu
</button>
12. Доступный опрос "Да/Нет"Плохой код
<form role="form">
  <h2>Poll title</h2>
  <div id="pollQuestion">Is this accessible?</div>
  <div name="pollGroup" role="radiogroup">
    <div role="radiogroup" aria-label="Poll title">
      <input type="radio" name="poll" aria-labelledby="pollQuestion" value="[object Object]">
      <span>Yes</span>
      <input type="radio" name="poll" aria-labelledby="pollQuestion" value="[object Object]">
      <span>No</span>
      <input type="radio" name="poll" aria-labelledby="pollQuestion" value="[object Object]">
      <span>Maybe</span>
      <input type="radio" name="poll" aria-labelledby="pollQuestion" value="[object Object]">
      <span>Can you repeat the question?</span>
    </div>
    <button type="submit">Vote</button>
  </div>
</form>
Ошибки и что следует исправить
  • Элемент <form> сам по себе семантически подразумевает форму, поэтому нет необходимости повторно указывать это с помощью атрибута role="form"
  • Форма – это важный для доступности страницы ориентир. Поэтому внутрь формы полезно поместить заголовок, а в элементе <form> с помощью атрибута aria-labeledby, сослаться на него. Это существенно облегчает навигацию в документе с помощью вспомогательных технологий
  • Задавать атрибут role="radiogroup" необязательно, и уж точно не дважды. Если нужно сгруппировать элементы, просто используйте <fieldset>
  • Не используйте aria-labelledby для создания связи между радиокнопкой и вопросом. Данный атрибут предназначен для установки доступного имени. Вместо этого для текста вопроса используйте элемент <legend>
  • Чтобы радиокнопке назначить доступное имя, текст из <span> поместите в элемент <label> и свяжите с радиокнопкой с помощью атрибута for
  • Кнопку также следует поместить внутрь <fieldset> для создания одной логической группы элементов
Хороший код
<form aria-labelledby="poll-title">
  <h2 id="poll-title">Poll title</h2>
  <fieldset>
    <legend>Is this accessible?</legend>
    <input type="radio" id="radio1" name="poll" value="yes">
    <label for="radio1">Yes</label>
    <input type="radio" id="radio2" name="poll" value="no">
    <label for="radio2">No</label>
    <input type="radio" id="radio3" name="poll" value="maybe">
    <label for="radio3">Maybe</label>
    <input type="radio" id="radio4" name="poll" value="repeat">
    <label for="radio4">Can you repeat the question?</label>
    <button type="submit">Vote</button>
  </fieldset>
</form>
13. Ссылка или Плохой код
<input type="checkbox" id="accept" required>
<label for="accept">
  <a href="/legal"> I accept the confidentiality policy and data… </a>
</label>
Ошибки и что следует исправить
  • Вкладывать элементы с запускаемым поведением (таким как клик) считается плохим решением
  • Возможность выбора чекбокса путём нажатия на его название улучшает удобство и доступность (путём увеличения области клика)
  • Но в данном случае пользователи не ожидают, что при нажатии на название чекбокса откроется новая страница
  • Размещайте ссылки за пределами элемента <label>
Хороший код
<input type="checkbox" id="accept" required>
<label for="accept"> I accept the confidentiality policy and data… </label>
(read <a href="/legal">Terms and conditions</a>)
Источники 14. Неподходящий "type"Плохой код
<a type="button" class="button" href="/signup" tabindex="-1">Sign up</a>
Ошибки и что следует исправить
  • В данном примере с элементом <a> используется атрибут type , хотя он никак не влияет на семантику и можно сказать, что совсем неуместен
  • Если его и добавлять, то лишь со значением, являющимся допустимым MIME-типом. Браузеры могут учитывать его, но исключительно как рекомендацию
  • Если это ссылка, которая в атрибуте href содержит путь на какой-то ресурс (страницу или документ), следует использовать элемент <a>, а не <button>, независимо от того, как данный элемент выглядит в дизайне: как ссылка или как кнопка
  • Отрицательное значение tabindex значит, что элемент не получит фокус при переключении между элементами с помощью клавиатуры. Правда, такой элемент всё же может получить фокус с помощью JavaScript
  • Не меняйте семантику элементов, присущую им по умолчанию, если только в этом нет явной необходимости
  • Если вам нужна кнопка, просто используйте элемент <button>
Хороший код
<a href="/signup" class="button">Sign up</a>
Источники 15. Буква за буквойКонтекст: буквы обёрнуты в <div> с целью анимирования каждой буквы через JavaScriptПлохой код
<h3>
  <div style="display: block; text-align: start; position: relative;" class="title">
    <div style="position: relative; display: inline-block; transform: rotateX(90deg); transform-origin: 50% 50% -30.8917px;" class="char">H</div>
    <div style="position: relative; display: inline-block; transform: rotateX(90deg); transform-origin: 50% 50% -30.8917px;" class="char">e</div>
    <div style="position: relative; display: inline-block; transform: rotateX(90deg); transform-origin: 50% 50% -30.8917px;" class="char">a</div>
    <div style="position: relative; display: inline-block; transform: rotateX(90deg); transform-origin: 50% 50% -30.8917px;" class="char">d</div>
    <div style="position: relative; display: inline-block; transform: rotateX(90deg); transform-origin: 50% 50% -30.8917px;" class="char">i</div>
    <div style="position: relative; display: inline-block; transform: rotateX(90deg); transform-origin: 50% 50% -30.8917px;" class="char">n</div>
    <div style="position: relative; display: inline-block; transform: rotateX(90deg); transform-origin: 50% 50% -30.8917px;" class="char">g</div>
  </div>
</h3>
Ошибки и что следует исправитьЕсли каждая буква обёрнута в отдельный элемент, вспомогательные технологии могут озвучивать текст, произнося каждую букву по отдельностиИзвините, данный ресурс не поддреживается. :( Код примера, продемонстрированного на видео
  • Старайтесь избегать разрастания DOM. Слишком большое количество DOM-узлов или вложенных элементов может плохо отразиться на производительности страницы
  • Большое DOM-дерево ведёт к большому дереву доступности, которое также может плохо сказываться и на производительности вспомогательных технологий
  • Рекомендуется отделять представление от содержимого. Стили, которые не изменяются динамично, поместите в CSS-файл
Хороший кодРешение №1
<h3> Heading </h3>
Решение №2Если в этом действительно есть необходимость, добавьте версию текста, доступную для скринридеров, а текст, предназначеный для визуального отображения, скройте с помощью aria-hidden="true"
<h3 class="title">
  <span class="sr-only">Heading</span>
  <div aria-hidden="true">
    <div style="transform-origin: 50% 50% -30.8917px;" class="char">H</div>
    <div style="transform-origin: 50% 50% -30.8917px;" class="char">e</div>
    <div style="transform-origin: 50% 50% -30.8917px;" class="char">a</div>
    <div style="transform-origin: 50% 50% -30.8917px;" class="char">d</div>
    <div style="transform-origin: 50% 50% -30.8917px;" class="char">i</div>
    <div style="transform-origin: 50% 50% -30.8917px;" class="char">n</div>
    <div style="transform-origin: 50% 50% -30.8917px;" class="char">g</div>
  </div>
</h3>
К сожалению, не существует встроенного способа скрывать содержимое только визуально. Для этого следует использовать CSS-правило, называемое классом .sr-only которое гарантирует, что контент будет скрыт визуально, но останется доступным для пользователей скринридеров.
.title {
  display: block;
  text-align: start;
  position: relative;
}
.char {
  position: relative;
  display: inline-block;
  transform: rotateX(90deg);
}
.sr-only {
  position: absolute;
  white-space: nowrap;
  width: 1px;
  height: 1px;
  overflow: hidden;
  border: 0;
  padding: 0;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  margin: -1px;
}
Источники 16. alt, хотя нет..., aria-label, хотя нет..., altКонтекст: список изображений, которые ссылаются на страницы с товаромПлохой код
<a tabindex="0">
  <div alt="Browser Wars: The Last Engine" aria-label="Browser Wars: The Last Engine">
    <div>
      <img alt="Browser Wars: The Last Engine" src="thumbnail.jpg">
    </div>
  </div>
</a>
Ошибки и что следует исправить
  • Если у элемента <a> атрибут href имеет пустое значение или данного атрибута нет вообще, он представляет собой заполнитель для того места, где могла быть ссылка (HTML спецификация)
  • Если вы добавляете обработчик клика к ссылке-заглушке, то, по всей видимости, хотите, чтобы она была не заглушкой, а полноценной ссылкой с атрибутом href или кнопкой <button>, в зависимости от того, что должно происходить при клике
  • Ссылки без href не получают фокус при переключении между элементами с помощью клавиатуры. tabindex исправляет эту ситуацию, но необходимость использовать данный атрибут лишь подтверждает тот факт, что корректно оформленная ссылка была бы в данном случае лучшим выбором
  • Атрибут alt не используется с элементами div и никак не влияет на их семантическое значение
  • Не злоупотребляйте ARIA. Атрибут aria-label лишний для элемента div, потому что img уже имеет доступное имя (значение атрибута alt)
Хороший кодПолноценная ссылка с заполненным атрибутом href и текстовая альтернатива alt для изображения
<a href="detail.html">
  <div>
    <img alt="Browser Wars: The Last Engine" src="thumbnail.jpg">
  </div>
</a>
Источники 17. Недоступные карточкиКонтекст: список карточек со ссылками, каждая из которых имеет заголовок, изображение и краткое описаниеПлохой код
<section>
  <section>
    <h2>Overview</h2>
    <figure class="card" data-url="image1.html" style="background: url(image1.jpg)">
      <figcaption>
        <h4>My heading</h4>
        <article>Teasertext...</article>
      </figcaption>
    </figure>
    <figure class="card" data-url="image2.html" style="background: url(image2.jpg)"> … </figure>
  </section>
</section>
Ошибки и что следует исправить
  • Вероятнее всего, в подобных ситуациях необходимости в таком количестве элементов <section> нет. Чтобы лучше понять почему, рекомендую прочитать статью "Why You Should Choose HTML5 <article> Over <section>" автора Bruce Lawson
  • Не следует недооценивать важность соблюдения иерархии заголовков. Например, пользователи скринридеров при навигации по сайту нередко ориентируются в структуре документа именно по его заголовкам
  • Согласно спецификации, HTML5-элемент <figure> представляет собой самодостаточный элемент, который под основным содержимым опционально может содержать подпись. Но в этом примере нет содержимого, есть только подпись
  • Изображнеие карточки не является декоративным, оно несёт какую-то информацию и должно быть частью HTML-кода документа, а не добавляться через CSS-свойство background . Фоновые изображения доступны пользователям не всех устройств
  • В приведённом примере обработка клика на карточку происходит только через JavaScript. Если нет элемента ссылки с указанием пути (<a href="path/to/page">), для пользователей скринридеров переход на страницу карточки становится недоступным. Также элемент карточки не получает фокус при навигации с помощью клавиатуры
  • Элементы <h1> - <h6> представляют собой вводный заголовок для родительского элемента <section>. <h4> является потоковым содержимым и, как следствие, технически может быть потомком <figcaption>, но лучше сделать его заголовком всей карточки
  • Элемент <article> представляет собой самодостаточную композицию на странице. Это может быть газетная статья , эссе или отчёт, публикация в блоге или социальной сети. Для обычного абзаца текста лучше использовать элемент <p>
  • Сделать доступной карточку, вся область которой является кликабельной, непросто. Дополнительную информацию можно найти в разделе "источники" ниже
Хороший код
<div>
  <section>
    <h2>Overview</h2>
    <article class="card">
      <h3>
        <a href="image1.html"> My heading </a>
      </h3>
      <img src="image1.jpg" alt="Description of image1" />
      <p>Teasertext...</p>
    </article>
    <article class="card"> … </article>
  </section>
</div>
Источники 18. Панель div-игацииКонтекст: главная панель навигации Плохой код
<div class="nav">
  <div>
    <div>about</div>
    <div>thoughts</div>
  </div>
</div>
Ошибки и что следует исправить
  • <div> - это элемент, используемый в крайнем случае - когда никакие другие элементы не подходят. Использование <div> вместо более подходящих по семантике элементов ухудшает доступность
  • Для главной панели навигации лучше использовать самантический элемент <nav>. Он является важным для доступности страницы ориентиром, содержащим ссылки на внешние и внутренние страницы. Пользователи скринридеров могут сразу получить доступ к навигации или наоборот пропустить её
  • Используйте элементы <ul> или <ol> для структурирования ссылок, связанных семантически и визуально. Скринридеры обычно объявляют номер элемента в списке, что помогает ориентироваться
  • Если в навигации имеет значение последовательность элементов, используйте <ol> вместо <ul>
  • Если вместо <a> для ссылок использовать элемент <div>, событие клика будет вызываться только непосредственно кликом мыши. Это сделает их недоступными для пользователей скринридеров и тех, кто переключается между элементами с помощью клавиатуры
Хороший код
<nav>
  <ul class="nav">
    <li>
      <a href="/about">about</a>
    </li>
    <li>
      <a href="/thoughts">thoughts</a>
    </li>
  </ul>
</nav>
Источники 19. Неправильная работа с заголовкамиКонтекст: простая страница, которая отображает наличие товараПлохой код
<h1>Product Status</h1>
<h2>Is the product available?</h2>
<div>
  <h3>
    <div>
      <div>
        <i>
          <h3 class="message is-success">
            It‘s <a>available</a>.
          </h3>
        </i>
      </div>
    </div>
  </h3>
</div>
Ошибки и что следует исправить
  • Элементы <h1> - <h6> не должны использоваться для разметки подзаголовков, альтернативных заголовков и слоганов, если только они не озаглавливают новый раздел или подраздел
  • Все элементы <div> в данном примере излишни. Скорее всего, они присутствуют только потому, что фронтенд-фреймворк добавляет их по умолчанию. Используйте Fragments in React или подобные техники в других фреймворка, чтобы избежать этого
  • Старайтесь избегать разрастания DOM. Слишком большое количество DOM-узлов и вложенных элементов может отрицательно повлиять на производительность страницы
  • Большое DOM-дерево ведёт к большому дереву доступности, что может отрицательно влиять и на скорость работы вспомогательных технологий
  • Потомками <h1> - <h6> могут быть только элементы фразового содержимого. <h3> и <div> к ним не относятся
  • Элемент <i> представляет собой фрагмент, который скринридеры произносят изменённой интонацией, чтобы обозначить его отличие от остального текста. Если вам нужен просто курсивный текст, используйте CSS-свойство font-style: italic
  • Если у элемента <a> нет атрибута href, он представляет собой заглушку в том месте, где в другой ситуации может быть ссылка
  • Если вы добавляете обработчик клика к ссылке-заглушке, то, по всей видимости, хотите, чтобы она была полноценной ссылкой с атрибутом href или кнопкой <button>, в зависимости от того, что должно происходить при клике
Хороший код
<h1>Product Status</h1>
<p>Is the product available?</p>
<p class="message is-success">
   It‘s <a href="/product.html">available</a>.
</p>
Источники 20. Спецвыпуск HTMHell: кнопка "Закрыть"В данном спецвыпуске рассматривается один из наиболее сложных и наиболее спорных шаблонов во фронтенд-разработке – кнопка "Закрыть".В модальных окнах, рекламных баннерах и других всплывающих элементах интерфейса часто можно встретить кнопку с символом закрытия, позволяющую пользователям, или, по крайней мере, части из них, закрыть его. Часто функциональность таких кнопок ограничена лишь пользователями мыши, потому что большинство реализаций кнопки закрытия - полный отстой.После недолгого исследования, занявшего менее 2 часов, HTMHell представляет 11 примеров плохой реализации данного элементаПример 1: с фоновым изображением
<div class="close"></div>
close::after {
  background: url("close.png");
  content: "";
}
Ошибки и что следует исправить
  • <div> - это элемент для крайнего случая, когда никакие другие элементы не подходят. Использование <div> вместо более подходящих по семантике элементов ухудшает доступность
  • На <div> событие клика вызывается только непосредственно кликом мыши. На элементах <button> это происходит ещё и при нажатии на кнопки Enter или Space на клавиатуре
  • <div> не получает фокус при переключении между элементами с помощью клавиатуры
  • Для фонового изображения невозможно задать текстовую альтернативу
  • Данный элемент скринридеры озвучат: Никак
Пример 2: с иконкой
<div class="close">
  ✕
</div>
Ошибки и что следует исправить
  • Символ "✕" не является чем-то вроде "Закрыть" или "Перечёркнуто", это знак умножения. Например, 2 ✕ 2 (два умножить на два). В кнопках "Закрыть" использовать его неуместно
  • В первом примере подробно описаны проблемы, связанные с использованием элемента <div>
  • Данный элемент скринридеры могут озвучить: как-то вроде "умножить на" или "разы" (times)
Пример 3: Иконки Font Awesome
<div class="close">
  <i class="fas fa-times"></i>
</div>
.fa-times::before {
  content: '\f00d';
}
Ошибки и что следует исправить
  • Скринридеры могут озвучивать содержимое, которое генерируется через CSS
  • Font Awesome рекомендуют скрывать иконки семантически с помощью атрибута aria-hidden="true" для элемента <i>
  • Font Awesome добавляет Unicode-содержимое через псевдоэлемент ::before. Вспомогательные технологии могут озвучивать эту Unicode-альтернативу, которая в этом конкретном примере будет звучать как "разы" (times), поскольку fa-times - это не крестик, а знак умножения. Обратите внимание: Talkback и VoiceOver в данном примере не озвучат вообще ничего
  • Элемент <i> представляет собой фрагмент, который скринридеры произносят другой интонацией, что обозначить его отличие от остального текста. Если вам нужен просто курсивный текст, используйте CSS-свойство font-style: italic
  • В первом примере подробно описаны проблемы, связанные с использованием элемента <div>
  • Данный элемент скринридеры могут озвучить: "разы" (times)
Пример 4: Закрывающая ссылка
<a href="#" class="close">
</a>
a::after {
  font-size: 28px;
  display: block;
  content: "×";
}
Ошибки и что следует исправить
  • Если элемент <a> содержит атрибут href, он представляет собой ссылку на другой ресурс: такой как страница или PDF-документ
  • Задача элемента в нашем примере – вызвать JavaScript-действие на текущей странице. Элемент <button> с атрибутом type="button" подходит лучше, потому что не имеет поведения по умолчанию и разработан для вызова действий в ответ на нажатие пользователем
  • Если вы не уверены, когда нужно использовать элемент <a>, а когда <button>, посмотрите видео "The Links vs. Buttons Showdown" от Marcy Sutton
  • Скринридеры могут озвучивать содержимое, которое генерирует CSS
  • Символ "✕" не является чем-то вроде "Закрыть" или "Перечёркнуто", это знак умножения. Например, 2 ✕ 2 (два умножить на два). Не используейте его в кнопках "Закрыть"
  • Данный элемент скринридеры могут озвучить: "ссылка, разы" (link, times)
Пример 5: Закрывающая ссылка с текстом
<a href="#" class="close">
  Close
</a>
.close::before {
  content: "\e028";
}
Ошибки и что следует исправить
  • Хорошая попытка, но это всё ещё ссылка, а не кнопка
  • В предыдущем примере подробно написано про использование элемента <a> и генерируемое CSS содержимое
  • Данный элемент скринридеры могут озвучить: "ссылка, разы закрыть" (link, times close)
Пример 6: Закрывающая ссылка без атрибута href
<a class="close" onclick="close()">×</a>
Ошибки и что следует исправить
  • Ещё одна хорошая попытка, но ссылка без атрибута href всё еще не является кнопкой
  • Если у элемента <a> нет атрибута href, он представляет собой заглушку в том месте, где в другой ситуации может быть ссылка
  • Если вы добавляете обработчик клика к ссылке-заглушке, то, по всей видимости, хотите, чтобы она была не заглушкой, а полноценной ссылкой с атрибутом href или кнопкой <button>, в зависимости от того, что должно происходить при клике
  • Ссылки-заглушки не получают фокус при переключении между элементами с помощью клавиатуры
  • Если вы не уверены, когда нужно использовать элемент <a>, а когда <button>, посмотрите видео "The Links vs. Buttons Showdown" от Marcy Sutton
  • Данный элемент скринридеры могут озвучить: "разы, кликабельно" (times, clickable)
Пример 7: Ссылка-заглушка и изображение
<a onclick="close();">
   <img src="close.png">
</a>
Ошибки и что следует исправить
  • Для изображения не задана текстовая альтернатива. Скринридеры могут озвучить название файла
  • В 6 примере подробно написано про использование ссылок-заглушек
  • Скринридеры могут озвучить данный элемент: "close.png, изображение" (close.png, image)
Пример 8: Радио-кнопка
<label class="close" for="close">
   <svg> … </svg>
</label>
<input id="close" type="radio">
[type="radio"] {
  display: none;
}
Ошибки и что следует исправить
  • Когда сторонники доступности говорят "Просто используйте кнопку", они имеют в виду элемент <button>, а не радио-кнопки
  • Радио-кнопки используются в группах, описывающих набор связанных вариантов (опций)
  • У SVG нет текстовой альтернативы. Чтобы больше узнать о доступности SVG, рекомендую почитать статью "Creating Accessible SVGs" автора Carie Fisher
  • Также, display: none на элементе <input> делает недоступным <label>
  • Данный элемент скринридеры озвучат: Никак
Пример 9: Кнопка с иконкой
<button class="close" type="button">
  ×
</button>
Ошибки и что следует исправить
  • Символ "✕" не является чем-то вроде "Закрыть" или "Перечёркнуто", это знак умножения. Например, 2 ✕ 2 (два умножить на два). Не используейте его в кнопках "Закрыть"
  • Данный элемент скринридеры могут озвучить: "разы, кнопка" (times, button)
Пример 10: Кнопка с svg
<button class="close">
  <svg> … </svg>
</button>
Ошибки и что следует исправить
  • У SVG нет текстовой альтернативы. Чтобы больше узнать о доступности SVG, рекомендую почитать статью "Creating Accessible SVGs" автора Carie Fisher
  • Данный элемент скринридеры могут озвучить: "кнопка" (button)
Пример 11: Старая добрая буква "X"
<div role="button" tabindex="0">X</div>
Ошибки и что следует исправить
  • Нет необходимости с помощью атрибута role явно задавать семантику элемента, вместо этого стоит просто использовать элемент <button
  • При использовании <button> атрибут tabindex не нужен. HTML-кнопки получают фокус по умолчанию
  • В 1 примере подробно описаны проблемы, связанные с использованием элемента <div>
  • Буква "X" не является иконкой для кнопки "Закрыть"
  • Данный элемент скринридеры могут озвучить: "икс, кнопка" (X, button)
"Использовать букву "X" для кнопок "Закрыть" – это то же, что добавлять в кофе соль вместо сахара, потому что выглядит она так же"Max Böck
Примеры правильной разметкиРешение 1: Кнопка с видимым текстом без иконки
<button type="button">
  Close
</button>
  • Только текст: легко в реализации и понятно для пользователей
  • Скринридеры могут озвучить данный элемент: "Закрыть, кнопка" (Close, button)
Решение 2: Кнопка с видимым текстом и только визуально доступной иконкой
<button type="button">
  Close
  <span aria-hidden="true">×</span>
</button>
  • Если вы вынуждены использовать иконку "умножить", скройте её от скринритеров, обернув в элемент <span> с атрибутом aria-hidden="true"
  • Скринридеры могут озвучить данный элемент: "Закрыть, кнопка" (Close, button)
Решение 3: Кнопка со скрытым текстом и только визуально доступной иконкой
<button type="button">
  <span class="sr-only">Close</span>
  <span aria-hidden="true">×</span>
</button>
.sr-only {
  position: absolute;
  white-space: nowrap;
  width: 1px;
  height: 1px;
  overflow: hidden;
  border: 0;
  padding: 0;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  margin: -1px;
}
  • К сожалению, не существует встроенного способа скрывать содержимое только визуально. Для этого следует использовать CSS-правило, называемое классом .sr-only которое гарантирует, что контент будет скрыт визуально, но останется доступным для пользователей скринридеров.
  • Скринридеры могут озвучить данный элемент: "Закрыть, кнопка" (Close, button)
Решение 4: Кнопка со скрытым текстом и только визуально доступной иконкой
<button type="button" aria-label="Close">
  <span aria-hidden="true">×</span>
</button>
  • Если вы не хотите показывать текст на экране, обеспечьте для иконки или SVG текстовую альтернативу, добавив к кнопке атрибут aria-label
  • Скринридеры могут озвучить данный элемент: "Закрыть, кнопка" (Close, button)
Решение 5: Font AwesomeДля полноты картины, закрывающая кнопка с иконкой Font Awesome
<button type="button" class="close" aria-label="Close">
  <span class="fa fa-times" aria-hidden="true"></span>
</button>
Общие примечанияПорой может иметь смысл использование более описательных названий "Закрыть окно", "Закрыть галереию", "Закрыть рекламу".Если вы используете сторонние решения для подальных, диалоговых окон, проверяйте, как они были реализованы перед добавлением на сайт. Не полагайтесь на других в вопросах качества и доступности кода.Источники 21. Легендарный legendКонтекст: кнопка, которая разворачивает и сворачивается блок текстаПлохой код
<button class="panel-heading" tabindex="0" href="#collapse0" aria-expanded="true">
  <legend> Industries Served </legend>
</button>
Ошибки и что следует исправить
  • <legend> не разрешается помещать в какой-нибудь другой элемент, кроме <fieldset> (HTML спецификация для legend)
  • При использовании <button> атрибут tabindex не нужен. HTML-кнопки получают фокус по умолчанию.
  • Атрибут href не может использоваться с элементом <button> (HTML спецификация для button)
Хороший код
<button class="panel-heading" aria-expanded="true">
  Industries Served
</button>
Источники 22. Старая добрая div-ссылкаКонтекст: ссылка на другую страницуПлохой код
<div>About us</div>
<div onClick="location.href='about.html'">
  About us
</div>
<div data-page="aboutus" data-url="index.php">
  About us
</div>
... или любой другой вариант этого шаблона, где для ссылки на другую страницу используется отличный от <a> элементОшибки и что следует исправить
  • <div> - это элемент для крайнего случая, когда никакие другие элементы не подходят. Использование <div> вместо более подходящих по семантике элементов ухудшает доступность
  • На <div> событие клика вызывается только непосредственно кликом мыши. На элементах <a> это происходит ещё и при нажатии на кнопку Enter на клавиатуре.
  • <div> не получает фокус при переключении между элементами с помощью клавиатуры
  • При нажатии правой кнопкой мыши в контекстном меню не будет пунктов "Открыть в новой вкладке/окне" или "Добавить ссылку в закладки"
  • По умолчанию скринридеры просто озвучивают текст внутри <div> (например, "О нас"). В случае использования ссылки <a> скринридеры текст и роль элемента (например, "О нас, ссылка")
  • Атрибуты наподобие aria-label у элементов <div> могут работать неправильно
  • Пользователи скринридеров могут использовать раздел со списком ссылок страницы. <div>-ссылок в этом разделе не будет, если только к элементу не будет добавлен атрибут role="link"
Хороший код
<a href="aboutus.html">
  About us
</a>
23. Шаблон карточкиПлохой код
<article>
  <div>
    <div class="sr-only">Image</div>
    <img src="/feature-teaser.png" alt="Feature teaser" />
  </div>
</article>
<div>
  <span>
    <span>Exciting feature!</span>
  </span>
  <div> This text describes what the feature does! </div>
  <a href="/blog/feature">
    <span>Read more</span>
    <svg viewBox="0 0 9 12" xmlns="http://www.w3.org/2000/svg">
      <path d="M.84 10.59L5.42 6 .84 1.41 2.25 0l6 6-6 6z"></path>
    </svg>
  </a>
</div>
Ошибки и что следует исправить
  • В примере выше используется <article>. Этот элемент предназначен для самодостаточного содержимого, которое может быть повторно использовано на странице. Если здесь и есть что-то повторно используемое, то вся карточка. Более подходящим является элемент <section>
  • В первом <div> присутствует доступный только для скринридеров текст "Image". Ощущение, что это своего рода определение роли следующего за ним элемента <img>. Правильно подобранные HTML-элементы сами сообщают о своей семантике. Необходимость в дополнительных "уточнениях" отпадает
  • Далее расположен <span>, который, кажется, является заголовком. Вспомогательные технологии могут использовать указанные в коде заголовки для быстрой навигации. Следовательно, более корректным будет использовать элемент заголовка корректного уровня. В данном случае – <h4>
  • Основной текст внутри карточки обёрнут в <div>. Использование <p> лучше передало бы его предназначение
  • "Read more" – не самый лучший текст для ссылки. Это особенно заметно пользователям скринридеров, которые используют навигацию по ссылкам. Из названия непонятно, куда именно ведёт эта ссылка
  • <svg> внутри ссылки не предоставляет дополнительную информацию и должен быть скрыт от скринридеров
Хороший код
<section>
  <div>
    <img src="/feature-teaser.png" alt="" />
  </div>
  <div>
    <h4>Exciting feature!</h4>
    <p>This text describes what the feature does!</p>
    <a href="/blog/feature">
      <span>Read more about our exciting feature </span>
      <svg aria-hidden="true" viewBox="0 0 9 12" xmlns="http://www.w3.org/2000/svg">
        <path d="M.84 10.59L5.42 6 .84 1.41 2.25 0l6 6-6 6z"></path>
      </svg>
    </a>
  </div>
</section>
Источники 24. Placeholder - это не label
<input type="text" placeholder="First name">
Ошибки и что следует исправить
  • Каждому элементу <input> нужен <label>. Когда пользователи скринридера переходят к полям формы, озвучивается содержимое <label> и тип поля (например, "имя, поле ввода"). Если этот текст пропущен, пользователи могут не знать, какую информацию они должны указать в этом поле
  • Некоторые скринридеры всё же берут текст из атрибута placeholder, но не стоит полагаться на это
  • По умолчанию, текст placeholder отображается светло-серым цветом с плохой контрастностью. Людям со слабым зрением может быть трудно его прочитать при ярком солнечном свете
  • С помощью псевдоэлемента ::placeholder можно улучшить контрастность цвета. Но тут тоже нужно не переборщить, поскольку текст с очень контрастным цветом пользователи могут принять за текстовое содержимое поля
  • Использование <label> увеличивает область выбора нужного поля, что может быть очень полезным, особенно на устройствах с сенсорным экраном
  • Если placeholder является единственным местом, содержащим название поля, при вводе текста он скрывается. Это ухудшает удобство заполнения форм, особенно если содержат много полей.
  • Пользователи не могут проверить, правильно ли они заполнили форму, потому что видят только значения, но не названия полей.
  • Если браузер заполняет поле автоматически, пользователь вынужден вырезать содержимое поля, чтобы убедиться, что оно заполнилось корректно
  • Текст placeholder обрезается, если он выходит за рамки поля
  • Инструменты подобные Google Translate могут не переводить значение данного атрибута при переводе всей страницы
  • Названия полей лучше воспринимаются, когда расположены над соответствующим текстовым полем, а не внутри него
Хороший код
<label for="firstname">First name</label>
<input type="text" id="firstname">
Источники
===========
Источник:
habr.com
===========

===========
Автор оригинала: Manuel Matuzović et al.
===========
Похожие новости: Теги для поиска: #_razrabotka_vebsajtov (Разработка веб-сайтов), #_css, #_html, #_usability, #_accessibility, #_htmhell, #_html, #_css, #_accessibility, #_razrabotka_vebsajtov (
Разработка веб-сайтов
)
, #_css, #_html, #_usability, #_accessibility
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 23-Ноя 01:08
Часовой пояс: UTC + 5