Поиск по вводу — один из наиболее распространённых паттернов пользовательского интерфейса. Он применяется в автодополнении, выборе элементов из большого списка, фильтрации данных, командных палитрах, навигации и формах. Основная задача — позволить пользователю вводить текст и получать релевантные результаты в реальном времени.
В библиотеке Radix UI подобная функциональность не представлена в виде одного универсального компонента. Вместо этого она строится из набора примитивов:
Popover, Command, Select или
DropdownMenuТакой подход соответствует философии Radix UI: библиотека предоставляет низкоуровневые доступные примитивы, а сложные интерфейсные паттерны собираются из них.
Поиск по вводу обычно состоит из нескольких элементов:
input)Типичная схема взаимодействия:
Input → изменение значения → фильтрация данных → обновление списка
В React состояние поиска чаще всего хранится в
useState.
Простейшая структура:
const [query, setQuery] = useState("");
const filteredItems = items.filter(item =>
item.toLowerCase().includes(query.toLowerCase())
);
Radix UI используется для создания интерфейса списка, навигации по элементам и доступного взаимодействия с клавиатурой.
Комбинация Popover и поля ввода позволяет создать
интерфейс автодополнения.
import * as Popover from "@radix-ui/react-popover";
import { useState } from "react";
function SearchBox({ items }) {
const [query, setQuery] = useState("");
const filtered = items.filter(item =>
item.toLowerCase().includes(query.toLowerCase())
);
return (
<Popover.Root>
<Popover.Trigger asChild>
<input
value={query}
onCha nge={(e) => setQuery(e.target.value)}
placeholder="Поиск..."
/>
</Popover.Trigger>
<Popover.Content>
<ul>
{filtered.map(item => (
<li key={item}>{item}</li>
))}
</ul>
</Popover.Content>
</Popover.Root>
);
}
В этой архитектуре:
Radix UI уделяет большое внимание доступности (accessibility). При реализации поиска необходимо учитывать несколько аспектов:
Пользователь должен иметь возможность:
Radix решает это через систему roving focus и управление фокусом внутри компонентов.
Для поискового интерфейса обычно используются:
role="combobox"
aria-expanded
aria-controls
aria-activedescendant
При использовании компонентов Radix значительная часть этих атрибутов добавляется автоматически.
Одним из наиболее удобных способов реализации поиска является
компонент Command. Он используется для командных палитр и
поиска элементов.
Структура:
import * as Command from "cmdk";
function SearchCommand({ items }) {
return (
<Command>
<Command.Input placeholder="Поиск..." />
<Command.List>
{items.map(item => (
<Command.Item key={item}>
{item}
</Command.Item>
))}
</Command.List>
</Command>
);
}
Особенности:
Command фактически реализует готовый интерфейс
поиска.
Автодополнение — частный случай поиска по вводу, при котором предлагаются варианты завершения текста.
Пример логики:
const suggestions = data.filter(item =>
item.startsWith(query)
);
Для нечувствительности к регистру:
item.toLowerCase().startsWith(query.toLowerCase())
При работе с большими массивами данных (тысячи элементов) простая фильтрация может стать медленной. Применяются следующие техники:
Дебаунс ограничивает количество вычислений.
import { useDebounce } from "use-debounce";
const [debouncedQuery] = useDebounce(query, 300);
Фильтрация выполняется только после паузы ввода.
Если список содержит много элементов, используется виртуализация.
Популярные библиотеки:
react-virtualreact-windowreact-virtuosoRadix UI хорошо интегрируется с ними, так как его компоненты не навязывают структуру DOM.
Часто данные загружаются с сервера. В этом случае поиск выполняется через API.
Типичный алгоритм:
Пример:
useEffect(() => {
async function fetchData() {
const res = await fetch(`/api/search?q=${query}`);
const data = await res.json();
setResults(data);
}
if (query.length > 2) {
fetchData();
}
}, [query]);
Ограничение длины запроса уменьшает нагрузку на сервер.
При асинхронном поиске важно показывать состояние загрузки.
{loading && <div>Загрузка...</div>}
В интерфейсе поиска это обычно реализуется внутри списка результатов.
Если совпадений нет, интерфейс должен явно сообщать об этом.
Нет результатов
Пример:
{filtered.length === 0 && (
<div className="empty">
Ничего не найдено
</div>
)}
Для улучшения UX совпадающая часть текста может подсвечиваться.
Алгоритм:
Пример:
function highlight(text, query) {
const index = text.toLowerCase().indexOf(query.toLowerCase());
if (index === -1) return text;
const before = text.slice(0, index);
const match = text.slice(index, index + query.length);
const after = text.slice(index + query.length);
return (
<>
{before}
<strong>{match}</strong>
{after}
</>
);
}
Результаты поиска должны скрываться в нескольких ситуациях:
Radix автоматически управляет этим поведением через
Popover или Dialog.
Поле поиска часто содержит кнопку очистки.
<button onCl ick={() => setQuery("")}>
×
</button>
После очистки обычно:
Фокус — ключевой элемент доступности интерфейса поиска.
Radix решает несколько задач:
Это достигается благодаря внутренним механизмам управления фокусом.
Компонент Select может быть расширен возможностью
поиска.
Структура:
Sel ect
├ Trigger
└ Content
├ Input (поиск)
└ Items
Пример:
<Select.Content>
<input
placeholder="Фильтр..."
onCha nge={(e) => setQuery(e.target.value)}
/>
{filteredItems.map(item => (
<Select.Item key={item} value={item}>
{item}
</Select.Item>
))}
</Select.Content>
Dropdown-меню может содержать поле фильтрации.
<DropdownMenu.Content>
<input
placeholder="Поиск..."
onCha nge={(e) => setQuery(e.target.value)}
/>
{filtered.map(item => (
<DropdownMenu.Item key={item}>
{item}
</DropdownMenu.Item>
))}
</DropdownMenu.Content>
Такой интерфейс используется в:
После выбора элемента необходимо обновить состояние.
const [selected, setSelected] = useState(null);
При выборе:
onSel ect={(value) => setSelected(value)}
Выбранное значение может:
При реализации поиска важны несколько факторов:
const filtered = useMemo(() => {
return items.filter(...)
}, [items, query]);
Это предотвращает лишние вычисления.
Часто выводится только часть совпадений:
максимум 10–20 элементов
filtered.slice(0, 10)
Для больших наборов данных используется:
Они позволяют выполнять быстрый поиск по сложным условиям.
Нечёткий поиск позволяет находить результаты даже при ошибках в написании.
Пример:
"react" → "reaact"
Библиотека Fuse.js используется следующим образом:
import Fuse fr om "fuse.js";
const fuse = new Fuse(items, {
keys: ["name"]
});
const results = fuse.search(query);
Крупные приложения используют более сложную архитектуру:
SearchProvider
├ SearchInput
├ SearchResults
├ SearchItem
└ SearchEmptyState
Такой подход обеспечивает:
Одним из популярных интерфейсов поиска является command palette, аналогичная интерфейсу редакторов кода.
Особенности:
Radix и cmdk часто используются вместе для реализации
такого интерфейса.
В сложных системах поиск может включать:
Пример структуры:
Results
├ Recent
├ Pages
├ Commands
└ Settings
Каждая категория может иметь собственную фильтрацию.
Современные интерфейсы поиска учитывают ряд UX-паттернов:
Radix UI обеспечивает инфраструктуру для реализации всех этих сценариев через доступные примитивы управления фокусом, меню, списками и всплывающими контейнерами.