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

Ввод

Поиск

JS по желанию Живая фильтрация. Поле и очистка работают без скрипта

Текстовое поле с лупой и нативной кнопкой очистки. Обёртка вокруг input[type=search], а не отдельный контрол: своя кнопка потребовала бы JS.

Компонент
Разметка
<div class="inst-field">
  <label class="inst-label" for="q">Поиск по прогонам</label>
  <span class="inst-search">
    <input class="inst-input" id="q" type="search" placeholder="worldgen">
  </span>
</div>

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

Три части, и порядок между ними обязателен.

<span class="inst-search">
  <input class="inst-input" type="search" placeholder="worldgen" aria-label="Поиск по прогонам">
</span>
Что Обязательно Почему
inst-search на обёртке да Лупа рисуется псевдоэлементом обёртки: на самом <input> псевдоэлементов нет
inst-input на поле да Оформление поля целиком приходит оттуда. inst-search добавляет только отступ под лупу и очистку. Отступ задан селектором & > .inst-input — поле без класса встанет под значок
type="search" да Крестик очистки — нативный. Без этого типа его не существует, и это обычное текстовое поле с лупой
<label for> или aria-label да placeholder подписью не является: он исчезает при вводе

Лупа несёт pointer-events: none: клик по значку попадает в поле, а не в пустоту перед ним.

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

Используйте Возьмите другое
Фильтрация или поиск по списку, таблице, логу Произвольный текст, который сохраняюттекстовое поле: у него нет крестика, и очистка ему не нужна
Значение, которое пользователь часто стирает целиком Выбор из известного набораселект: поиск по трём вариантам медленнее выбора
Поле в панели инструментов над данными Переключение между заранее известными видамисегментированный контрол
Одно поле — одна строка запроса Несколько условий сразу — это форма фильтров, а не одно поле

Размеры

Все — от текстового поля, потому что inst-search не заводит своих.

Пример
Разметка
<span class="inst-search">
  <input class="inst-input inst-input--sm" type="search" value="sm" aria-label="Поиск sm">
</span>
<span class="inst-search">
  <input class="inst-input" type="search" value="md" aria-label="Поиск md">
</span>
<span class="inst-search">
  <input class="inst-input inst-input--lg" type="search" value="lg" aria-label="Поиск lg">
</span>

Отступ под лупу считается от --control-pad-sm и ширины значка, поэтому у всех трёх размеров текст начинается на одинаковом расстоянии от значка, а не наезжает на него в sm.

Состояния

Пример
Разметка
<span class="inst-search">
  <input class="inst-input" type="search" value="Обычное" aria-label="Поиск обычный">
</span>
<span class="inst-search">
  <input class="inst-input" type="search" value="Недоступно" disabled aria-label="Поиск недоступен">
</span>

Наведение, фокус, ошибка, readonly и disabled — те же, что у текстового поля.

Поведение

Очистка

Крестик очистки — собственная кнопка браузера (::-webkit-search-cancel-button), приведённая к тону кита: тот же глиф и тот же приглушённый цвет, что у снятия тега. Её не прячут и не подменяют — поведение «очистить и сообщить об этом форме» уже написано платформой, и своя кнопка означала бы JS ради того, что уже работает.

Плата названа честно: в Firefox нативного крестика нет, и поле там просто без очистки. Крестик — улучшение, а не единственный способ стереть значение.

JS

Поле работает без единой строки: type="search" даёт нативную кнопку очистки, Escape очищает, форма отправляется по Enter. Скрипт нужен, только если результаты обновляются на лету.

Фильтрация на лету

input.addEventListener('input', () => {
  const q = input.value.trim().toLowerCase();
  for (const row of rows) {
    row.hidden = q !== '' && !row.dataset.name.includes(q);
  }
  count.textContent = rows.filter((r) => !r.hidden).length;
});

Читается input, а не keyup: очистка крестиком, вставка мышью и автозаполнение keyup не порождают, и список молча оставался бы старым.

Что сказать вслух

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

<p class="inst-u-visually-hidden" aria-live="polite">
  <span id="count">12</span> совпадений
</p>

Композиции

В панели инструментов

В контексте
Разметка
<div class="inst-toolbar">
  <span class="inst-search">
    <input class="inst-input inst-input--sm" type="search" placeholder="Фильтр" aria-label="Фильтр по задачам">
  </span>
  <button class="inst-btn inst-btn--sm" type="button">Сбросить</button>
</div>

С подсказкой о синтаксисе

Пример
Поддерживаются кавычки и минус для исключения
Разметка
<div class="inst-field">
  <label class="inst-label" for="q2">Поиск по логу</label>
  <span class="inst-search">
    <input class="inst-input" id="q2" type="search" aria-describedby="q2-hint">
  </span>
  <span class="inst-field-hint" id="q2-hint">Поддерживаются кавычки и минус для исключения</span>
</div>

Правила

Так Нативный type="search"

Крестик, роль «поле поиска» и Esc для очистки приходят от платформы.

Не так Своя кнопка очистки

Она означает JS ради того, что браузер уже написал, и теряет клавиатурный эквивалент.

Так Подпись даже в тулбаре

Там, где видимая подпись съела бы строку, — aria-label. Поле без имени не называет, по чему оно ищет.

Не так Поиск по трём вариантам

Выбор из известного короткого набора быстрее делается селектом или сегментированным.

Доступность

Подпись Видимая <label for> предпочтительна. aria-label — когда поле стоит в панели инструментов и подпись съела бы строку
Лупа Псевдоэлемент, для скринридера её нет вовсе — и правильно: это украшение, а работу называет подпись
Очистка Нативная кнопка браузера. Клавиатурный эквивалент — Esc в поле поиска, тоже нативный
Тип поля type="search" объявляется как «поле поиска»: роль приходит от платформы, role="search" руками ставить не нужно
Контраст Значок берёт --text-muted — это украшение, а не несущий знак. Крестик темнеет на наведении до --text-primary
Цель нажатия Крестик — --size-chevron, 10px. Он дублирует выделение и Backspace, поэтому не является единственным способом стереть значение

API

ИмяЗначениеЧто делает
класс
inst-searchОбёртка. Рисует лупу и отводит под неё место
inst-inputСамо поле. Все размеры и состояния — оттуда
токен
--control-pad-smvar(--space-4)
--size-icon16px
--size-chevron10px
--space-36px
--text-mutedlight-dark(var(--n-8), var(--n-6))
--text-primarylight-dark(var(--n-12), var(--n-1))

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