Графики
Кольцо
Та же доля, что у меры, но в габарите контрола: кольцо встаёт рядом с текстом карточки, где полосе на всю ширину места нет. 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>
Использование
<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-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; }
Когда использовать
| Используйте | Возьмите другое |
|---|---|
| Доля от целого в тесном месте: рядом с заголовком карточки, в строке | Доля во всю ширину блока — мера: полоса длиннее, значит точнее |
| Одна величина, у которой есть потолок | Несколько долей, складывающихся в целое — это не кольцо, а легенда с рядами: кольцо кита однодуговое |
| Число внутри кольца как главный носитель значения | Число само по себе — метрика: кольцо без величины у потолка ничего не добавляет |
| Статичное значение, приходящее из данных | Неизвестная длительность — мера с data-state="indeterminate": у кольца нет неопределённого вида |
Устройство
| Узел | Работа |
|---|---|
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-* дуге не положены: кольцо показывает одну
величину, а не ряд среди равных — см. палитру.
Композиции
В карточке
Разметка
<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 остаётся тем более — без подписи внутри он единственный
носитель значения для скринридера.
Правила
Так Размер кольца — снаружи
inline-size и block-size на элементе. Радиус внутри посчитан под длину
окружности и правке не подлежит.
Не так Другой радиус в viewBox
Пунктир дуги считается из длины окружности при r=8. Другой радиус — и дуга
перестанет соответствовать значению, молча.
Так aria-label предложением
«Израсходовано 74% бюджета», а не «74». Цифра без единицы и контекста ничего не сообщает.
Не так Категориальный тон у дуги
Кольцо показывает одну величину, а не ряд среди равных. --chart-* дуге не
положены.
Доступность
| Роль | role="img" — в примере, а не в примечании |
| Имя | aria-label предложением: «Израсходовано 74% бюджета», а не «74» |
| Цвет не единственный носитель | Значение продублировано числом в inst-ring-label либо в подписи рядом. Дуга без числа читается только зрячим и только на цвет |
| Контраст | Дуга держит 3:1 против дорожки. Дорожка не несущая, поэтому 3:1 к поверхности с неё не спрашивается |
| Кегль подписи | --text-2xs — 11px, пол кита. Кольцу меньше --control-h-lg подпись внутрь не помещается: выносите её наружу |
| Цифры | font-variant-numeric: tabular-nums — обновление значения не дёргает центровку |
API
| Имя | Значение | Что делает |
|---|---|---|
| класс | ||
inst-ring-wrap | — | Обёртка. Позиционирует подпись |
inst-ring | — | <svg>. Размер — --control-h-lg, поворот -90° |
inst-ring-track | — | Полная окружность дорожки |
inst-ring-fill | — | Дуга значения. Читает --value |
inst-ring-label | — | Подпись по центру |
| переменная | ||
--value | 0 | Доля 0…1. Ставится инлайном как данные |
--tone-mark | --accent-mark | Цвет дуги. Приходит из data-tone |
--track | — | Цвет дорожки |
| токен | ||
--control-h-lg | 38px | |
--accent-mark | light-dark(var(--a-4), var(--a-3)) | |
--text-2xs | 0.6875rem | |
--radius-full | 999px | |
Почему кит устроен именно так — конституция · Открытый код под MIT, github.com/keshon/instrument