Графики
Кольцо
Та же доля, что у меры, но в габарите контрола: кольцо встаёт рядом с текстом карточки, где полосе на всю ширину места нет. SVG, без JS.
Разметка
<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.
Разметка
<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>
Варианты
Разметка
<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 число уходит
рядом с кольцом или во всплывающую подсказку: дуга,
рядом с которой нигде нет числа, читается только по длине и по цвету.
Разметка
<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>
Композиции
В карточке
Разметка
<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-lg | 38px | база +14: плотность · масштаб |
--accent-mark | light-dark(var(--a-4), var(--a-3)) | одно на все ячейки |
--text-2xs | 0.6875rem | база +4: масштаб |
--radius-full | 999px | одно на все ячейки |
Почему кит устроен именно так — конституция · Открытый код под MIT, github.com/keshon/instrument