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

Ввод

Текстовое поле

Однострочный и многострочный ввод. Поле стоит во врезе (--surface-field), а не на цвете панели: иначе его единственной границей остаётся рамка, и поле приходится искать.

Компонент
Латиница, цифры и дефис
Разметка
<div class="inst-field">
  <label class="inst-label" for="agent">Имя агента</label>
  <input class="inst-input" id="agent" placeholder="worldgen-01">
  <span class="inst-field-hint">Латиница, цифры и дефис</span>
</div>

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

Поле, подпись и подсказка живут в одной обёртке inst-field.

<div class="inst-field">
  <label class="inst-label" for="agent">Имя агента</label>
  <input class="inst-input" id="agent" placeholder="worldgen-01">
  <span class="inst-field-hint">Латиница, цифры и дефис</span>
</div>
Что Обязательно Почему
<label for> или aria-label да placeholder подписью не является: он исчезает при вводе и не читается частью скринридеров
inst-input на поле да Врез, рамка, высота и все состояния приходят оттуда
inst-field на обёртке нет Нужна там, где есть подпись или подсказка. Голое поле в тулбаре обходится без неё
aria-describedby на поле нет, но обычно да Связывает подсказку с полем: без него она видна глазом и не читается скринридером

Многострочное поле — тот же контракт, класс inst-textarea.

Когда использовать

Используйте Возьмите другое
Произвольный текст: имя, путь, запрос Выбор из набораселект или сегментированный контрол
Число с единицей и осью — inst-num-field Число, которое тянут мышьючисловое поле инспектора
Многострочный текст — inst-textarea Поиск с очисткойпоиск: input[type=search] даёт нативный крестик
Значение с приставкой: валюта, единица Значение только для чтенияreadonly, а не disabled: см. ниже

Размеры

Симметричны кнопочным, потому что поле и кнопка стоят в одной строке формы.

Пример
Разметка
<input class="inst-input inst-input--sm" value="sm" aria-label="Поле sm">
<input class="inst-input" value="md" aria-label="Поле md">
<input class="inst-input inst-input--lg" value="lg" aria-label="Поле lg">
Размер Высота Кегль
inst-input--sm --control-h-sm --text-xs
по умолчанию --control-h-md --text-sm
inst-input--lg --control-h-lg --text-md

Горизонтальный отступ не меняется: у поля текст прижат к краю, а не отцентрован, поэтому отступу незачем расти вместе с высотой.

У inst-textarea те же три размера, высота — два с половиной контрола.

Состояния

Пример
Разметка
<input class="inst-input" value="Обычное" aria-label="Обычное">
<input class="inst-input" value="Только чтение" readonly aria-label="Только чтение">
<input class="inst-input" value="Недоступно" disabled aria-label="Недоступно">
<input class="inst-input" value="Ошибка" aria-invalid="true" aria-label="Ошибка">
Состояние Как ставится Что происходит
наведение :hover Рамка темнеет. Уступает ошибке явно, см. ниже
фокус :focus-visible Кольцо плюс рамка акцентом
ошибка :user-invalid или aria-invalid="true" Красная рамка. Первое ставит платформа, второе — приложение
только чтение readonly Врез снимается, цвет текста полный: значение можно прочитать, выделить и скопировать
недоступно disabled Прозрачность 0.5 — как у кнопки

readonly — не disabled

Это разные вещи, и пока они выглядели одинаково, пользователь не мог понять, ждать ему разблокировки или значение неизменяемо в принципе.

readonly disabled
Можно прочитать и скопировать да нет
Попадает в фокус да нет
Уходит с формой на сервер да нет
Как выглядит врез снят, текст полный прозрачность 0.5

Поведение

Валидация

Ошибку показывает платформа.

Пример
Пришлём отчёт о прогоне Проверьте адрес
Разметка
<div class="inst-field">
  <label class="inst-label" for="mail">Почта<span class="inst-required">*</span></label>
  <input class="inst-input" id="mail" type="email" required
         aria-describedby="mail-msg">
  <span class="inst-field-hint" id="mail-msg">Пришлём отчёт о прогоне</span>
  <span class="inst-field-error" data-when="invalid">Проверьте адрес</span>
</div>

:user-invalid, а не :invalid. Разница принципиальная: :invalid горит красным с первой секунды, ещё до того, как в поле что-то ввели, — форма встречает пользователя россыпью ошибок, которых он не совершал. :user-invalid срабатывает только после взаимодействия.

Подсказка и ошибка занимают один слот. Ошибка заменяет подсказку, а не сдвигает форму: разметка не перекладывается в момент заполнения.

Кто показывает Как
Платформа data-when="invalid" на inst-field-error — покажется по :user-invalid
Приложение тот же блок без атрибута — например, по ответу сервера

Композиции

Приставка и суффикс

Пример
в час
Разметка
<div class="inst-input-group">
  <span class="inst-input-affix">₽</span>
  <input class="inst-input" aria-label="Бюджет">
  <span class="inst-input-affix">в час</span>
</div>

Подпись сбоку

Пример
Разметка
<div class="inst-field inst-field--side">
  <label class="inst-label" for="t">Таймаут</label>
  <input class="inst-input" id="t" value="30">
</div>

Колонка подписей — тот же --label-col, что у инспектора: два способа показать пару «имя — значение» не имеют права разъезжаться по метрике.

Правила

Так Видимая подпись через label for

Она читается и глазом, и скринридером, и не исчезает при вводе.

Не так Placeholder вместо подписи

Он пропадает тогда, когда нужен, — во время заполнения, — и частью скринридеров не читается вовсе.

Так readonly для неизменяемого значения

Значение читается, копируется и уходит с формой. Видно, что оно окончательное, а не временно заблокированное.

Не так disabled для «сейчас нельзя править»

Поле выпадает из обхода, значение нельзя скопировать и оно не уходит на сервер. Пользователь не понимает, ждать ли разблокировки.

Так Ошибка словами

Сообщение текстом рядом с полем: цвет не имеет права быть единственным носителем.

Не так :invalid без user

Форма встречает россыпью красного до первого ввода. Ошибки, которых никто не совершал, обучают их игнорировать.

Доступность

Подпись <label for> обязателен. placeholder подписью не является: он исчезает при вводе и не читается частью скринридеров
Подсказка aria-describedby на поле, указывающий на inst-field-hint
Ошибка Сообщение текстом, а не только красной рамкой: цвет не имеет права быть единственным носителем
Обязательность required плюс видимая пометка. Звёздочка без словесного объяснения в начале формы — половина контракта
Контраст Рамка поля берёт --border-control и держит 3:1: здесь граница и есть контрол
Порядок состояний Записан селектором: отключено > ошибка > фокус > наведение. У :hover:not(:disabled) специфичность (0,3,0), а у :user-invalid всего (0,2,0) — курсор над невалидным полем прятал красную рамку, пока наведение не начало уступать явно

API

ИмяЗначениеЧто делает
класс
inst-inputОднострочное поле
inst-textareaМногострочное
inst-fieldОбёртка: подпись + контрол + подсказка
inst-labelПодпись
inst-field-hintПодсказка
inst-field-errorСообщение об ошибке
inst-input-groupГруппа с приставками
inst-input-affixПриставка или суффикс
inst-requiredПометка обязательности
inst-optionalПометка обязательности
модификатор
inst-input--smРазмер
inst-input--lgРазмер
inst-textarea--smРазмер
inst-textarea--lgРазмер
inst-field--sideПодпись сбоку вместо «сверху»
атрибут
aria-invalidtrue — ошибка от приложения
data-wheninvalid — показом управляет платформа
токен
--surface-fieldlight-dark(var(--n-1), var(--n-14))
--control-h-sm/md/lg
--control-pad-smvar(--space-4)
--radius-sm5px
--text-xs/sm/md
--label-col92px
--border-controllight-dark(oklch(0 0 0 / 0.46), oklch(1 0 0 / 0.38))
--err-textlight-dark(var(--err-5), var(--err-3))
--leading-ui1.4

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