Перейти к содержимому
Плотность
Тема

Действия

Кнопка

Действие, которое пользователь запускает сам. По умолчанию кнопка тихая: экран, где всё primary, не имеет primary вовсе.

Компонент
Разметка
<button class="inst-btn inst-btn--primary" type="button">Запустить</button>
<button class="inst-btn" type="button">Отмена</button>
<button class="inst-btn inst-btn--ghost" type="button">Подробнее</button>
<button class="inst-btn inst-btn--danger" type="button">Удалить</button>

Использование

Минимальная кнопка — один класс и обязательный type.

<button class="inst-btn" type="button">Отмена</button>

type обязателен. Кнопка без него внутри формы имеет type="submit" по умолчанию и отправляет форму — включая ту, которая всего лишь показывала подсказку.

Кнопка бывает ссылкой. Переход по адресу обязан быть <a> — иначе ломаются средний клик, «открыть в новой вкладке» и копирование адреса, — и класс на ссылке даёт ровно ту же кнопку, без подчёркивания.

Пример
Разметка
<a class="inst-btn" href="#кнопка">Открыть отчёт</a>
<a class="inst-btn inst-btn--primary" href="#кнопка">Перейти к прогону</a>
<button class="inst-btn" type="button">Запустить</button>

Когда использовать

Используйте Возьмите другое
Действие, меняющее состояние: запустить, сохранить, удалить Переход по адресу — это <a>. Кнопка, ведущая на другую страницу, ломает средний клик, «открыть в новой вкладке» и копирование ссылки
Ровно одно primary на экран — главное действие Выбор одного из равных вариантовсегментированный контрол, а не три кнопки
Несколько связанных действий вплотную — группа Переключение вкл/выкл — свитч или чекбокс: у кнопки нет состояния «нажата»
Опасное действие — --danger Второстепенная ссылка в тексте — обычная ссылка в потоке

Варианты

Вариант — это две строки: он меняет только внутренние переменные, а не переписывает правило.

Пример
Разметка
<button class="inst-btn" type="button">По умолчанию</button>
<button class="inst-btn inst-btn--primary" type="button">Primary</button>
<button class="inst-btn inst-btn--ghost" type="button">Ghost</button>
<button class="inst-btn inst-btn--danger" type="button">Danger</button>
Вариант Когда
без модификатора Умолчание. Рамка --border-control, прозрачная заливка
inst-btn--primary Главное действие экрана. Одно на экран
inst-btn--ghost Третьестепенное: рамка снята, остался только текст
inst-btn--danger Необратимое или разрушительное действие

Размеры

Пример
Разметка
<button class="inst-btn inst-btn--sm" type="button">sm</button>
<button class="inst-btn" type="button">md</button>
<button class="inst-btn inst-btn--lg" type="button">lg</button>

Высота приходит из яруса ролей (--control-h-*), поэтому кнопка и стоящее рядом поле того же размера физически не могут разойтись. Плотность контейнера перенастраивает все три размера разом.

Размер Высота Отступ Кегль
inst-btn--sm --control-h-sm --control-pad-sm --text-xs
по умолчанию --control-h-md --control-pad-md --text-sm
inst-btn--lg --control-h-lg --control-pad-lg --text-md

Состояния

Пример
Разметка
<button class="inst-btn" type="button">Обычная</button>
<button class="inst-btn" type="button" disabled>Недоступна</button>
<button class="inst-btn inst-btn--primary" type="button" aria-busy="true">Запустить</button>
Состояние Как ставится Что происходит
наведение :hover:not(:disabled) Меняется только --btn-bg
нажатие :active:not(:disabled) --btn-bg + scale(0.985) — нажатие надо почувствовать, а не разглядывать
фокус :focus-visible Кольцо с отступом 1px, без собственного радиуса
недоступна disabled Прозрачность 0.5, а не серая заливка: кнопка сохраняет идентичность, и видно, какое именно действие недоступно
занята aria-busy="true" Подпись гаснет, на её месте кольцо. См. ниже

Занятость

Пример
Разметка
<button class="inst-btn inst-btn--primary" type="button" aria-busy="true">Запустить</button>
<button class="inst-btn" type="button" aria-busy="true">Отмена</button>
<button class="inst-btn inst-btn--icon" type="button" aria-busy="true" aria-label="Обновить">
  <svg class="inst-icon" aria-hidden="true"><use href="#i-refresh"/></svg>
</button>

Четыре решения, каждое из которых легко сделать наоборот и получить дефект:

  • Подпись не убирается из потока, а гасится цветом. Ширина кнопки остаётся прежней. Иначе полоса действий дёргается в тот момент, когда пользователь смотрит на курсор.
  • Подпись остаётся в разметке — её читает скринридер, и запасной aria-label не нужен.
  • Кнопка сохраняет фокус. disabled выбросил бы её из порядка обхода прямо под руками у того, кто пришёл с клавиатуры.
  • Кит снимает только мышь (pointer-events). Защита от повторного нажатия — в обработчике: это поведение, а не оформление.

С иконкой

Пример
Разметка
<button class="inst-btn" type="button">
  <svg class="inst-icon" aria-hidden="true"><use href="#i-refresh"/></svg>
  Обновить
</button>
<button class="inst-btn inst-btn--icon" type="button" aria-label="Обновить">
  <svg class="inst-icon" aria-hidden="true"><use href="#i-refresh"/></svg>
</button>

Иконочная кнопка квадратная. Прямоугольник 32×28 — самый частый признак самодельного кита. Ширина берётся из той же высоты контрола, поэтому квадрат сохраняется во всех трёх размерах и во всех плотностях.

Кнопка без текста обязана нести aria-label. Иконка при этом aria-hidden="true" — иначе скринридер прочитает её дважды или не прочитает вовсе.

Композиции

Группа

Пример
Разметка
<div class="inst-btn-group">
  <button class="inst-btn inst-btn--sm" type="button">Слева</button>
  <button class="inst-btn inst-btn--sm" type="button">Центр</button>
  <button class="inst-btn inst-btn--sm" type="button">Справа</button>
</div>

Несколько разных действий, поставленных вплотную. Не путать с сегментированным контролом: там один выбор среди равных, и у него всегда ровно одно выбранное значение.

Шов группы — снятая рамка у соседа, а не отрицательное поле в -0.5px: браузер никогда не рисует рамку тоньше физического пикселя, поэтому сдвиг на полпикселя оставлял видимое утолщение на каждом стыке.

Сценарии

Полоса действий формы

Главное действие последнее, отмена перед ним, обе прижаты к дальнему краю.

В контексте
Разметка
<form class="inst-panel">
  <div class="inst-panel-body">
    <div class="inst-field">
      <label class="inst-label" for="p-name">Имя прогона</label>
      <input class="inst-input" id="p-name" type="text" value="nightly-4127">
    </div>
    <div class="inst-form-actions">
      <button class="inst-btn" type="button">Отмена</button>
      <button class="inst-btn inst-btn--primary" type="submit">Сохранить</button>
    </div>
  </div>
</form>

Действия строки таблицы

Плотный ряд — ghost и sm: рамка у каждой кнопки в таблице превратила бы колонку в решётку. Разрушительное действие рамку сохраняет — это единственное в ряду, которое обязано быть заметно.

В контексте
Разметка
<div class="inst-cluster">
  <button class="inst-btn inst-btn--sm inst-btn--ghost" type="button">Открыть</button>
  <button class="inst-btn inst-btn--sm inst-btn--ghost" type="button">Повторить</button>
  <button class="inst-btn inst-btn--sm inst-btn--danger" type="button">Удалить</button>
</div>

Правила

Так Одно primary на экран

Главное действие ровно одно. Второе primary не добавляет важности первому — оно её отнимает.

Не так Кнопка вместо ссылки

Переход по адресу — это <a>. Кнопка ломает средний клик, «открыть в новой вкладке» и копирование адреса.

Так Занятость через aria-busy

Кнопка остаётся в порядке обхода, подпись читается скринридером, ширина не меняется.

Не так Занятость через disabled

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

Так Иконочная кнопка с aria-label

inst-btn--icon без доступного имени — кнопка, которую нечем назвать.

Не так Цвет как единственный признак

danger отличается не только тоном: подпись называет действие словом «Удалить», а не «ОК».

Доступность

Клавиатура Tab — фокус, Enter и Space — нажатие. Всё от нативного <button>, кит ничего не перехватывает
Фокус :focus-visible — кольцо появляется от клавиатуры и не появляется от мыши. В группе фокусная кнопка поднимается по z-index, чтобы кольцо не срезал сосед
Контраст Подпись каждого варианта проверена на 4.5:1 в шести темах. Заливка primary ограничена сверху по светлоте: светлее 0.56 белая подпись перестаёт держать порог
Занятость aria-busy озвучивается, подпись остаётся
Недоступность disabled — нативная. Прозрачность не роняет подпись ниже порога, потому что читается вместе с фоном
Цель нажатия md — 32px, sm — 26px. В плотности compact sm даёт 22px, что ниже 24px по WCAG 2.5.8: применяйте compact к плотным табличным видам, а не к основной навигации
Уменьшенное движение Кольцо занятости замедляется, но не останавливается: остановленный индикатор сообщает «ничего не происходит», когда происходит

Настройка

Свой вариант — две строки. Переопределяются переменные, а не правило.

.my-btn--accent {
  --btn-fg: var(--accent-on);
  --btn-bg: var(--accent-solid);
}

Два ограничения, которые проще нарушить, чем заметить:

  • Наведение меняет только --btn-bg. Не переопределяйте background напрямую — переменная перестанет читаться, и вариант потеряет ховер.
  • Заливка под белой подписью не может быть светлее 0.56 по OKLCH. Выше этого порога белый текст перестаёт держать 4.5:1. По той же причине --btn-bg-hover уходит от цвета подписи. Зеркальное «в тёмной теме светлее» роняет контраст подписи до 2.61.

API

ИмяЗначениеЧто делает
класс
inst-btnБазовый. На <button> или <a>. Обязателен всегда
inst-btn-groupКонтейнер группы: снимает рамку у соседа и скругляет только торцы
модификатор
inst-btn--primaryСплошная заливка акцентом. Одно главное действие на экран
inst-btn--ghostБез рамки. Для второстепенных действий в плотном ряду
inst-btn--dangerРазрушительное действие: тон ошибки в подписи и рамке
inst-btn--sm26pxВысота из --control-h-sm, кегль --text-xs
inst-btn--lg38pxВысота из --control-h-lg, кегль --text-md
inst-btn--iconКвадратная, только иконка. Требует aria-label
атрибут
typebutton · submitОбязателен. Без него кнопка внутри формы её отправит
aria-busytrueДействие пошло: подпись гаснет, на её месте крутится кольцо. Ширина не меняется
aria-labelОбязателен у inst-btn--icon: кнопка без текста не имеет доступного имени
disabledНедоступно. Для занятости не применяется — выбрасывает кнопку из обхода с клавиатуры
переменная
--btn-fg--text-primaryЦвет подписи
--btn-bgtransparentЗаливка. Наведение меняет только её
--btn-bg-hover--surface-hoverЗаливка под курсором
--btn-bg-active--surface-activeЗаливка при нажатии
--btn-border--border-controlРамка
токен
--control-h-md32pxВысота. Перенастраивается плотностью
--control-pad-md12pxГоризонтальный отступ
--radius-md7pxСкругление отдельно стоящего контрола
--text-sm13pxКегль подписи
--weight-medium500Начертание подписи
--size-spinner13pxКольцо занятости
--dur-180msДлительность перехода заливки

Почему кит устроен именно так — конституция · Открытый код под MIT, github.com/keshon/instrument