Focus Order — це послідовність, у якій елементи інтерфейсу отримують фокус під час навігації за допомогою клавіатури, Switch Control, VoiceOver або TalkBack. У мобільних додатках порядок фокусу визначає, як користувач переміщається між елементами керування жестами або кнопками. Згідно з W3C WCAG 2.2, Success Criterion 2.4.3, 2023, фокус має слідувати в логічному порядку, що зберігає сенс вмісту. Порушення цього принципу — одна з частих причин непрохідності accessibility-аудиту.
Головне
Focus Order — це послідовність, у якій користувач переміщається між інтерактивними елементами за допомогою альтернативних методів введення: клавіатури (Tab), Switch Control (крок за кроком), VoiceOver (жест праворуч/ліворуч) або TalkBack. На відміну від миші або сенсорного екрана, де користувач вибирає елемент безпосередньо, фокусна навігація лінійна — кожен крок переміщує фокус на наступний елемент.
За даними Apple HIG, 2024, VoiceOver використовує порядок елементів у дереві accessibility, яке будується на основі візуального розташування: лівий верхній кут → правий нижній. Якщо екран містить складне верстання (колонки, Grid, ZStack), дерево може не відповідати візуальному порядку.
Принцип WCAG 2.4.3: «Якщо веб-сторінку можна послідовно переміщати за розділами і порядок фокусу впливає на сенс, то фокус має слідувати в порядку, що зберігає сенс і можливість керування». Виняток: динамічний вміст, де фокус може стрибати для привернення уваги (попередження, модальні вікна).
Користувач Switch Control (люди з моторними порушеннями) переміщається по елементах автоматично — цикл за циклом. Якщо порядок порушено, користувач витрачає в 3 рази більше часу на завершення форми. За даними Deque University, 2024, коректний Focus Order скорочує час заповнення форми на 60% для користувачів допоміжних технологій.
Особлива увага — модальним вікнам. Після відкриття модального вікна фокус має негайно переміститися на перший інтерактивний елемент всередині модалки (зазвичай кнопка «Закрити» або «Підтвердити»). Після закриття — повернутися на елемент, який викликав модальне вікно. Це вимога WCAG 2.4.3 і одночасно поширена помилка.
В iOS VoiceOver автоматично будує порядок на основі геометрії: елементи сортуються за Y, потім за X. Для екранів зі складною структурою цей порядок може бути некоректним — розробник має втрутитися.
Основні інструменти:
Приклад завдання кастомного порядку для картки товару:
class ProductCardView: UIView {
let titleLabel = UILabel()
let priceLabel = UILabel()
let buyButton = UIButton()
override var accessibilityElements: [Any]? {
get {
return [titleLabel!, priceLabel!, buyButton!]
}
set {}
}
}
Для програмного переміщення фокусу після дії:
UIAccessibility.post(
notification: .layoutChanged,
argument: newlyAddedItem
)
Властивість shouldGroupAccessibilityElement корисна для карток у колекціях. Якщо встановити true на батьківській картці, VoiceOver сприймає всю картку як один елемент. Користувач може двічі торкнутися, щоб активувати картку цілком, або налаштувати ротор для навігації всередині. Рекомендується для UICollectionViewCell та UITableViewCell.
В Android TalkBack також використовує геометричний порядок, але пріоритет надається явним атрибутам nextFocus*. Ці атрибути задаються в XML або програмно:
| Атрибут | Призначення | Приклад |
|---|---|---|
| nextFocusDown | Елемент при навігації вниз | @+id/field_email |
| nextFocusUp | Елемент при навігації вгору | @+id/field_name |
| nextFocusLeft | Елемент ліворуч | @+id/btn_back |
| nextFocusRight | Елемент праворуч | @+id/btn_next |
Приклад для форми реєстрації:
<EditText
android:id="@+id/field_email"
android:nextFocusDown="@+id/field_password" />
<EditText
android:id="@+id/field_password"
android:nextFocusDown="@+id/btn_submit" />
Для RecyclerView порядок фокусу динамічний — визначається адаптером. Якщо комірки мають складну структуру, встановіть descendantFocusability = «beforeDescendants» і задайте порядок у вузлі елемента списку. Для Jetpack Compose порядок фокусу задається через Modifier.focusOrder() та FocusOrder. Пріоритет: previous (дочірній), next (наступний), custom key.
Якщо елемент занадто малий для фокусу (менше 44pt), збільште hit area через TouchDelegate в iOS або minWidth/minHeight в Android. За даними Google Material Design, 2024, мінімальна область дотику — 48×48dp. VoiceOver та TalkBack фокусуються на bounding box елемента. Елементи розміром менше 30pt можуть бути недоступні для жестового фокусу — користувач фізично не може потрапити по них пальцем.
Стрибаючий фокус — коли після акції (наприклад, видалення елемента) фокус переміщується на початок списку або на системну кнопку «Назад». Користувач VoiceOver втрачає контекст. Рішення: програмно переміщати фокус на елемент, найближчий до видаленого.
Невидимий фокус — елемент отримує фокус, але візуального індикатора немає (користувачі клавіатури не бачать, де знаходяться). В iOS перевіряйте UIAccessibility.isVoiceOverRunning для кастомних індикаторів. За даними Deque University, 2024, невидимий фокус — друга за частотою причина провалу accessibility-аудиту.
Модальні вікна — фокус залишається на фоновому вмісті після відкриття модального вікна. В iOS модальний view автоматично захоплює фокус, якщо встановлено modalPresentationStyle = .pageSheet. В Android використовуйте setFocusable(true) на контейнері діалогу.
Зворотна проблема: фокус застряє всередині модального вікна і не може вийти (крім закриття). Це допустимо тільки для модальних вікон — користувач повинен свідомо закрити вікно. Для звичайних екранів focus trap — критична помилка. Рішення: переконайтеся, що останній елемент модального вікна (кнопка «Закрити») передає фокус назад.
Для кастомних екранів (карти, канваси, ігри) автоматичний геометричний порядок непридатний. Розробник має побудувати дерево accessibility вручну. В iOS для цього перевизначається метод UIAccessibilityContainer.
Приклад для кастомного канвасу:
class CanvasView: UIView {
var shapes: [ShapeView] = []
override var accessibilityElements: [Any]? {
get {
// Сортуємо фігури за Z-індексом, а не за геометрією
return shapes.sorted { $0.zIndex < $1.zIndex }
}
set {}
}
}
В Android для кастомної View перевизначте onInitializeAccessibilityNodeInfo:
override fun onInitializeAccessibilityNodeInfo(
info: AccessibilityNodeInfo
) {
super.onInitializeAccessibilityNodeInfo(info)
info.addChild(firstElement)
info.addChild(secondElement)
info.isFocusable = true
}
Для динамічних списків (чат, стрічка новин) після додавання елемента викликайте переміщення фокусу на перший новий елемент. В iOS: UIAccessibility.post(notification: .layoutChanged, argument: newMessage). В Android: sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED).
iOS автоматично визначає область фокусу на основі frame елемента. Якщо елемент має трансформацію (transform, rotation), VoiceOver може фокусуватися на неправильній області. Явно задайте accessibilityFrame в координатах екрана: element.accessibilityFrame = UIAccessibility.convertToScreenCoordinates(element.bounds, in: element). Це гарантує, що Voiceover підсвітить правильну область.
Для анімованих екранів (UIKit Dynamics, Lottie, SpriteKit) програмний фокус особливо важливий. VoiceOver не може побудувати дерево accessibility для динамічно рухомих елементів. Встановлюйте isAccessibilityElement = false на контейнерах анімації та true тільки на інтерактивних елементах всередині.
Ручне тестування: увімкніть VoiceOver (iOS) або TalkBack (Android), проведіть жестом праворуч по всій послідовності. Фокус має слідувати візуальному порядку — зліва направо, зверху вниз. Кожен інтерактивний елемент має отримати фокус рівно один раз.
Автоматизоване тестування ускладнено, але можливо:
func testKeyboardFocusOrder() {
let app = XCUIApplication()
app.launch()
app.textFields["Email"].tap()
// Tab — тільки з апаратною клавіатурою
}
Для Android використовуйте Accessibility Testing Framework:
@Test
fun testFocusOrder() {
onView(withId(R.id.fieldEmail))
.check(matches(isFocusable()))
onView(withId(R.id.fieldEmail))
.perform(focus())
onView(withId(R.id.fieldPassword))
.check(matches(isFocused()))
}
Найнадійніший метод — UI-тест сценарію: заповніть форму покроково (Email → Пароль → Надіслати), перевіряючи, що кожен крок завершується успішно. Якщо порядок фокусу порушено, сценарій впаде при спробі взаємодії з елементом поза фокусом.
Інструмент Accessibility Inspector в Xcode показує повне дерево accessibility. Ви можете пройти по елементах у порядку VoiceOver і побачити точний фокусний шлях. Використовуйте вкладку «Audit» для автоматичного пошуку порушень Focus Order.
Часті запитання
WCAG 2.4.3 (Focus Order) — критерій успіху рівня A. Вимагає, щоб порядок фокусу зберігав сенс вмісту при послідовній навігації. Порушення вважається критичним і блокує сертифікацію.
Приховані елементи повинні мати isAccessibilityElement = false в iOS або visibility = gone/invisible в Android. При появі — програмно перемістіть фокус через UIAccessibility.post(notification: .layoutChanged).
iOS керує через accessibilityElements та shouldGroupAccessibilityElement, Android — через атрибути nextFocus* та AccessibilityNodeInfo. Принцип однаковий: геометричний порядок за замовчуванням із можливістю перевизначення.
Встановіть descendantFocusability = «beforeDescendants» на кореневому елементі та налаштуйте порядок в адаптері через onInitializeAccessibilityNodeInfo для кожної комірки.
Підключіть апаратну клавіатуру через Bluetooth або USB. В iOS натисніть Tab для переміщення фокусу. В Android увімкніть TalkBack і використовуйте клавіші Tab та стрілки.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також