Компоненты и состояния: кнопки и поля, которые ведут себя одинаково

Анатомия кнопки, три вида в шести состояниях, восемь состояний и что они показывают, поле с ошибками и настоящие состояния, которые можно попробовать, — с живыми примерами.

Дизайн-системы Обновлено

Коротко

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

Анатомия кнопки

До состояний — части. У каждой части есть значение из токенов, а не число, подобранное для одной этой кнопки.

Оформить заказ 123 456
  1. Контейнер: фон color.accent-strong, высота 48 px
  2. Поля по бокам: space.5 = 24 px
  3. Подпись: font.step-0, насыщенность 600
  4. Промежуток до значка: space.2 = 8 px
  5. Значок: 20 px, цвет подписи
  6. Радиус: radius.full — полностью скруглённые края

Три вида × шесть состояний

Вся кнопка на одном листе. Именно это должно быть в макете до начала разработки.

ОбычноеНаведениеФокусНажатиеНедоступнаЗагрузка
Главная КупитьКупитьКупитьКупитьКупитьКупить
Второстепенная КупитьКупитьКупитьКупитьКупитьКупить
Контурная КупитьКупитьКупитьКупитьКупитьКупить

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

Восемь состояний и что они должны показывать

Каждое состояние отвечает на вопрос человека: можно ли это нажать, сработало ли, что не так.

СостояниеКогдаЧто меняется
Обычное элемент готов к работе базовый вид
Наведение над ним курсор немного другой фон
Фокус на него перешли с клавиатуры хорошо заметное кольцо
Нажатие в момент клика темнее тон и лёгкий сдвиг
Недоступно действие пока невозможно приглушённый вид, нет отклика
Загрузка действие выполняется индикатор и подпись о процессе
Ошибка значение неверное сигнальный цвет и подсказка, как исправить
Успех значение проверено спокойное подтверждение

Поле ввода в шести состояниях

Ошибка не просто красит рамку — она говорит, что не так и как это исправить.

Обычное Электронная почта [email protected] Пришлём подтверждение заказа
Заполнено Электронная почта [email protected] Пришлём подтверждение заказа
Фокус Электронная почта anna@exa Пришлём подтверждение заказа
Ошибка Электронная почта anna@ Проверьте адрес: после @ нужен домен
Проверено Электронная почта [email protected] Адрес подтверждён
Недоступно Электронная почта [email protected] Почту меняют в профиле

Попробуйте: настоящие состояния

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

Наведите, перейдите табом, нажмите
Проверьте адрес почты

Состояния в коде: 2 примера

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

Кнопка и её состояния

Одна строка на состояние; ни один цвет не записан напрямую.

button.css
/* Кнопка: описано каждое состояние, каждое значение — из токенов */
.button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 2.75rem;                 /* 44px: удобная цель для пальца */
  padding-inline: var(--space-5);
  border: 0;
  border-radius: var(--radius-full);
  background: var(--color-accent-strong);
  color: var(--color-on-accent-strong);
  font: 600 var(--step-0) / 1 var(--font-text);
  transition: background-color var(--duration-fast), transform var(--duration-fast);
}

.button:hover            { background: var(--color-accent-strong-hover); }
.button:focus-visible    { outline: 2px solid var(--color-focus); outline-offset: 3px; }
.button:active           { transform: translateY(1px) scale(0.98); }
.button:disabled         { background: var(--color-disabled); color: var(--color-text-muted); cursor: not-allowed; }
.button[aria-busy="true"] { cursor: progress; }

Загрузка и ошибки в разметке

aria-busy и aria-describedby сообщают вспомогательным технологиям то, что видит глаз.

states.html
<!-- Загрузка: кнопка занята, её нельзя нажать дважды, и она говорит, что происходит -->
<button class="button" type="submit" disabled aria-busy="true">
  <span class="spinner" aria-hidden="true"></span>
  Сохраняем…
</button>

<!-- Ошибка принадлежит своему полю, и поле на неё ссылается -->
<label for="email">Электронная почта</label>
<input id="email" type="email" aria-invalid="true" aria-describedby="email-error">
<p id="email-error" class="field-error">Проверьте адрес: после @ нужен домен</p>

7 правил законченного компонента

  1. 01

    Все состояния — в макете

    Компонент без состояний — эскиз, а не компонент.

  2. 02

    Фокус виден всегда

    Кольцо, заметное на любом фоне, — для тех, кто работает с клавиатуры.

  3. 03

    Наведение — не единственный сигнал

    На телефоне курсора нет; элемент должен выглядеть нажимаемым и без него.

  4. 04

    44 px для пальца

    Нажимаемые элементы — не ниже 44 px, даже если выглядят меньше.

  5. 05

    Ошибки говорят, что делать

    «Проверьте адрес: после @ нужен домен», а не «Неверное значение».

  6. 06

    Не только цвет

    У ошибки и успеха есть ещё текст или значок — для тех, кто не различает цвета.

  7. 07

    Значения из токенов

    Каждое состояние использует роли, поэтому новая тема не ломает ни одно из них.

Частые ошибки с компонентами

  1. Только обычное состояние

    Каждый разработчик придумывает наведение и фокус по-своему.

  2. Убранная рамка фокуса

    outline: none без замены делает сайт непригодным для работы с клавиатуры.

  3. Недоступная кнопка без объяснения

    Человек не понимает, что сделать, чтобы она заработала.

  4. Ошибки до ввода

    Форма, которая красная ещё до ввода, отпугивает.

  5. Нет состояния загрузки

    Человек нажимает ещё раз и отправляет заказ дважды.

  6. Копии вместо вариантов

    Пять слегка разных кнопок вместо одной с тремя вариантами.

Вопросы о компонентах и состояниях

Какие состояния есть у кнопки?

Обычное, наведение, фокус, нажатие, недоступность и загрузка; иногда ещё выбранное.

Чем :focus отличается от :focus-visible?

:focus-visible показывает кольцо, когда человек работает с клавиатуры, и не показывает его после клика мышью.

Когда показывать ошибки в форме?

После того как человек ввёл значение и ушёл из поля или после попытки отправить — :user-invalid делает ровно это.

Нужны ли вообще недоступные кнопки?

Иногда лучше оставить кнопку активной и объяснить проблему по нажатию.

Какого размера должны быть нажимаемые элементы?

Не меньше 44 × 44 px нажимаемой области, даже если видимый элемент меньше.

Сколько вариантов кнопки нужно системе?

Обычно три: главная, второстепенная и контурная — плюс опасное действие там, где оно нужно.

Форма

Компоненты
без догадок

Проектирую и верстаю библиотеки компонентов, где описано каждое состояние, — и в макете, и в коде. Расскажите о проекте — отвечу в течение рабочего дня.

Или пишите на [email protected]