Перейти к содержимому
Плотность
Тема

Графики

Кольцо

Та же доля, что у меры, но в габарите контрола: кольцо встаёт рядом с текстом карточки, где полосе на всю ширину места нет. 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>

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

<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.

Пример
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-* дуге не положены: кольцо показывает одну величину, а не ряд среди равных — см. палитру.

Композиции

В карточке

В контексте
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 остаётся тем более — без подписи внутри он единственный носитель значения для скринридера.

Правила

Так Размер кольца — снаружи

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Подпись по центру
переменная
--value0Доля 0…1. Ставится инлайном как данные
--tone-mark--accent-markЦвет дуги. Приходит из data-tone
--trackЦвет дорожки
токен
--control-h-lg38px
--accent-marklight-dark(var(--a-4), var(--a-3))
--text-2xs0.6875rem
--radius-full999px

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