Ссылки против кнопок в современных веб-приложениях

Перевод статьи Links vs. Buttons in Modern Web Applications с сайта marcysutton.com для CSS-live.ru, автор — Марси Саттон

Кнопки в интерфейсе Github
Github: ссылки или кнопки?

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

Кнопки

Почему-то люди становятся веб-разработчиками,так и не узнав про HTML-элемент <button>. (Мне и самой потребовалось несколько лет, пока я узнала, для чего нужны заголовки h1-h6, так что бывает). Могучая кнопка и правда крутая. Вот что она может делать:

  • Получать фокус с клавиатуры по умолчанию
  • Делать «клик» по нажатию на пробел
  • Отправлять данные формы на сервер
  • Очищать форму
  • Блокироваться с помощью атрибута disabled
  • Давать подсказку скринридеру с помощью неявного атрибута role="button"
  • Показывать состояния ∶focus, ∶hover, ∶active, ∶disabled

Если добавить немного скрипта,кнопка идеальный элемент для:

  • Открывания модального окна
  • Вызова всплывающего меню
  • Переключения интерфейса
  • Проигрывания медиа-контента
  • Вставки с помощью JS в случае, если они работают только с помощью JS

Ссылки

Вот несколько базовых возможностей ссылок, или якорей, или фундамента веба:

  • Создавать гипертекст,  сеть онлайн-ресурсов
  • Перевести пользователя на новую страницу или окно
  • Изменять URL
  • Вызвать браузерные перерисовку/перезагрузку
  • Переходить по якорям внутри страницы
  • Связывать между собой разные части приложения с отрисовкой на клиенте
  • Способны принимать фокус по умолчанию с помощью атрибута href
  • Регистрируют клик с помощью клавиши Enter
  • Имеют неявную роль link
  • Не блокируются как кнопки, но их можно сделать неактивными с помощью tabindex="-1" и aria-hidden="true"
  • Могут открываться в новых окнах (а раньше — и во фреймах)
  • Показывать состояния ∶link, ∶visited, ∶focus, ∶hover, ∶active

Лично для меня самое главное различие между ссылкой и кнопкой — то, что ссылки перемещают пользователя на новый ресурс,  унося его из текущего контекста (внутренние ссылки — единственная загвоздка здесь). Кнопка переключает что-то в интерфейсе, например, видео-плеер, или вызывает новый контент в том же самом контексте, например, всплывающее меню, использующее aria-haspopup.

Что такое навигация? А что маршрутизация?

Изменение URL означает, что пользователь переходит на новую страницу.  Это перезагружает браузер с новым ресурсом и перерисовывает пиксели. Навигация может вызываться с помощью действий формы, якорных ссылок и JavaScript-свойства location.

Подобно сортировке писем на почте,маршрутизация — механизм для подключения сетевых запросов с соответствующим контентом в приложении. Маршрутизаторы — общая технология для множества разных веб-фреймворков (помню, как на заре своей карьеры я балдела от PHPшных),  ставящая в соответствие представления и их части определенным фрагментам URL. Благодаря написанию динамических маршрутов, новый контент можно создавать динамически (без хардкода)

Откуда возникает путаница?

В мире клиентских веб-приложений построенных с помощью Angular, Ember или React, браузерная перерисовка может вызваться в любой момент. Немного неясно, какой элемент лучше подходит для дела, когда вы можете выполнять тот же код, что для маршрута, но с обработчиком клика кнопки и без изменения URL. К примеру, если я открываю панель над существующим контентом страницы, я перехожу сюда или переключаю интерфейс? Зависит ли это от того, есть ли у этой панели свой собственный URL? Вот код, который я видела недавно:

<a href="#" tabindex="0" ng-click="userPicker.userClicked(true)" aria-label="Some username"></a>

По первому впечатлению я посчитала, что это надо заменить на элемент button, поскольку нет значения в href, но есть tabindex="0" и привязка обработчика ng-click. Выглядит очевидно, да? Мне сказали, что он должен оставаться якорем, поскольку «он перенаправляет на внешние ссылки и страницу профиля». Это стало ясно только после изучения JavaScript-кода, который вызывается во многих подобных местах по привязке обработчика ng-click на элементе button. Неужели обязательно надо так глубоко копать, чтобы помочь выбрать правильный элемент?

Если в чисто клиентском приложении фокус обрабатывается правильно, это может не сказаться на пользователе — у ссылки ng-click и tabindex останется по-прежнему неявная роль ссылки. Однако,при декларативном подходе намного лучше было бы использовать заполненный href и маршрутизацию, если якорь перенаправляет пользователя на новый ресурс. Tabindex и события click, привязанные к тегу ссылки — антипатерн разметки, который еще аукнется тем, кто позднее будет писать код и проверять его.

Если делать маршрутизацию основной частью веб-приложения, то веб-разработчикам будет намного проще использовать ссылки и понять предназначение кнопок. А еще с ним проще создавать серверные JavaScript-приложения с прогрессивным улучшением, пользуясь шаблонами URL, а не полагаясь на события по клику.

Роль UX в разработке доступных приложений

Я скажу это вслух: эта путаница часто начинается с дизайна и UX. Компонент дизайна приходит к нам с прямоугольными кнопками интерфейса, и — потому что так надо — в коде они должны быть ссылками. Где это становится проблемой?

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

Фреймворки, по общему признанию, стирают эту грань и только способствуют выбору неправильного элемента, как в примере с ng-click выше. Можно использовать JavaScript для запуска асинхронной отправки формы на нужном нам элементе (даже без тега <form>, но это еще не повод от него отказываться). Аналогично, можно инициировать изменения представления без маршрутизации, даже если их лучше всего обрабатывать с помощью ссылки. Одни элементы подходят в одном случае, другие — в другом: всё дело в том, чтобы использовать нативные возможности этого элемента по максимуму.

Так выглядят идентичные внешне кнопки и ссылки на Гитхабе в браузерном отладчике.

Заметили разницу?

Что можно для этого сделать?

Вернитесь к дизайну и сделайте ссылки похожими на ссылки, а кнопки — на кнопки. Без двусмысленности разработчикам станет легче писать более доступный код, лучше отвечающий ожиданиям пользователей. (Можно ли кликнуть правой кнопкой мыши на эту квадратную кнопку, чтобы открыть в новом окне?)

В разработке:

  • Если пользователь скринридера перейдет табом на интерактивный элемент, подскажет ли роль этого элемента, чего от него ждать? (Уводит ли он куда-то с текущей страницы? Об этом хорошо бы узнать заранее.)
  • Отключаете ссылочные фичи типа смены URL или правого клика? Подумайте о кнопке.
  • Поддерживайте маршрутизацию в вашем приложении с помощью href, ng-href, и так далее.
  • Навигация по страницам заслуживает изменения заголовка и истории.

Это ещё не конец.

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

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

P.S. Это тоже может быть интересно: