Modal
Модальне вікно зі стеком шарів, пасткою фокуса й блокуванням прокрутки.
Знизу до sm вікно — bottom sheet, вище — центрований діалог. Це не декор:
центроване вікно на телефоні з піднятою клавіатурою просто не вміщається.
Приклад
Шапка й футер однакової висоти (хрестик — того ж розміру, що кнопки футера),
тож вікно виглядає симетрично. Кнопки футера мають однакову мінімальну ширину
(7rem) і центрований підпис — «Скасувати» й «Зберегти» не різняться на око.
У режимі bottom sheet (до sm) вони ділять ширину порівну: дві дії поруч
читаються рівноцінними й вціляють під великий палець.
Свайп униз (bottom sheet)
swipe-to-close додає шторці нативний жест: над шапкою з'являється ручка, і
вікно можна потягнути вниз — за ручку або за шапку (крім її кнопок і полів).
Відпустили далі чверті висоти чи різким кидком — вікно закривається, інакше
повертається на місце. Жест діє лише поки вікно bottom sheet (до sm): за
шапку центрованого діалогу не тягають. Типово вимкнено — поведінка без пропа
не змінилася.
Свайп — явна дія, як хрестик: працює з closable і навіть у persistent
вікні. Якщо закриття треба підтвердити («Відкинути зміни?»), керуйте
v-model самі — відмовлена шторка повертається на місце, а не висить за
краєм.
Вкладені оверлеї
Усі оверлеї бібліотеки ділять один стек шарів. Відкрийте другий рівень і натисніть Escape — закриється лише верхній. Те саме з підказкою чи меню всередині вікна: Escape спершу закриває їх, а вікно з формою — лише наступним натиском.
API
Props
| Назва | Тип | Типово | Опис |
|---|---|---|---|
title | string | "" | Заголовок. Ігнорується, якщо задано слот `header`. |
size | lgsmmdxlfull | "lg" | Ширина панелі. |
panelClass | string | "" | Додаткові класи панелі — коли готових розмірів не вистачає. |
role | dialogalertdialog | "dialog" | Роль панелі. `alertdialog` — для підтверджень, що переривають роботу і чекають відповіді (UiConfirmDialog): скрінрідер оголошує їх як попередження, а не як звичайне вікно. |
modalId | string | — | Стабільний id шару в стеку оверлеїв. Задавати не обов'язково: без нього генерується автоматично. |
initialFocus | string | — | CSS-селектор усередині панелі, якому віддати фокус при відкритті. Типово фокус отримує сама панель, а НЕ перший інпут: на мобільних автофокус в інпуті одразу піднімає клавіатуру і з'їдає пів екрана. |
ariaLabel | string | — | Доступне ім'я діалогу, коли слот `header` не є заголовком. Без нього aria-labelledby вказує на контейнер слота, і якщо там лежить поле вводу — ім'я вікна обчислюється з його ЗНАЧЕННЯ. Діалог пошуку оголошувався як «Діалог: тек» на слові «текст». |
ariaDescribedby | string | — | id елемента з поясненням, що саме відбувається. Потрапляє в `aria-describedby` панелі: коли фокус одразу стрибає на кнопку, без цього скрінрідер оголошує лише заголовок і кнопку, пропускаючи сам текст повідомлення. |
modelValue | boolean | false | Відкрито. Використовуйте через `v-model`. |
closable | boolean | true | Показувати хрестик і дозволяти закриття через Escape. |
closeOnBackdrop | boolean | true | Клік по затемненому фону закриває вікно. Типово `true` — на відміну від UiDrawer, де типово `false`. Модалка зазвичай інформаційна, і випадкове закриття нічого не коштує; drawer майже завжди містить форму, де це втрата введеного. |
persistent | boolean | false | Ні Escape, ні клік по фону не закривають — лише явна дія. |
noPadding | boolean | false | Прибрати внутрішні відступи вмісту. |
swipeToClose | boolean | false | Свайп униз закриває вікно, поки воно bottom sheet (до `sm`): над шапкою з'являється ручка, тягнути можна за неї й за шапку. Як і хрестик, свайп — явна дія: працює з `closable`, навіть якщо вікно `persistent`. Ширше `sm` вікно центроване, і жест вимкнено. |
Події
| Назва | Payload | Опис |
|---|---|---|
update:modelValue | [value: boolean] | Зміна стану відкриття. Використовуйте через `v-model`. |
close | [] | { "Вікно закрито — будь-яким способом": "хрестиком, Escape або кліком по фону." } |
Слоти
| Назва | Опис |
|---|---|
default | Вміст вікна. |
header | Замінює заголовок цілком. Тоді `title` не використовується. |
footer | Кнопки дій. Футер не рендериться, якщо слот порожній. |
Коли використовувати
Для короткої взаємодії, яка має перервати основний потік: підтвердження, компактна форма, перегляд деталі.
Якщо форма велика або користувач має звірятися зі сторінкою під нею —
беріть UiDrawer.
Коли НЕ використовувати
Не кладіть у модалку багатокрокові майстри. Заблокована прокрутка фону плюс обмежена висота панелі роблять довгий вміст незручним саме там, де його найбільше.
Не ставте :closable="false", доки у вікні немає власного шляху виходу.
Цей прапорець вимикає всі способи закриття одразу: і хрестик, і Escape,
і клік по фону. Разом із пасткою фокуса, яка робить решту сторінки inert,
вікно стає глухим кутом — вийти можна хіба перезавантаженням вкладки. Саме
так свого часу поводився пошук по цій документації. Якщо закриття справді
має бути лише явним, лишіть у вікні власну кнопку, яка змінює v-model.
Доступність
Панель має role="dialog", aria-modal="true" і назву — з title через
aria-labelledby або з aria-label, якщо заголовок замінено слотом.
Для підтверджень, що переривають роботу й чекають відповіді, задайте
role="alertdialog" і ariaDescribedby — id елемента з текстом питання.
Коли фокус одразу стає на кнопку, без опису скрінрідер оголошує лише
заголовок і кнопку й мовчить про те, що саме підтверджується. Так робить
UiConfirmDialog.
Коли слот header містить не заголовок, а керування — поле пошуку,
перемикач — назву треба задати props ariaLabel явно. Інакше
aria-labelledby вказує на контейнер слота, і назву вікна браузер
обчислює зі значення поля: діалог оголошувався як «Діалог: тек», щойно
користувач набирав «текст».
Фокус утримується всередині панелі, а при закритті повертається на елемент,
з якого вікно відкрили. Решта прямих дітей <body> на час відкриття
позначаються inert і aria-hidden — з лічильником, тож вкладені
оверлеї можна закривати в будь-якому порядку: атрибути знімаються, коли
елемент відпустив останній оверлей.
Типово фокус отримує сама панель, а не перший інпут: на мобільних
автофокус в інпуті одразу піднімає клавіатуру і з'їдає пів екрана. Якщо
потрібно інакше — задайте initialFocus.
Портування
Компонент не працює сам по собі: йому потрібні чотири композабли зі списку
залежностей вище. Разом вони становлять overlay-ядро, спільне для UiModal,
UiDrawer і UiConfirmDialog — копіюйте їх один раз на проєкт.