Перейти к содержимому
EN
Масштаб
Плотность
Тема
Акцент

Действия

Кнопка

Действие, которое пользователь запускает сам. По умолчанию кнопка тихая: экран, где всё 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--soft" type="button">Фильтр</button>
<button class="inst-btn inst-btn--ghost" type="button">Подробнее</button>
<button class="inst-btn inst-btn--danger" type="button">Удалить</button>

Контракт

Что Обязательно Почему
.inst-btn да Базовый класс. На <button> или на <a>
type у <button> да Кнопка без него внутри формы имеет type="submit" по умолчанию и отправляет форму — включая ту, которая всего лишь показывала подсказку
<a> для перехода по адресу да Кнопка, ведущая на другую страницу, ломает средний клик, «открыть в новой вкладке» и копирование адреса. Класс на ссылке даёт ровно ту же кнопку
aria-busy="true" на время работы да, если кнопка ждёт Озвучивается, подпись при этом остаётся на месте
disabled нативный Прозрачность не роняет подпись ниже порога: она читается вместе с фоном
Бегущий tabindex нет Tab, Enter и Space целиком от нативной <button>; instrument.js здесь не перехватывает ничего
aria-label на inst-btn--icon да Кнопка без доступного имени — кнопка, которую нечем назвать
<button>, а не <input> ради вида да Подменять кнопку полем — ложь скринридеру про то, что здесь можно печатать, и сломанная клавиатура: поле не открывается пробелом

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

Подпись каждого варианта проверена на 4.5:1 в пяти темах, а заливка primary ограничена сверху по светлоте, и предел хью-зависимый: 0.545 у петроли, стоящей умолчанием. Цель нажатия — 32px у md и 24px у sm; в плотности compact sm даёт 22px, что ниже 24px по WCAG 2.5.8, и потому compact применяют к плотным табличным видам, а не к основной навигации. Кольцо занятости при prefers-reduced-motion замедляется, но не останавливается: остановленный индикатор сообщает «ничего не происходит», когда происходит.

Варианты

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

Пример
Разметка
<button class="inst-btn" type="button">По умолчанию</button>
<button class="inst-btn inst-btn--primary" type="button">Primary</button>
<button class="inst-btn inst-btn--soft" type="button">Soft</button>
<button class="inst-btn inst-btn--outline" type="button">Outline</button>
<button class="inst-btn inst-btn--ghost" type="button">Ghost</button>
<button class="inst-btn inst-btn--danger" type="button">Danger</button>
Вариант Когда
без модификатора Умолчание — утопленная: провал по светлоте, ни рамки, ни тени. Глубина считается от подложки, поэтому шаг одинаков на странице, в панели и в модалке
inst-btn--primary Главное действие экрана. Одно на экран
inst-btn--soft Контрол внутри группы: приглушённая заливка, рамки нет
inst-btn--outline Обводка: тихая рамка, заливки нет. Действие, которое обязано выглядеть нажимаемым, не претендуя на внимание
inst-btn--ghost Третьестепенное: ни рамки, ни заливки — только текст
inst-btn--danger Необратимое или разрушительное действие. Тон, а не вес: ставится поверх любого из четырёх и красит подпись, не добавляя рамки
data-tone Тон подписи из общего словаря: running — акцентное действие без хрома, ok, warn, error, neutral. Ставится поверх любого веса, кроме --primary

Лестница весов

Вес отвечает на «насколько громко кнопка просит нажать себя», и ответ измерим: присутствие — это площадь, взвешенная шагом светлоты к земле.

Вес светлая тёмная
--ghost 0 0
--outline 0.007 0.007
--soft 0.017 0.022
умолчание 0.046 0.048
--primary 0.47 0.17

Монотонна во всех пяти темах, соседние ступени расходятся не меньше чем на 0.022 — это проверяет cmd/contrast парой «лестница: soft против default». Рамка у outline декоративная, а не несущая.

Тон

Тон — общий словарь кита, тот же, что у бейджа, точки, меры и баннера. Кнопка читает из него --tone-ink для подписи и --tone-bg для наведения.

Пример
Разметка
<button class="inst-btn inst-btn--ghost" type="button" data-tone="running">Добавить контекст</button>
<button class="inst-btn" type="button" data-tone="ok">Принять</button>
<button class="inst-btn" type="button" data-tone="error">Удалить</button>
<button class="inst-btn inst-btn--danger" type="button">Удалить</button>

Последние две строки дают одно и то же: inst-btn--danger — это тот же тон error, записанный классом. Класс остался, потому что разрушительное действие встречается часто и заслуживает короткого написания.

Призрачная с тоном running — то самое действие-ссылка: подпись акцентом, ни рамки, ни заливки. Ею набирают «добавить контекст», «добавить строку», «создать первый объект» — то, что приглашает нажать, но не спорит с главной кнопкой экрана.

Что Почему
Тон ставится на саму кнопку --tone-ink наследуется, и правило «просто читать переменную» покрасило бы кнопку внутри аварийного баннера само собой. Тон объявляется, а не подхватывается
--primary тон не принимает Подпись там стоит на заливке акцентом и держит 4.5:1 только белым. Красный или зелёный на этой заливке даёт 1.5–2.0, поэтому сочетание обезврежено явно
Тон не заменяет вес Вес отвечает, насколько громко кнопка просит нажать себя; тон — что за действие. Вопросы разные, поэтому и оси разные

Переключатель значения

Есть контрол, у которого главное — значение, а подпись лишь говорит, чего это значение: текущий репозиторий, ветка, окружение, учётная запись. «master» сам по себе не значит ничего, и убрать «Current branch» нельзя.

В контексте
Разметка
<button class="inst-btn inst-btn--stacked" type="button" style="inline-size:15rem">
  <svg class="inst-icon" aria-hidden="true"><use href="#i-book"/></svg>
  <span class="inst-btn-stack">
    <span class="inst-btn-label">Текущий репозиторий</span>
    <span class="inst-btn-value">instrument-ui-kit</span>
  </span>
  <svg class="inst-icon inst-icon--sm" aria-hidden="true"><use href="#i-chevron"/></svg>
</button>

Колонкой становится текст, поэтому .inst-btn-stack обязателен: у такого контрола есть ведущая иконка и шеврон, и колонка из всей кнопки положила бы их сверху и снизу. Две строки — свойство этого варианта, а не разрешение переносить любую кнопку.

Кнопка в одежде поля

В контексте
Разметка
<button class="inst-btn inst-btn--field" type="button" style="inline-size:18rem">
  <svg class="inst-icon" aria-hidden="true"><use href="#i-list"/></svg>
  Выберите файл или команду
</button>

Открывает палитру или список, но остаётся кнопкой. Вид берётся у вреза, а поведение — кнопочное: рамка здесь несущая, потому что она и есть контрол, внутри одна подсказка.

Ссылкой

Класс на <a> даёт ровно ту же кнопку, без подчёркивания.

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

Размеры

Пример
Разметка
<button class="inst-btn inst-btn--xs" type="button">xs</button>
<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--xs --control-h-xs --space-3 --text-2xs
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

xs стоит особняком: бокс у неё от бейджа, а не из яруса контролов, — ниже sm соседом в строке оказывается чип, а не кнопка. Она для контрола, который принадлежит чему-то другому: значки под ответом, настройки сессии под полем ввода. Цель нажатия остаётся --tap-min через псевдоэлемент — рисунок уменьшается, цель нет. Отсюда требование к строке: зазор не меньше --space-3. При compact бокс равен 18px, и на меньшем зазоре увеличенные цели перекрываются. Главная кнопка, стоящая сама по себе, остаётся sm.

Состояния

Пример
Разметка
<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>

Подпись не убирается из потока, а гасится цветом: ширина держится, и скринридер её по-прежнему читает. Фокус кнопка сохраняет — disabled выбросил бы её из обхода прямо под руками у того, кто пришёл с клавиатуры, — а снимается только мышь: защита от второго нажатия принадлежит обработчику.

С иконкой

Пример
Разметка
<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>

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

Кнопка без текста обязана нести 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>

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

Сценарии

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

Главное действие стоит у внешнего края полосы. Здесь полоса прижата к концу строки, поэтому главное идёт последним, а отмена перед ним; при выравнивании к началу порядок обратный — см. форму.

В контексте
Разметка
<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 inst-form-actions--end">
      <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>

Настройка

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

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

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

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

API

Классы
ИмяЧто делает
класс
inst-btnБазовый. На <button> или <a>. Обязателен всегда
inst-btn-groupКонтейнер группы: снимает рамку у соседа и скругляет только торцы
inst-btn-stackСтолбик текста внутри --stacked. Обязателен: колонкой становится текст, а не кнопка, иначе значок уедет над подписью
inst-btn-labelПодпись внутри .inst-btn-stack. Тише и мельче значения
inst-btn-valueЗначение внутри .inst-btn-stack. Длинное обрезается: это идентификатор, переносить его нельзя
inst-btn--outlineОбводка: тихая рамка, заливки нет. Ступень между призрачной и мягкой
модификатор
inst-btn--primaryСплошная заливка акцентом. Одно главное действие на экран
inst-btn--softПриглушённая заливка, рамки нет. Контрол внутри группы: тулбар, ряд значков, чип выбора
inst-btn--stackedДве строки: тихая подпись сверху, значение снизу. Для контрола, у которого главное — значение (репозиторий, ветка, окружение)
inst-btn--fieldВид вреза, поведение кнопки. Открывает палитру или список; подменять настоящим <input> нельзя — это ложь скринридеру
inst-btn--ghostБез рамки и без заливки. Для второстепенных действий в плотном ряду
inst-btn--dangerРазрушительное действие: тон ошибки в подписи. Это ТОН поверх веса, а не пятый вес — сочетается с любым
inst-btn--xsБокс бейджа: --control-h-xs, --space-3, --radius-xs. Для контрола, принадлежащего чему-то другому. Цель нажатия остаётся --tap-min через ::before
inst-btn--smВысота из --control-h-sm, кегль --text-xs
inst-btn--lgВысота из --control-h-lg, кегль --text-md
inst-btn--iconКвадратная, только иконка. Требует aria-label
Атрибуты
ИмяЗначениеЧто делает
атрибут
typebutton · submitОбязателен. Без него кнопка внутри формы её отправит
aria-busytrueДействие пошло: подпись гаснет, на её месте крутится кольцо. Ширина не меняется
aria-label—Обязателен у inst-btn--icon: кнопка без текста не имеет доступного имени
disabled—Недоступно. Для занятости не применяется — выбрасывает кнопку из обхода с клавиатуры
data-tonerunning · ok · warn · error · neutralТон подписи из общего словаря кита. Ставится на саму кнопку
Токены
ИмяЗначениеОткуда
переменная
--btn-fgЦвет подписи--text-primaryзадаёт приложение
--btn-bgЗаливка. Наведение меняет только её--surface-raisedзадаёт приложение
--btn-bg-hoverЗаливка под курсором--surface-hoverзадаёт приложение
--btn-bg-activeЗаливка при нажатии--surface-activeзадаёт приложение
--btn-borderРамка. Волосяная: опознаёт кнопку подпись, а не контур--borderзадаёт приложение
--btn-shadowУ умолчания тени нет — глубину несёт провал. Ставится у --primary: там она вместе с кромкой даёт толщину сплошной заливкеnoneзадаёт приложение
токен
--control-h-mdВысота. Перенастраивается плотностью32pxбаза +14: плотность · масштаб
--control-pad-mdГоризонтальный отступ. Выводится из высоты через --control-ratio-md12pxодно на все ячейки
--radius-mdСкругление отдельно стоящего контрола8pxбаза +4: масштаб
--shadow-controlТень в один пиксель. Не «плавает сверху», а «имеет толщину»0 1px 2px -1px var(--shadow-color-far), 0 1px 1px -1px var(--shadow-color-near)одно на все ячейки
--gap-inlineПросвет между значком и подписью8pxбаза +14: плотность · масштаб
--text-mutedЦвет значка: он тише подписи, которую сопровождаетlight-dark(var(--n-8), var(--n-6))одно на все ячейки
--text-smКегль подписи14pxбаза +4: масштаб
--weight-mediumНачертание подписи600одно на все ячейки
--size-spinnerКольцо занятости14pxбаза +14: плотность · масштаб
--dur-1Длительность перехода заливки80msодно на все ячейки

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