Навигация
Тулбар
Полоса инструментов внутри панели: режимы, фильтры, мелкие действия над её содержимым. Не шапка экрана и не меню.
Разметка
<div class="inst-panel">
<div class="inst-panel-header"><span class="inst-panel-title">Инспектор</span></div>
<div class="inst-toolbar">
<button class="inst-btn inst-btn--sm inst-btn--ghost inst-btn--icon" type="button" aria-label="Переместить">
<svg class="inst-icon" aria-hidden="true"><use href="#i-move"/></svg></button>
<button class="inst-btn inst-btn--sm inst-btn--ghost inst-btn--icon" type="button" aria-label="Повернуть">
<svg class="inst-icon" aria-hidden="true"><use href="#i-rotate"/></svg></button>
<button class="inst-btn inst-btn--sm inst-btn--ghost inst-btn--icon" type="button" aria-label="Масштаб">
<svg class="inst-icon" aria-hidden="true"><use href="#i-scale"/></svg></button>
<span class="inst-toolbar-sep"></span>
<span class="inst-toolbar-spacer"></span>
<span class="inst-badge">terrain_chunk_04</span>
</div>
<div class="inst-panel-body">…</div>
</div>
Использование
<div class="inst-toolbar">
<button class="inst-btn inst-btn--sm inst-btn--ghost inst-btn--icon" type="button"
aria-label="Переместить" aria-pressed="true">
<svg class="inst-icon" aria-hidden="true"><use href="#i-move"/></svg></button>
<span class="inst-toolbar-sep"></span>
<span class="inst-toolbar-spacer"></span>
<span class="inst-badge">terrain_chunk_04</span>
</div>
| Что | Обязательно | Почему |
|---|---|---|
type="button" у каждой кнопки |
да | Иначе внутри формы она её отправит |
aria-label у иконочной кнопки |
да | Кнопка без текста не имеет доступного имени: «Переместить», а не «Инструмент 1» |
aria-hidden="true" у иконки |
да | Иначе скринридер прочитает её дважды или не прочитает вовсе |
aria-pressed у кнопки-режима |
да, если инструмент остаётся включённым | Состояние обязано быть в разметке. Ставит приложение |
Роль toolbar кит не назначает: она обещает стрелочную навигацию и бегущий
tabindex, а это поведение — слой приложения. Полоса из обычных кнопок без
роли доступна и без обещаний.
Когда использовать
| Используйте | Возьмите другое |
|---|---|
| Инструменты, действующие на содержимое этой панели | Действия над экраном целиком — шапка экрана и её inst-page-actions |
| Постоянно видимая полоса: режим переключают часто | Редкие действия по клику — меню: полоса из десяти иконок дороже одной кнопки «ещё» |
| Иконочные кнопки и мелкие контролы в один ряд | Просто ряд кнопок вне панели — кластер: у тулбара есть рамка и место в поверхности |
| Пара действий у заголовка секции | Заголовок секции — inst-section-actions, секция |
Устройство
Два служебных элемента, которые легко перепутать:
| Элемент | Что делает | Когда |
|---|---|---|
inst-toolbar-sep |
Вертикальная линия на всю высоту полосы | Между смысловыми группами инструментов: трансформации отдельно, отображение отдельно |
inst-toolbar-spacer |
margin-inline-start: auto — всё после него уезжает к дальнему краю |
Один раз на полосу, перед хвостом: статусом, счётчиком, кнопкой закрытия |
Разделитель — пустой <span>, то есть чистая графика без текста и без роли.
Группировку он показывает глазу; для скринридера её несёт порядок и подписи
кнопок.
Композиции
Место в панели
Тулбар стоит между шапкой и телом панели и приносит нижнюю линию сам. Шапка
при этом свою линию снимает (:has(+ .inst-toolbar)) — иначе на тридцати
пикселях оказалось бы два правила подряд.
Разметка
<div class="inst-panel">
<div class="inst-panel-header"><span class="inst-panel-title">Модули</span></div>
<div class="inst-toolbar">
<div class="inst-segmented" role="radiogroup" aria-label="Вид">
<button type="button" role="radio" aria-checked="true">Список</button>
<button type="button" role="radio" aria-checked="false">Сетка</button>
</div>
<span class="inst-toolbar-sep"></span>
<span class="inst-search">
<input class="inst-input inst-input--sm" type="search" placeholder="Фильтр" aria-label="Фильтр по модулям">
</span>
<span class="inst-toolbar-spacer"></span>
<span class="inst-badge">3 из 12</span>
</div>
<div class="inst-panel-body">Содержимое области.</div>
</div>
Тулбар без шапки тоже законен: панель, у которой нет названия, но есть режимы.
Правила
Так Тулбар про содержимое своей панели
Режимы и фильтры действуют на то, что лежит в этой области, — потому полоса и стоит внутри неё.
Не так Тулбар для действий над экраном
Действия уровня экрана живут в шапке экрана, а не внутри одной из панелей.
Так Одна распорка на полосу
inst-toolbar-spacer отбивает хвост — статус, счётчик, закрытие. Двух точек
разрыва в одной полосе не бывает.
Не так role="toolbar" без бегущего tabindex
Роль обещает стрелочную навигацию. Полоса из обычных кнопок доступна и без обещаний.
Доступность
| Клавиатура | Каждая кнопка обходится Tab. Стрелочная навигация внутри полосы — только вместе с role="toolbar" и бегущим tabindex, то есть с JS приложения |
| Имена | Иконочные кнопки обязаны нести aria-label: «Переместить», а не «Инструмент 1» |
| Цель нажатия | inst-btn--sm — 26px. В плотности compact — 22px, ниже порога 24px по WCAG 2.5.8 |
| Разделитель | Пустой элемент без текста и роли — в дереве доступности его нет, и это правильно: он не сообщает ничего сверх порядка |
| Контраст | Линии --border-subtle и --border — декорация (порог 3:1), а не текст |
| Печать | Тулбар не печатается: это хром приложения, а не данные |
API
| Имя | Значение | Что делает |
|---|---|---|
| класс | ||
inst-toolbar | — | Полоса. Ряд с зазором --space-3 и нижней линией |
inst-toolbar-sep | — | Вертикальный разделитель групп |
inst-toolbar-spacer | — | Прижать хвост полосы к дальнему краю |
| токен | ||
--space-2 | 4px | |
--space-3 | 6px | |
--pad-cell-x | var(--space-4) | |
--hairline | 1px | |
--border-subtle | light-dark(oklch(0 0 0 / 0.07), oklch(1 0 0 / 0.06)) | |
--border | light-dark(oklch(0 0 0 / 0.12), oklch(1 0 0 / 0.11)) | |
Почему кит устроен именно так — конституция · Открытый код под MIT, github.com/keshon/instrument