Перейти до вмісту
tatetUI

Select

Випадний список із фільтрацією, повною клавіатурною навігацією та ARIA-combobox.

Єдиний серед чотирьох вихідних проєктів компонент із повноцінним ARIA-combobox. Решта реалізацій були <div> із випадайкою — візуально схоже, для скрінрідера не існує.

Приклад

Список звужується під час набору.

API

Props

НазваТипТиповоОпис
options*SelectOption[]—Варіанти вибору. Фільтрація за введеним текстом відбувається локально.
modelValuestring | number—Обране значення. Використовуйте через `v-model`.
labelstring——
placeholderstring——
sizeFieldSize"md"Висота поля. На мобільному кожен розмір вищий за десктопний.
errorstring—Текст помилки. Його наявність вмикає стан помилки.
hintstring—Підказка під полем. Ховається, коли показано помилку.
idstring—Стабільний DOM id. `name` використовується лише для форми.
namestring——
disabledboolean——
requiredboolean——
loadingboolean—Показує індикатор замість списку — для серверного пошуку.
clearableboolean—Дозволяє скинути вибір хрестиком.
filterablebooleantrueДозволяє звужувати список набором тексту. Без фільтра поле поводиться як нативний `<select>`: Enter і пробіл відкривають список.
mobileSheetbooleantrueНа вузькому екрані (до 768px) список відкривається нижнім sheet'ом на всю ширину замість випадайки під полем. Вимикайте лише там, де sheet недоречний: поле в щільному рядку таблиці чи в іншому sheet'і, що вже займає екран.

Події

НазваPayloadОпис
search[query: string]Введений текст. Підключайте для серверної фільтрації.
update:modelValue[value: string | number]Обране значення або `null`, якщо вибір скинуто.

Слоти

НазваОпис
optionВласний рендер пункту списку.

Доступно через ref

НазваТипОпис
focus() => voidСтавить фокус на поле.

Коли використовувати

Для вибору одного значення з відомого набору. Фільтр (filterable) типово ввімкнений: гортати довгий список гірше, ніж набрати три букви. Для короткого списку його можна вимкнути — тоді поле поводиться як нативний <select>.

Коли НЕ використовувати

Для двох-трьох варіантів беріть радіокнопки: вони показують усі варіанти одразу, без зайвого кліку.

Для довільного тексту з підказками потрібен інший компонент — цей завжди повертає значення зі списку.

Доступність

Поле має role="combobox", aria-autocomplete="list", aria-expanded, aria-controls і aria-activedescendant. Панель — role="listbox", пункти — role="option" з aria-selected.

Стрілки пропускають деактивовані пункти, а не зупиняються на них. Escape викликає stopPropagation — інакше той самий натиск закрив би ще й модалку, у якій селект стоїть.

З фільтром уже перша літера, набрана в закрите поле, відкриває список і фільтрує його, а Enter бере знайдений пункт. Без фільтра Enter і пробіл відкривають список, а пробіл у відкритому списку обирає пункт — як у нативного <select>.

Атрибути, що не є props (aria-label, data-*), ідуть на саме поле, а class і style — на обгортку разом із лейблом.

Панель телепортується в body. Базовий шар — 1100, а всередині вкладеного Modal або Drawer значення автоматично піднімається над найближчим оверлеєм.

На мобільному

До 768px список відкривається не випадайкою під полем, а нижнім sheet'ом (UiDrawer position="bottom") на всю ширину: рядки по 48px, обраний пункт посередині екрана, закриття тапом по фону, свайпом униз чи хрестиком. Поле в цьому режимі — кнопка: readonly, тож клавіатура не вилазить від тапу.

Заголовок sheet'а — label, а без нього aria-label, зовнішній <label for> поля або плейсхолдер: людина бачить, що саме обирає, хоча поле лишилося під фоном.

Пошук з'являється лише в списку довшому за 10 пунктів і фокус сам НЕ отримує — клавіатура не накриває список, доки людина не вирішила шукати. З пошуком висота sheet'а фіксована (90dvh): за вмістом він стрибав би вниз із кожною літерою і ховався б під клавіатурою разом із полем пошуку.

Sheet відкривається на click, а не на pointerdown, як випадайка: інакше фон з'являвся під пальцем ще до того, як дотик завершився. Поворот екрана з відкритим списком закриває його, а не переносить між режимами.

Вимкнути режим — :mobile-sheet="false": для поля в щільному рядку таблиці чи всередині іншого sheet'а, що вже займає екран.

Деталі реалізації

Позиція рахується власним кодом, без @floating-ui і без VueUse — компонент має копіюватись у чужий проєкт без нової залежності.

Слухач прокрутки навішено з capture: true. Подія scroll не спливає, тож без цього відкрита панель лишалася б висіти на місці, поки список під нею від'їжджає.

Позиція вимірюється двічі: перший раз із припущенням висоти 240px, другий — коли панель уже отримала реальну. Без другого проходу вибір напрямку відкриття інколи помилявся.

Поки користувач не почав набирати, показується весь список. Інакше після відкриття видно лише один пункт — той, що вже обраний і чия назва стоїть у полі як текст фільтра.

Клік у поле лише відкриває список; закриває його шеврон, клік поза полем або вибір. Поки клік перемикав, клік усередині відкритого поля — просто щоб переставити каретку — закривав список і стирав набраний запит.

Назва в полі стежить не лише за значенням, а й за назвою обраного пункту. Форма редагування зазвичай отримує значення раніше за список опцій, і поле лишалося порожнім, доки користувач не відкривав і не закривав його.