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

Основания

Иконки

Inline SVG на сетке 16, обводка currentColor толщиной 1.5. Иконка наследует цвет текста, поэтому в библиотеке нет ни одного иконочного варианта тона.

Компонент
Разметка
<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-settings"/></svg>
</button>

Контракт

Пять требований. Нарушение любого видно сразу — иконка либо съезжает по базовой линии, либо перестаёт менять цвет вместе с текстом.

Требование Почему
Inline SVG (или <use> из спрайта) Иконка обязана менять цвет вместе с текстом. <img> и background-image этого не умеют
stroke: currentColor, fill: none Один файл работает во всех пяти темах и во всех тонах без единого варианта
Толщина обводки 1.5 Общая с хайрлайном оптика: 1 читается как царапина, 2 — как жирный шрифт рядом с 400
Сетка 16 (viewBox="0 0 16 16") Совпадает с --size-icon. Другая сетка даёт другую оптическую толщину при том же stroke-width
Цвет не живёт в data-URI Форма рисуется маской, цвет приходит токеном. Иначе тема иконку не догоняет

Класс .inst-icon объявляет это плюс flex: none — иначе иконка сжимается первой, когда подпись рядом не помещается.

.inst-icon {
  flex: none;
  inline-size: var(--icon-size, var(--size-icon));
  block-size: var(--icon-size, var(--size-icon));
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

Разметка

Случай Разметка
Иконка рядом с подписью aria-hidden="true" на <svg>. Имя даёт подпись; без атрибута скринридер прочитает элемент дважды или невнятно
Иконка вместо подписи aria-hidden="true" на <svg> и aria-label на кнопке. Кнопка без текста иначе не имеет доступного имени
Иконка внутри ссылки То же самое: имя несёт ссылка
Иконка как единственный носитель информации Так не бывает. Добавьте подпись или aria-label — цвет и форма не имеют права быть единственным носителем

Доступность

Декоративная иконка aria-hidden="true" всегда, без исключений: имя даёт подпись или aria-label
Кнопка без текста Обязана нести aria-label. Иначе у неё нет доступного имени вовсе
Контраст Иконка наследует цвет текста, поэтому проходит те же пороги. Иконка на --text-faint — декорация и не имеет права нести смысл
Цвет не единственный носитель Иконка сама по себе второй носитель тона (сноска), но не первый: тон обязан быть ещё и в тексте
Режим принудительных цветов Обводка currentColor переживает сброс, потому что цвет берётся от текста, а не задан своим значением
Фокус На <svg> фокуса не бывает: фокусируется кнопка или ссылка вокруг него, focusable="false" у спрайта убирает старый IE-артефакт

Устройство

Спрайт объявляется один раз на документ, до первого <use>. Сам он невидим и скрыт от скринридера.

<svg class="inst-sprite" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg">
  <symbol id="i-refresh" viewBox="0 0 16 16">…</symbol>
</svg>

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

<svg class="inst-icon" aria-hidden="true"><use href="#i-refresh"/></svg>

У <svg> в месте использования нет собственного viewBox: он приходит из <symbol>. Собственный viewBox на внешнем элементе перебивает символьный и обрезает часть глифов.

Доступные id

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

Плюс 103 символов страниц справочника (i-p-*) — они принадлежат сайту, а не киту.

Размеры

Класс или переменная Размер Токен Где
inst-icon 18px --size-icon Умолчание: кнопка, пункт меню, сноска, шапка
inst-icon--sm 10px --size-chevron Шеврон селекта, крестик тега — служебный глиф
inst-icon--down — поворот 90deg Единственный шеврон, повёрнутый: аффорданс выпадающего списка на кнопке

Какой размер берёт шеврон

--size-chevron — это 10px против 18 у --size-icon, и одно другим не заменяется: токен отвечает на три разные задачи, и только две из них его собственные.

Задача Размер Почему Где
Разделитель между словами --size-chevron Стоит внутри строки текста и меряется по кеглю Хлебные крошки, путь каскадера
Фурнитура рамки — раскрытие того, на чём стоит --size-chevron Меряется по подписи рядом. Полоса 0.62…0.86 кегля: крупнее — и стрелка спорит со словами Стрелка селекта, твист аккордеона, твист дерева, стрелка сортировки
Глиф в колонке хвостовых глифов --icon-size Меряется по фактическому соседу, а сосед — другой глиф, а не подпись Стрелка ветки каскадера: она стоит у того же края, что галочка меню

Третья строка — та, где ошибиться легче всего. .inst-cascader-item носится вместе с .inst-menu-item, у которого отметка выбора — --size-icon; десять против восемнадцати в одной колонке читаются как два словаря, а не как один список.

Шеврон, который сам является целью нажатия, — отдельный вопрос, и ответ на него не мельче глиф, а крупнее бокс. .inst-tab-close это и формулирует: квадрат из яруса контролов, а служебный глиф внутри, — потому что крестик в 10px это цель в 10px и заливка ховера размером со знак. Там, где цель настоящая, ставится настоящая inst-btn--xs: псевдоэлемент, нарисованный под кнопку, — обещание, которого разметка не держит.

| --icon-size: var(--size-icon-sm) | 14px | --size-icon-sm | Показание в строке состояния: там кегль --text-2xs | | --icon-size: var(--size-icon-lg) | 24px | --size-icon-lg | Значок в рельсе, где подписи нет вовсе |

Все четыре — одна мера с разными опорами. Значок рядом с подписью меряется против ЕЁ прописной, поэтому у одиннадцатого кегля значок свой; значок без подписи меряется против цели нажатия, потому что сравнивать не с чем. Проверяет cmd/proportion, полосы там же.

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

Размеры не реагируют на плотность намеренно — см. плотность: иконка носитель значения, и на 12px обводка 1.5 превращается в кляксу.

Композиции

<div class="inst-menu" role="menu">
  <button class="inst-menu-item" type="button" role="menuitem">
    <svg class="inst-icon" aria-hidden="true"><use href="#i-refresh"/></svg>Перезапустить
  </button>
  <button class="inst-menu-item" type="button" role="menuitem">
    <svg class="inst-icon" aria-hidden="true"><use href="#i-copy"/></svg>Скопировать id
  </button>
</div>

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

Правила

Что брать, а что нет

Используйте Возьмите другое
Иконка рядом с подписью — второй носитель смысла Иконка вместо подписи в списке действий — в меню подпись обязательна, см. меню в поповере
Иконка вместо подписи там, где действие общеизвестно и есть aria-label Иконка вместо статуса — статус несёт точку и слово, см. бейдж
Иконка как второй признак тона у сноски Цвет как единственный носитель — общее правило. Тон описан в цвете
<use href="#i-*"> из спрайта Текстовый глиф ⟳ или ▶ — разные метрики в разных шрифтах и риск подстановки эмодзи
.inst-icon--sm в шевроне и мелком контроле Своя ширина инлайном — размер приходит из ролей
--icon-size на контейнере, когда компоненту нужен свой размер Селектор потомка .мой-блок > .inst-icon — если ваш слой раньше kit.components, правило не сработает и не пожалуется

Настройка

  1. Нарисуйте на сетке 16×16, только обводкой.
  2. Уберите fill, stroke, stroke-width из самих путей — они придут из .inst-icon.
  3. Скруглите концы и стыки не в файле, а классом: stroke-linecap и stroke-linejoin уже там.
  4. Положите в спрайт как <symbol id="i-…" viewBox="0 0 16 16">.
  5. Проверьте на тёмной теме: обводка на 1.5 в тёмной теме оптически тоньше, и слишком мелкая деталь исчезает.

API

Классы
ИмяЧто делает
класс
inst-spriteСпрайт в теле страницы. Прячет его: без правила это видимый блок в 150px
inst-iconБазовый. Обязателен всегда
модификатор
inst-icon--smУменьшение до --size-chevron
inst-icon--downТот же шеврон, повёрнутый вниз: аффорданс выпадающего списка. По часовой от inline-end, поэтому в RTL он тоже смотрит вниз
Токены
ИмяЗначениеОткуда
переменная
--icon-sizeРазмер этого значка. Ставится на КОНТЕЙНЕР и наследуется вниз. Через переменную, а не через селектор потомка: раскладка лежит в более раннем слое, и селектор оттуда мёртв молчаvar(--size-icon-xs)задаёт приложение
токен
--size-iconУмолчание: значок рядом с подписью базового кегля18pxбаза +4: масштаб
--size-icon-smЗначок при кегле --text-2xs: строка состояния14pxбаза +4: масштаб
--size-icon-lgЗначок БЕЗ подписи: рельс разделов. Меряется против цели нажатия24pxбаза +4: масштаб
--size-chevronСлужебный глиф: шеврон, крестик10pxбаза +4: масштаб
--stroke1pxодно на все ячейки

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