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

Графики

Кольцо

Та же доля, что у меры, но в габарите контрола: кольцо встаёт рядом с текстом карточки, где полосе на всю ширину места нет. SVG, без JS.

Компонент
74
Разметка
<span class="inst-ring-wrap">
  <svg class="inst-ring" viewBox="0 0 20 20" role="img" aria-label="Израсходовано 74% бюджета">
    <circle class="inst-ring-track" cx="10" cy="10" r="8"/>
    <circle class="inst-ring-fill" cx="10" cy="10" r="8" style="--value:0.74"/>
  </svg>
  <span class="inst-ring-label">74</span>
</span>

Контракт

Что Обязательно Почему
role="img" на <svg> когда стоит отдельно Иначе у графики нет роли, и aria-label на ней не гарантирован. Внутри подписанного контрола кольцо получает aria-hidden="true", а имя несёт контрол: два имени на одном объекте читаются дважды
aria-label со значением словами да «74» внутри кольца — это цифра без единицы и без контекста
viewBox="0 0 20 20", cx="10" cy="10" r="8" да Пунктир дуги посчитан из длины окружности при r=8
--value в долях 0…1 да Не проценты и не градусы

viewBox="0 0 20 20" и r="8" не произвольны: длина окружности при r=8 равна 50.27, и пунктир дуги считается прямо из неё. Другой радиус — другая длина, и дуга перестанет соответствовать значению.

/* Кольцо крупнее — снаружи, а не радиусом внутри */
.my-ring { inline-size: 48px; block-size: 48px; }

Доступность

Роль role="img" — в примере, а не в примечании
Имя aria-label предложением: «Израсходовано 74% бюджета», а не «74»
Цвет не единственный носитель Значение продублировано числом в inst-ring-label либо в подписи рядом. Дуга без числа читается только зрячим и только на цвет
Контраст Дуга держит 3:1 против дорожки. Дорожка не несущая, поэтому 3:1 к поверхности с неё не спрашивается
Кегль подписи --text-2xs — 11px, нижний предел кегля. Кольцу меньше --control-h-lg подпись внутрь не помещается: выносите её наружу
Цифры font-variant-numeric: tabular-nums — обновление значения не дёргает центровку

Устройство

Узел Работа
inst-ring-wrap Позиционирующая обёртка. Только она умеет держать подпись по центру
inst-ring Сам <svg>. Повёрнут на -90°, поэтому дуга начинается сверху, а не справа
inst-ring-track Дорожка — полная окружность
inst-ring-fill Дуга значения
inst-ring-label Число по центру. Табличные цифры, чтобы не дёргалось при обновлении

Значение — доля 0…1 в --value. Ни проценты, ни градусы: из доли пунктир считается без пересчёта. style="--value:0.74" — инлайновый стиль как канал данных, а не оформление: значение приходит с сервера и не может жить в статическом CSS.

Пример
25 60 96
Разметка
<span class="inst-ring-wrap">
  <svg class="inst-ring" viewBox="0 0 20 20" role="img" aria-label="Готово 25%">
    <circle class="inst-ring-track" cx="10" cy="10" r="8"/>
    <circle class="inst-ring-fill" cx="10" cy="10" r="8" style="--value:0.25"/>
  </svg>
  <span class="inst-ring-label">25</span>
</span>
<span class="inst-ring-wrap">
  <svg class="inst-ring" viewBox="0 0 20 20" role="img" aria-label="Готово 60%">
    <circle class="inst-ring-track" cx="10" cy="10" r="8"/>
    <circle class="inst-ring-fill" cx="10" cy="10" r="8" style="--value:0.6"/>
  </svg>
  <span class="inst-ring-label">60</span>
</span>
<span class="inst-ring-wrap">
  <svg class="inst-ring" viewBox="0 0 20 20" role="img" aria-label="Готово 96%">
    <circle class="inst-ring-track" cx="10" cy="10" r="8"/>
    <circle class="inst-ring-fill" cx="10" cy="10" r="8" style="--value:0.96"/>
  </svg>
  <span class="inst-ring-label">96</span>
</span>

Варианты

Пример
62 88 100
Разметка
<span class="inst-ring-wrap">
  <svg class="inst-ring" viewBox="0 0 20 20" role="img" aria-label="Расход 62%">
    <circle class="inst-ring-track" cx="10" cy="10" r="8"/>
    <circle class="inst-ring-fill" cx="10" cy="10" r="8" style="--value:0.62"/>
  </svg>
  <span class="inst-ring-label">62</span>
</span>
<span class="inst-ring-wrap">
  <svg class="inst-ring" viewBox="0 0 20 20" role="img" aria-label="Расход 88%, у порога">
    <circle class="inst-ring-track" cx="10" cy="10" r="8"/>
    <circle class="inst-ring-fill" cx="10" cy="10" r="8" style="--value:0.88" data-tone="warn"/>
  </svg>
  <span class="inst-ring-label">88</span>
</span>
<span class="inst-ring-wrap">
  <svg class="inst-ring" viewBox="0 0 20 20" role="img" aria-label="Лимит исчерпан">
    <circle class="inst-ring-track" cx="10" cy="10" r="8"/>
    <circle class="inst-ring-fill" cx="10" cy="10" r="8" style="--value:1" data-tone="error"/>
  </svg>
  <span class="inst-ring-label">100</span>
</span>

Дуга читает --tone-mark и без атрибута берёт --accent-mark. Как и у меры, тон меняется вслед за смыслом величины. Категориальные тона --chart-* дуге не положены: кольцо показывает одну величину, а не ряд среди равных — см. палитру.

Размеры

Размер Бокс Обводка Где
по умолчанию --control-h-lg 3 Рядом с текстом карточки. Единственный, внутрь которого влезает подпись
inst-ring--sm --control-h-sm 2.5 В строке контролов, рядом с кнопкой
inst-ring--xs --icon-size 2 Там, где стоит глиф: конец строки сессии, рядом с моделью и усилием

Самая мелкая ступень — не высота контрола. Там кольцо не контрол среди контролов, а ещё один глиф в строке, поэтому оно читает --icon-size — ту же переменную, что читает каждая иконка в этой строке, — и двигается вместе со строкой. С --control-h-xs выходило 22px против глифов в 14 и читалось как третий объект между ними.

Обводка следует тому же доводу: иконка нарисована толщиной 1.5 на сетке 16, то есть 9.4%, а сетка кольца — 20, значит 2 даёт ту же оптическую массу.

Подпись вниз не идёт. --text-2xs — нижняя граница шкалы кегля, и внутрь 22px она не влезает, тем более внутрь 14. Ниже --control-h-lg число уходит рядом с кольцом или во всплывающую подсказку: дуга, рядом с которой нигде нет числа, читается только по длине и по цвету.

Пример
Контекст израсходован на 90%
Разметка
<span class="inst-ring-wrap">
  <svg class="inst-ring inst-ring--sm" viewBox="0 0 20 20" role="img" aria-label="Израсходовано 40% контекста">
    <circle class="inst-ring-track" cx="10" cy="10" r="8"/>
    <circle class="inst-ring-fill" cx="10" cy="10" r="8" style="--value:0.4"/>
  </svg>
</span>
<span class="inst-tooltip">
  <span class="inst-ring-wrap" aria-describedby="tt-ring-xs-ru">
    <svg class="inst-ring inst-ring--xs" viewBox="0 0 20 20" role="img" aria-label="Израсходовано 90% контекста">
      <circle class="inst-ring-track" cx="10" cy="10" r="8"/>
      <circle class="inst-ring-fill" cx="10" cy="10" r="8" style="--value:0.9"/>
    </svg>
  </span>
  <span class="inst-tooltip-text" role="tooltip" id="tt-ring-xs-ru">Контекст израсходован на 90%</span>
</span>

Композиции

В карточке

В контексте
74
Бюджет израсходован
184 320 из 250 000 токенов
Разметка
<div class="inst-card inst-cluster inst-cluster--loose">
  <span class="inst-ring-wrap">
    <svg class="inst-ring" viewBox="0 0 20 20" role="img" aria-label="Израсходовано 74% бюджета">
      <circle class="inst-ring-track" cx="10" cy="10" r="8"/>
      <circle class="inst-ring-fill" cx="10" cy="10" r="8" style="--value:0.74"/>
    </svg>
    <span class="inst-ring-label">74</span>
  </span>
  <div>
    <div class="inst-card-title">Бюджет израсходован</div>
    <div class="inst-card-sub">184 320 из 250 000 токенов</div>
  </div>
</div>

Без подписи внутри

Пример
Разметка
<span class="inst-ring-wrap">
  <svg class="inst-ring" viewBox="0 0 20 20" role="img" aria-label="Готово 40%">
    <circle class="inst-ring-track" cx="10" cy="10" r="8"/>
    <circle class="inst-ring-fill" cx="10" cy="10" r="8" style="--value:0.4"/>
  </svg>
</span>

Обёртка остаётся: она даёт inline-flex и снимает лишний зазор строки под SVG. aria-label остаётся тем более — без подписи внутри он единственный носитель значения для скринридера.

API

Классы
ИмяЧто делает
класс
inst-ring-wrapОбёртка. Позиционирует подпись
inst-ring<svg>. Размер — --control-h-lg, поворот -90°
inst-ring-trackПолная окружность дорожки
inst-ring-fillДуга значения. Читает --value
inst-ring-labelПодпись по центру
модификатор
inst-ring--smБокс из --control-h-sm, обводка 2.5. В строке контролов
inst-ring--xsБокс из --icon-size — там, где стоит глиф, а не контрол. Обводка 2, оптическая масса иконки
Токены
ИмяЗначениеОткуда
переменная
--ring-sizeБокс. Задаётся модификаторами размера; вызывающий может задать напрямую--control-h-lgзадаёт приложение
--ring-strokeОбводка, в единицах viewBox. Масштабируется вместе с боксом, поэтому снижается отдельно3задаёт приложение
--valueДоля 0…1. Ставится инлайном как данные0задаёт приложение
--tone-markЦвет дуги. Приходит из data-tone--accent-markзадаёт приложение
--trackЦвет дорожки—задаёт приложение
токен
--control-h-lg38pxбаза +14: плотность · масштаб
--accent-marklight-dark(var(--a-4), var(--a-3))одно на все ячейки
--text-2xs0.6875remбаза +4: масштаб
--radius-full999pxодно на все ячейки

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