Паттерны
Select с поиском
Когда использовать:
- список вариантов длинный
- пользователям нужна текстовая фильтрация перед выбором
- компактность поля всё ещё важна
Заметки о композиции:
- рендерите текстовое поле внутри
SelectListbox - отключите встроенный поиск при наборе через
typeahead={false} - фильтруйте отрендеренный список
SelectOptionпо внешнему состоянию - передавайте название выбранного варианта в
SelectValueдочерним элементом: отфильтрованный вариант не рендерится, и копировать нечего - обрабатывайте Enter в поле поиска сами (здесь он выбирает первое совпадение): стрелки и Enter по списку работают, только пока фокус на триггере
Компромиссы:
- облегчает поиск в длинных списках
- усложняет работу с клавиатуры по сравнению с обычным select
import {
Label,
Select,
SelectCombobox,
SelectHiddenSelect,
SelectIcon,
SelectListbox,
SelectOption,
SelectValue,
Text,
TextField,
TextFieldInput,Контролируемый Select с очисткой
Когда использовать:
- выбранное значение можно сбросить
- поле должно синхронизироваться с состоянием URL, фильтрами или внешней логикой формы
Заметки о композиции:
- храните выбор в состоянии React
- передавайте
valueиonChangeValue - предусмотрите отдельное действие очистки, которое ставит
null, а неundefined
Компромиссы:
- состояние остаётся явным и предсказуемым
- добавляет сложности в состояние родителя по сравнению с неконтролируемым использованием
import {
Button,
Label,
Select,
SelectCombobox,
SelectIcon,
SelectListbox,
SelectOption,
SelectValue,
Text,
} from '@vega-ui/react';
import { useId, useState } from 'react';Select внутри оверлея
Когда использовать:
- модальное окно, выдвижная панель (Drawer) или шторка (Sheet) содержат поле одиночного выбора
- список должен сосуществовать с другой системой оверлеев
Заметки о композиции:
- рендерите
Selectкак обычно внутри содержимого оверлея - портал не нужен: список рендерится в top layer поверх оверлея
- перепроверьте восстановление фокуса и закрытие в реальном оверлее
Компромиссы:
- сохраняет обычное поведение поля внутри оверлеев
- требует больше интеграционного тестирования, чем изолированное использование компонента
import type { CSSProperties, FormEvent } from 'react';
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogTrigger,
Heading,
Icon,
IconButton,
Label,
Select,Select внутри предложения
Когда использовать:
- элемент встроен в читаемый текст в виде предложения
- выбор лёгкий и должен ощущаться встроенным
Заметки о композиции:
- используйте
variant='inline' - окружайте поле осмысленным текстом
- не используйте вариант inline без контекста
- дайте триггеру собственный
aria-label: предложение вокруг не является меткой
Компромиссы:
- ощущается лёгким и встроенным
- ясность и подпись зависят от окружающего текста
import {
Select,
SelectCombobox,
SelectHiddenSelect,
SelectIcon,
SelectListbox,
SelectOption,
SelectValue,
Text,
} from '@vega-ui/react';
export default function InlineVariantInsideSentenceLikeUi() {Насыщенное содержимое вариантов
Когда использовать:
- вариантам нужны иконки или дополнительный текст
- визуальный просмотр важнее минимальной плотности строк
Заметки о композиции:
- оставляйте
valueскалярным - делайте единицей выбора всю строку варианта
- не разбивайте вариант на несколько интерактивных элементов
- передайте
label, чтобы typeahead искал по названию, и короткий дочерний элементSelectValue, чтобы триггер не копировал всю строку - у вариантов фиксированная высота, поэтому многострочному содержимому нужен
height: 'auto'на варианте
Компромиссы:
- облегчает просмотр сложных вариантов
- увеличивает высоту строк и семантическую сложность
import {
Label,
Select,
SelectCombobox,
SelectHiddenSelect,
SelectIcon,
SelectListbox,
SelectOption,
SelectValue,
Text,
} from '@vega-ui/react';
import { useId, useState } from 'react';Select в нативной форме
Когда использовать:
- выбранное значение должно отправляться обычными механизмами формы
- интеграция опирается на
FormData
Заметки о композиции:
- добавьте
SelectHiddenSelect - держите select внутри границ формы
Компромиссы:
- улучшает совместимость с нативными формами
- добавляет в композицию ещё одну обязательную часть
import {
Button,
Label,
Select,
SelectCombobox,
SelectHiddenSelect,
SelectIcon,
SelectListbox,
SelectOption,
SelectValue,
Text,
} from '@vega-ui/react';Примечания:
- без
SelectHiddenSelectэлемент может рендериться корректно, но контракт нативной формы становится слабее
Своя иконка
Когда использовать:
- иконка триггера должна подсказывать вид значения, а не только наличие списка
- стандартный шеврон не подходит к окружающему интерфейсу
Заметки о композиции:
- передайте свою иконку дочерним элементом
SelectIcon; она заменяет стандартный шеврон - держите
SelectIconвнутриSelectCombobox, послеSelectValue
Компромиссы:
- придаёт триггеру смысл с первого взгляда
- без шеврона менее очевидно, что поле открывает список
import {
Select,
SelectCombobox,
SelectIcon,
SelectListbox,
SelectOption,
SelectValue,
} from '@vega-ui/react';
import { GlobeIcon } from '@vega-ui/icons';
export default function CustomIcon() {
return (Сравнение
Быстрое правило выбора
Выбирайте Select, если верно всё перечисленное:
- выбор одиночный
- значение —
stringилиnumber - набор вариантов лучше скрыть, чем показывать постоянно
- важны участие в нативной форме или семантика поля
Выбирайте другой элемент, если видимость, бинарное состояние или более богатое содержимое с действиями важнее компактного поведения поля.
Select и SegmentedControl
Используйте Select, когда:
- вариантов много
- места мало
- варианты не обязательно показывать всё время
Используйте SegmentedControl, когда:
- вариантов всего несколько
- пользователю полезно видеть все варианты сразу
- переключение между вариантами должно ощущаться мгновенным
Компромисс:
Selectэкономит место, но скрывает вариантыSegmentedControlоблегчает обнаружение вариантов, но занимает ширину макета
Select и Radio
Используйте Select, когда:
- список вариантов длинный
- поле находится в плотной форме
- элемент должен оставаться компактным
Используйте Radio, когда:
- набор вариантов короткий
- пользователи должны напрямую сравнивать все доступные варианты
- явная видимость одиночного выбора важнее компактности
Компромисс:
SelectплотнееRadioнагляднее для сравнения
Select и Checkbox
Используйте Select, когда:
- пользователь должен выбрать ровно один вариант
- значение должно вести себя в форме как скалярное поле
Используйте Checkbox, когда:
- взаимодействие бинарное
- каждый элемент представляет независимый выбор «да» или «нет»
Не заменяйте группы из нескольких чекбоксов одним Select, если модель продукта на самом деле не подразумевает одиночный выбор.
Select и Switch
Используйте Select, когда:
- пользователь выбирает из нескольких значений
- выбранное значение должно сохраняться как одно поле
Используйте Switch, когда:
- взаимодействие — мгновенное включение или выключение
- настройка меняется напрямую, без выбора из списка
Компромисс:
Select— для выбора из перечисленияSwitch— для бинарного состояния
Select и Popover
Используйте Select, когда:
- основная задача — выбрать значение
- нужны навигация по вариантам с клавиатуры и семантика значения
Используйте Popover, когда:
- содержимое — не поле одиночного выбора в первую очередь
- оверлей содержит действия, справочный текст или собственный интерфейс, а не семантику списка
Выбирайте этот компонент, когда
- выбор одиночный
- значение должно вести себя как одно компактное поле
- набор вариантов настолько длинный, что постоянно видимые элементы добавили бы шума
- важны участие в форме или плотный макет
Не выбирайте этот компонент, когда
- пользователю полезно видеть все варианты сразу
- взаимодействие бинарное, а не выбор из перечисления
- содержимое оверлея на самом деле не является полем
- модель значения опирается на объекты, а не на скалярные ключи
Решение проблем
Выбранное значение не отправляется с формой
Симптом
Элемент рендерится и визуально обновляется, но выбранного значения нет в нативной отправке формы.
Вероятная причина
В композиции отсутствует SelectHiddenSelect.
Как проверить
- отправьте окружающую форму
- изучите получившийся
FormData - убедитесь, что ожидаемого имени поля или значения там нет
Решение
Рендерите SelectHiddenSelect с name внутри корневого Select.
import {
Button,
Label,
Select,
SelectCombobox,
SelectHiddenSelect,
SelectIcon,
SelectListbox,
SelectOption,
SelectValue,
Text,
} from '@vega-ui/react';С клавиатуры нельзя выбрать отфильтрованный вариант
Симптом
Поле поиска внутри SelectListbox фильтрует варианты, но после ввода выбрать вариант без мыши невозможно.
Вероятная причина
Стрелки и Enter перемещают по списку, только пока фокус на триггере. Когда фокус в поле поиска, Select не обрабатывает его клавиши, а Tab уводит фокус из списка и закрывает его.
Как проверить
- откройте Select и введите текст в поле поиска
- нажмите стрелки и Enter
- проверьте, выбирается ли какой-нибудь вариант
Решение
Обрабатывайте Enter в поле поиска сами: выбирайте первое совпадение, закрывайте список и возвращайте фокус на триггер. Передавайте название выбранного варианта в SelectValue дочерним элементом, потому что отфильтрованный вариант не рендерится.
import {
Label,
Select,
SelectCombobox,
SelectHiddenSelect,
SelectIcon,
SelectListbox,
SelectOption,
SelectValue,
Text,
TextField,
TextFieldInput,Select визуально работает, но в диалоге поведение клавиатуры кажется сломанным
Симптом
Select открывается, но перемещение фокуса, закрытие или навигация с клавиатуры внутри оверлея ведут себя непоследовательно.
Вероятная причина
Поведение в составе оверлея проверяли изолированно, но не перепроверили внутри Dialog, Drawer или Sheet.
Как проверить
- отрендерите точную интеграцию внутри реального оверлея
- проверьте с клавиатуры открытие, закрытие, возврат фокуса и навигацию по вариантам
Решение
- сохраняйте стандартную композицию
Selectнетронутой - перепроверьте с реальным стеком оверлеев
- избегайте собственных структур триггера или вариантов, мешающих движению фокуса
Плейсхолдер никогда не появляется
Симптом
placeholder задан, но триггер всегда показывает какое-то другое содержимое.
Вероятная причина
Начальное значение уже выбрано, или собственные дочерние элементы SelectValue всегда рендерят содержимое.
Как проверить
- уберите
valueиdefaultValue - уберите собственные дочерние элементы из
SelectValue - проверьте, становится ли плейсхолдер видимым
Решение
- используйте
placeholderтолько когда выбранного значения нет - убедитесь, что собственный рендер значения учитывает пустое состояние
Очистка значения не обновляет интерфейс
Симптом
Родитель логически очищает значение, но интерфейс всё ещё показывает старый выбор.
Вероятная причина
Родитель ставит значение в undefined. Для Select undefined означает неконтролируемый режим, и компонент возвращается к собственному внутреннему состоянию, где хранится старый выбор; контролируемое пустое значение — null.
Как проверить
- изучите текущий проп
value - убедитесь, что
onChangeValueобновляет тот же источник истины
Решение
Очищайте контролируемое значение через null и преобразуйте undefined, который может прийти в onChangeValue: onChangeValue={(next) => setValue(next ?? null)}.
import {
Button,
Label,
Select,
SelectCombobox,
SelectIcon,
SelectListbox,
SelectOption,
SelectValue,
Text,
} from '@vega-ui/react';
import { useId, useState } from 'react';Варианты рендерятся корректно, но выбор не меняется
Симптом
Варианты видны и кликабельны, но выбранное значение не обновляется как ожидается.
Вероятная причина
Значения SelectOption не соответствуют ожидаемой скалярной модели значений, или собственная композиция варианта сломала выбираемую строку.
Как проверить
- убедитесь, что у каждого
SelectOptionзначение типаstringилиnumber - проверьте, остаётся ли вся строка варианта единицей выбора
Решение
- оставляйте значения вариантов скалярными
- не разбивайте один вариант на несколько конкурирующих интерактивных элементов
Только для чтения и отключённое состояние ведут себя не так, как ожидалось
Симптом
Состояние элемента визуально выглядит правильно, но фокусируемость или взаимодействие не соответствуют ожиданиям продукта.
Вероятная причина
readOnly и disabled восприняли как визуальные варианты, а не как варианты поведения.
Как проверить
- проверьте фокусируемость и взаимодействие в обоих состояниях
- уточните, должен ли элемент по-прежнему получать фокус
Решение
- используйте
disabled, когда взаимодействие должно полностью прекратиться - используйте
readOnly, когда поле должно оставаться фокусируемым, но неизменяемым
Значения-объекты вызывают проблемы с типами или поведением
Симптом
При передаче целых объектов в качестве значений вариантов появляются ошибки типов, нестабильный выбор или несовпадающие значения.
Вероятная причина
Публичный API рассчитан на значения string и number, а не на объекты.
Как проверить
- изучите
value, переданный вSelect - убедитесь, что
SelectOption value={...}скалярный
Решение
Храните в Select скалярный ключ, а полный объект получайте на уровне родителя.
Select выглядит правильно, но композиция всё равно хрупкая
Симптом
Компонент вроде бы работает, но небольшие изменения в настройке постоянно ломают поведение.
Вероятная причина
Слишком много поведения перенесено в собственные обёртки вместо готовых частей.
Как проверить
- сравните текущую композицию со стандартной из
index.mdиanatomy.md - убирайте собственные обёртки постепенно
Решение
- сначала вернитесь к стандартной композиции
- добавляйте собственные обёртки обратно только там, где встроенных частей недостаточно
- используйте
useSelectContextтолько для продвинутых случаев