Хабр, я снова пришёл к вам с практическими советами про доступность вместе с Ильёй. Мы показываем, как HTML и CSS могут улучшить или ухудшить её. Напоминаю, что Илья — мой незрячий знакомый, который помогает мне найти наши косяки в вёрстке.


Сегодня мы рассмотрим следующие аспекты:

  • Почему в наших дизайн-системах и библиотеках есть сломанные нестандартные радиокнопки;
  • Мой способ отказаться от ссылки для изображения с сохранением интерактивности;
  • Дублирование стилей при наведении для фокуса вводит меня в ступор;
  • Можно ли скрыть кнопку с помощью атрибута disabled от скринридера.

Давайте начнём!


▍ Как мы сломали нестандартные радиокнопки


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


Конечно, я вдохновлялся Codepen, когда создавал их. Большинство примеров вообще несовместимы с режимом высокой контрастности. Я пишу «большинство», потому что верю, что есть работающие правильно. Просто их не встретил.


Давайте посмотрим, что же нужно исправить. Я сделал скриншот из моей библиотеки.


Две радиокнопки. Обе отображаются одинаково без кружка в середине

Среди элементов есть выделенный. Угадайте, какой из них он? Правильный ответ — первый. Вот доказательство в виде разметки.


<body>
  <div class="custom-radio-button">
    <input id="сrb-1" class="toggle" type="radio" name="radio" checked>
    <label for="сrb-1" class="custom-radio-button__label">Вариант №1</label>
  </div>
  <div class="custom-radio-button">
    <input id="сrb-2" class="toggle" type="radio" name="radio">
    <label for="сrb-2" class="custom-radio-button__label">Вариант №2</label>
  </div>
</body>

Взглянем на стили компонента.


.toggle[type="radio"]::before {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  background-color: #242424;

  border-radius: 50%;
  opacity: 0;
  position: absolute;
  scale: 0;
}

Здесь оставшиеся стили радиокнопок
.toggle {
  appearance: none;
  margin: 0;
  width: 1rem;
  height: 1rem;

  border: 1px solid #242424;
  border-radius: 50%;
	
  display: grid;
  place-items: center;
}

.toggle[type="radio"]:checked::before {
  opacity: 1;
  scale: 1;
}


В режиме высокой контрастности Windows браузеры берут значения определённых свойств из настроек операционной системы. Для свойства background-color — из раздела «Фон».


Окно с настройками операционной системы. Можно задать цвет фона, текста, ссылки, неактивного текста, выделенного текста, текста у кнопки. Я выбрал задать цвет фона. Отображается элемент для выбора цвета. В поле уже выбран цвет 202020

В итоге получается, что у всех элементов со свойством background-color значение будет одним и тем же. По этой причине мы не увидим, какая радиокнопка выделена.


Починить компонент можно с помощью свойства border. Надо заменить им свойства width, height, background-color.


.toggle[type="radio"]::before {
  content: "";
  border: 0.25rem solid #242424;
  /* оставшиеся CSS */
}

Две радиокнопки. У первой есть в середине кружок, у второй его нет

Другое дело. Пожалуйста, проверьте радиокнопки в ваших библиотеках и дизайн-системах. Вдруг тоже нужно исправить.


▍ Свойство aspect-ratio помогает избавиться от дублирования ссылок


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


Блок с новостью. Превью, ниже заголовок с ссылкой, ниже время публикации и количество комментариев

В разметке у нас две ссылки. Одна для изображения, вторая для текста «Антон Миранчук уехал в Швейцарию! Всё о новом клубе и контракте россиянина». Обе ведут на одну и ту же страницу.


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


Для начала покажу разметку блока без лишних элементов.


<body>
  <div class="top-article _small">
    <a href="article-5699252.html" class="top-article__img">
      <!-- здесь изображение -->
    </a>
    <div class="top-article__info">
      <a href="article-5699252.html" class="top-article__title">
        Антон Миранчук уехал в Швейцарию! Всё о новом клубе и контракте россиянина
      </a>
      <!-- здесь блок с временем публикации и с счётчиком комментариев -->
    </div>
  </div>
</body>

Нам нужно отказаться от обёртки для изображения в виде ссылки с классом .top-article__img. Заменю её элементом <div>.


<body>
  <div class="top-article _small">
    <div class="top-article__img">
      <!-- здесь изображение -->
    </div>
    <div class="top-article__info">
      <a href="article-5699252.html" class="top-article__title">
        Антон Миранчук уехал в Швейцарию! Всё о новом клубе и контракте россиянина
      </a>
      <!-- здесь блок с временем публикации и с счётчиком комментариев -->
    </div>
  </div>
</body>

Я предлагаю использовать псевдо-элемент ::before для ссылки, который будет расположен над изображением. Так сохранится возможность перейти по ссылке, кликнув по изображению, и не будет двух ссылок.


Правда тут есть загвоздка. Как повторить размеры изображения? Мы знаем его ширину и высоту. Это поможет нам вычислить соотношение сторон. Понимаете, к чему я клоню? Да, свойство aspect-ratio здесь идеальный вариант.


Значение мы рассчитаем, поделив ширину изображения (269px) на высоту (160px). Итого получим 1.68. Позиционировать псевдо-элемент будем с помощью свойств left и bottom.


.top-article__title::before {
  content: "";
  position: absolute;
  width: 100%;
  aspect-ration: 1.68;
  bottom: 100%;
  left: 0;
}

Псевдоэлемент отображен поверх превью

Этот способ я сам придумал. Как вам? Может есть минусы? Поделитесь, пожалуйста, в комментариях.


▍ Почему вам следует сохранить обводку с помощью свойства outline


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


Первое, на что следует обратить внимание — это режим высокой контрастности Windows. Обводка, созданная с помощью свойства outline, остаётся визуально заметной при переключении с помощи клавиши Tab. Это видно на примере ссылки.


<body>
  <a href="#0" class="link">Ссылка №1</a>
  <a href="#0" class="link">Ссылка №2</a>
</body>

.link:focus {
  outline: 4px solid;
}

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

А вот изменение других свойств — нет. Для демонстрации уберу свойство outline и добавлю свойство background-color в качестве альтернативы.


.link:focus {
  outline: none;
}

.link:is(:hover, :focus) {
  background-color: pink;	
}

Две ссылки. На первую ссылку сфокусировались, поэтому применились стили для состояния. Внешне ссылки одинаковые

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


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


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


По этим причинам я прошу вас не убирать обводку. Лучше оставить её и свойство outline. Тем более, это можно сделать только для пользователей клавиатуры. Псевдо-класс :focus-visible никто не отменил.


Также сделайте форму обводки однообразной. Не заставляйте меня думать, почему она стала круглой. Большое спасибо.


▍ Доступна ли кнопка с атрибутом disabled для скринридеров?


Кнопки приходится использоваться часто в интерфейсах. И не только в формах. Многие переключатели следует делать с помощью них. Периодически требуется их скрыть до определённого момента.


Например, на сайте Купер в карусели есть кнопка для возврата назад.


Интерфейс Купер. С помощью инструментов разработчика показываю кнопку

Визуально она скрыта. При переключении с помощью клавиши Tab на неё нельзя попасть. Вроде всё отлично и нет никаких проблем. Это не так.


Посмотрим, как разработчики, скрыли элемент.


В инструментах разработчика показываю, свойство opacity со значением 0

Мы видим свойство opacity. Я рассказывал подробно, что оно не скрывает элемент от скринридеров. Не буду повторяться. Я дальше покажу, к чему данный метод скрытия элемента привёл в нашем примере.


Напомню, что при переключении клавишей Tab элемент не попадается. Я стал думать, почему же так. В HTML я нашёл причину.


В инструментах разработчика показываю атрибут disabled у кнопки

Атрибут disabled отключает интерактивность у элемента. Следовательно, на него нельзя попасть с помощью клавиши Tab. Но только пользователи скинридера кроме неё используют клавиши стрелки. Например, в этом случае скринридер NVDA прозносит: «Кнопка недоступна. Назад». Приехали.


Я думаю, что разработчики сайта сначала визуально скрыли кнопку с помощью свойства opacity. Потом они поняли, что она доступна с помощью клавиши Tab, и добавили атрибут disabled. На этом они остановились.


Не делайте, пожалуйста, так. Атрибут disabled не скрывает элемент. Скринридеры дадут понять пользователю, что она есть, но недоступна. Вот, что думает Илья:


«Сообщение скринридера, что кнопка вызывает недоумение. Я понимаю, что с элементом нельзя взаимодействовать. Но не ясно, к чему здесь мне это сообщение, потому что в примере некуда идти назад. Лучше бы вообще скрыть кнопку».


Разработчикам нужно было использовать свойство display со значением none, и убирать его по мере прокрутки элементов. В этом случае всё работало бы отлично.


▍ Заключение


С помощью этой статьи мы с Ильёй хотели призвать вас:

  • Не создавать дублирующие ссылки для изображений;
  • Починить нестандартные радиокнопки, чтобы ими можно было пользоваться;
  • Сохранить обводку при использовании клавиши Tab;
  • Не дублировать стили при наведении для фокуса;
  • Не скрывать от скринридеров интерактивные элементы с помощью атрибута disabled.

Оставлю ссылки на все выпуски:

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

Комментарии (2)


  1. chipa014
    27.09.2024 09:49

    Если мы знаем ширину и высоту изображения, мы можем задать размеры ссылки через ширину и высоту ))

    А вообще часто картинка приходит откуда-то извне, и заранее мы её размер не знаем. С другой стороны, я не вижу проблем сделать всю карточку (или контейнер с картинкой и заголовком) кликабельными.

    Мне больше нравится вариант с ссылкой в качестве элемента-обёртки, но можно и ::before растянуть на весь контейнер


    1. melnik909 Автор
      27.09.2024 09:49

      Я забыл ставить ссылку. Вот в этой статье написал, почему не надо ссылкой обворачивать элементы