Когда кнопка не является кнопкой?

Опубликовано: 2022-03-10
Краткий обзор ↬ Не все, что круглое и выделяется, считается кнопкой. В этой статье Вадим объясняет, как создать правильную интерактивную кнопку для своих пользователей, которую не следует путать ни с чем другим.

Допустим, у вас есть часть интерфейса, на которую щелкает пользователь, и что-то происходит. Звучит как кнопка для меня, но давайте пока назовем это «щелчком». Я знаю, вы уверены, что это тоже кнопка: она округлая и выделяется приятным томатным цветом, так что с ней нужно взаимодействовать. Но давайте задумаемся об этом на мгновение. Обещаю, это сэкономит время в долгосрочной перспективе.

Помидорная пуговица с надписью «Something».
Дизайн для вашего «щелчка» (большой предварительный просмотр)

Что, если текст в этой кликабельной штуке был «Подробнее», и нажатие на него привело пользователя к статье на другой странице? Хм. А что, если есть подчеркнутое синим слово «Закрыть», закрывающее всплывающее диалоговое окно? Это ссылка только потому, что она выделена синим цветом и подчеркнута? Конечно, нет.

Ссылка в виде кнопки и кнопка в виде ссылки
Дилемма ссылки или кнопки (превью в большом разрешении)
Еще после прыжка! Продолжить чтение ниже ↓

Вау! Кажется, что невозможно определить, ссылка это или кнопка, просто взглянув на нее. Это безумие! Нам нужно понять, что эта штука делает, прежде чем выбрать правильный элемент. Но что, если мы еще не знаем, что он делает, или просто запутались? Что ж, для нас есть удобная блок-схема:

Блок-схема: это кнопка. Если нет, то это ссылка. Вот и все.
Научная блок-схема выбора правильного элемента (большой предварительный просмотр)
  1. Это кнопка.
  2. Если нет, то это ссылка.
  3. Вот и все.

Итак, все ли кнопки? Нет, но вы всегда можете начать с кнопки практически для любого элемента, который можно щелкнуть или с которым можно взаимодействовать аналогичным образом. А если чего-то не хватает, например перехода на другую страницу, используйте вместо этого ссылку. И нет, указатель не причина делать его <a href> . У нас есть cursor: pointer для этого.

Сфокусированная кнопка помидора с текстом «Что-то» на ней
Не забудьте указать стили фокуса. (Большой превью)

Хорошо, это <button> — с этим мы согласны. Давайте поместим его в наш шаблон и стилизуем его в соответствии с дизайном: некоторые отступы, округление, заливка помидором, белый текст и даже некоторые стили фокуса. О, это так мило с твоей стороны.

 <button type="button" class="button"> Something </button> <style> .button { display: inline-block; padding: 10px 20px; border-radius: 20px; background-color: tomato; color: white; } .button:focus { outline: none; box-shadow: 0 0 0 5px #006AE3; } </style>

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

Уродливая кнопка-помидор, отрендеренная в браузере
Иногда браузер не лучший друг. (Большой превью)

Боже мой! Что-то не так с браузером. Почему эта кнопка такая некрасивая? Текст крошечный, даже несмотря на то, что мы явно установили размер body в 16px , и даже семейство font-family неверно. Округлая кайма с дурацкой псевдотенью — это настолько ретро, ​​что это еще даже не тренд.

Ах, это стиль браузера по умолчанию. Вам нужно осторожно отменить его или даже добавить Normalize.css или Reset.css… или вы можете просто использовать <div> и забыть об этом. Разве решение проблем не является тем, за что вам платят? Ты голоден, и это совсем не помогает. Но ты же профессионал: возьми себя в руки и подумай.

В чем разница между <button> и <div> ? Встроенная <button> — это интерактивный элемент, то есть с ним можно взаимодействовать. Это глубоко. Вы можете щелкнуть ее, вы можете сфокусироваться на ней с помощью клавиатуры, а также она передает доступную роль button программам чтения с экрана, позволяя пользователям понять, что это кнопка.

Впечатляющий! Вы не только знакомы с HTML-элементом <button> , но также знаете кое-что о поддержке ARIA и средств чтения с экрана. Возможно, вы даже пробовали VoiceOver или NVDA, чтобы проверить, насколько доступны ваши интерфейсы.

Итак, вы решили пойти на хитрость. Вы не будете возиться со стилем браузера и сделаете элемент похожим на интерактивную кнопку для пользователей, которым он может понадобиться. Это умно!

 <div class="button" tabindex="0" role="button"> Something </div>

Теперь она не только выглядит правильно, но и может быть сфокусирована с помощью клавиатуры благодаря tabindex="0" , и программы чтения с экрана будут рассматривать ее как правильную кнопку, потому что вы мудро добавили к ней role="button" . Git commit && push then! Есть некоторые дополнительные задачи для этой штуки, но мы закончили со стилем. Что возможно могло пойти не так? Пора обедать. Отлично, поехали!

Час спустя…

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

 <script> const buttons = document.querySelectorAll('.button'); [...buttons].forEach(button => { button.addEventListener('click', doSomething); }); function doSomething() { console.log('Something!'); } </script>

Сделанный. Теперь пользователь может щелкнуть его мышью на рабочем столе и коснуться пальцем на сенсорном экране. Событие клика сработает надежно, и вы увидите много Что-то! в вашей консоли. Какая следующая задача?

Подожди! Нам нужно убедиться, что это работает одинаково для пользователей клавиатуры. Поскольку у нас есть tabindex="0" на кнопке, она может быть сфокусирована, и как только она будет сфокусирована, пользователи должны иметь возможность нажимать пробел или клавишу «Ввод», чтобы активировать то, что мы прикрепили.

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

 <script> const buttons = document.querySelectorAll('.button'); [...buttons].forEach(button => { button.addEventListener('click', doSomething); button.addEventListener('keyup', (event) => { if (event.key == 'Enter' || event.key == ' ') { doSomething(); } }); }); function doSomething() { console.log('Something!'); } </script>

Фу! Теперь наша щелкающая штука полностью доступна с клавиатуры. Я так тобой горжусь! А JavaScript действительно волшебен — что бы мы без него делали?

Хорошо, последнее задание: «У кнопки должно быть отключенное состояние, которое меняет ее внешний вид и поведение на что-то онемевшее». Онемевший? Я предполагаю, что это означает что-то серое и не реагирующее на взаимодействие. Хорошо, давайте добавим состояние в таблицу стилей, используя БЭМ-нейминг.

 <div class="button button--disabled" tabindex="0" role="button"> Something </div> <style> .button--disabled { background-color: #9B9B9B; } </style> 
Серая кнопка с отключенным состоянием
Эта кнопка выглядит комфортно онемевшей. (Большой превью)

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

Черт, это становится сложно.

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

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

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

Собственно говоря, они и делают…

Давайте сделаем несколько шагов назад, прежде чем прыгать в этот беспорядок. Почему бы нам не попробовать сделать это с помощью элемента <button> ? У него есть несколько полезных трюков в рукаве, а не только уродливые стили браузера. Да, и не забудьте type="button" — вы не хотите, чтобы кнопка «Закрыть» во всплывающем окне случайно отправила форму, потому что type="submit" является значением по умолчанию.

По-видимому, когда <button> находится в фокусе и нажата клавиша пробела или «Enter», это вызовет событие click , точно так же, как это делают мобильные устройства, когда они получают касания, поглаживания, облизывания или что-то еще, что они способны получать. сегодня. В нашем коде на один обработчик событий меньше! Хороший.

 // A click is enough! button.addEventListener('click', doSomething);

Что касается отключенного состояния, атрибут disabled доступен для элемента <button> , а также для всех элементов формы, включая <fieldset> . Без шуток. Знаете ли вы, что вы можете отключить целую кучу входных данных, сгруппированных вместе, просто применив один атрибут к родительскому <fieldset> ?

Группа отключенных входов и кнопка
Куча входов, отключенных с помощью одного атрибута (большой предварительный просмотр)
 <fieldset disabled> <legend>A bunch of numb inputs</legend> <p> <label> <input type="radio" name="option"> Of course it's a link </label> </p> <p> <label> <input type="radio" name="option"> Obviously, it's a button </label> </p> <p> <label> <input type="radio" name="option"> I just wanna go home </label> </p> <button type="button">Button</button> </fieldset>

Теперь ты знаешь! Этот атрибут не только отключает все события на элементах формы, но и удаляет их из порядка табуляции. Задача решена!

 <button disabled type="button" class="button"> Something </button>

Но подождите, есть еще! Он также запускает псевдокласс :disabled в CSS, что означает, что мы можем избавиться от модификатора БЭМ для объявления стилей и вместо этого использовать встроенный динамический модификатор.

 .button:disabled { background-color: #9B9B9B; }

Что касается уродливых стилей браузера, нам не нужно использовать весь Normalize.css для исправления одной кнопки. Используйте его как источник мудрости: три дополнительные строки ниже исправят большинство раздражающих отличий от <div> . Если вам когда-нибудь понадобится больше, вы можете скопировать из него соответствующие части.

 .button { font-size: 100%; font-family: inherit; border: none; }

Сделанный. В конце концов, HTML не так уж и плох!

Но если это время от времени вас удивляет, не забудьте проверить ответы на HTML-спецификацию. С годами он стал намного дружелюбнее, и он полон хороших примеров использования и доступности. И, конечно же, старый добрый HTML5 Doctor по-прежнему является надежным местом для выяснения различий между элементами <section> и <article> и проверки того, является ли структура документа еще актуальной (не совсем). Есть хороший шанс, что вы также в конечном итоге прочитаете HTML-документацию Mozilla, и вы тоже не пожалеете об этом.

Теперь эта задача выполнена! Что дальше? Выпадающий карусельный календарь с полем поиска? О боже! Удачи с этим. Но помните: <button> — ваш друг!

Дальнейшее чтение на SmashingMag:

  • Как сделать кнопки лучше
  • Создание инклюзивных переключателей
  • Дизайн кнопки-призрака: действительно ли это актуально (и почему)?
  • Шаблоны проектирования: когда можно нарушать правила