Focus Order to sekwencja, w której elementy interfejsu otrzymują fokus podczas nawigacji za pomocą klawiatury, Switch Control, VoiceOver lub TalkBack. W aplikacjach mobilnych kolejność fokusu określa, jak użytkownik porusza się między kontrolkami za pomocą gestów lub przycisków. Według W3C WCAG 2.2, Success Criterion 2.4.3, 2023, fokus powinien podążać w logicznej kolejności, zachowującej sens treści. Naruszenie tej zasady to jedna z częstych przyczyn nieprzechodzenia audytu dostępności.
Najważniejsze
Focus Order to sekwencja, w której użytkownik porusza się między elementami interaktywnymi za pomocą alternatywnych metod wprowadzania: klawiatury (Tab), Switch Control (krok po kroku), VoiceOver (gest w prawo/lewo) lub TalkBack. W przeciwieństwie do myszy lub ekranu dotykowego, gdzie użytkownik wybiera element bezpośrednio, nawigacja fokusem jest liniowa — każdy krok przenosi fokus na następny element.
Według Apple HIG, 2024, VoiceOver używa kolejności elementów w drzewie dostępności, które buduje się na podstawie rozmieszczenia wizualnego: lewy górny róg → prawy dolny róg. Jeśli ekran zawiera złożony układ (kolumny, Grid, ZStack), drzewo może nie odpowiadać kolejności wizualnej.
Zasada WCAG 2.4.3: „Jeśli stronę można przechodzić sekwencyjnie po sekcjach i kolejność fokusu wpływa na znaczenie, to fokus powinien podążać w kolejności zachowującej znaczenie i sterowalność”. Wyjątek: treść dynamiczna, gdzie fokus może skakać w celu przyciągnięcia uwagi (ostrzeżenia, okna modalne).
Użytkownik Switch Control (osoby z zaburzeniami motorycznymi) porusza się po elementach automatycznie — cykl za cyklem. Jeśli kolejność jest zaburzona, użytkownik traci 3 razy więcej czasu na wypełnienie formularza. Według Deque University, 2024, prawidłowy Focus Order skraca czas wypełniania formularza o 60% dla użytkowników technologii wspomagających.
Szczególną uwagę należy zwrócić na okna modalne. Po otwarciu okna modalnego fokus powinien natychmiast przenieść się na pierwszy element interaktywny wewnątrz modala (zwykle przycisk „Zamknij” lub „Potwierdź”). Po zamknięciu — powrócić na element, który wywołał okno modalne. To wymaganie WCAG 2.4.3 i jednocześnie częsty błąd.
W iOS VoiceOver automatycznie buduje kolejność na podstawie geometrii: elementy są sortowane według Y, następnie według X. W przypadku ekranów o złożonej strukturze ta kolejność może być nieprawidłowa — programista musi interweniować.
Podstawowe narzędzia:
Przykład ustawiania niestandardowej kolejności dla karty produktu:
class ProductCardView: UIView {
let titleLabel = UILabel()
let priceLabel = UILabel()
let buyButton = UIButton()
override var accessibilityElements: [Any]? {
get {
return [titleLabel!, priceLabel!, buyButton!]
}
set {}
}
}
Do programowego przeniesienia fokusu po akcji:
UIAccessibility.post(
notification: .layoutChanged,
argument: newlyAddedItem
)
Właściwość shouldGroupAccessibilityElement jest przydatna w przypadku kart w kolekcjach. Jeśli ustawimy true na karcie nadrzędnej, VoiceOver postrzega całą kartę jako jeden element. Użytkownik może dwukrotnie dotknąć, aby aktywować całą kartę, lub skonfigurować rotor do nawigacji wewnątrz. Zalecane dla UICollectionViewCell i UITableViewCell.
W Androidzie TalkBack również używa kolejności geometrycznej, ale priorytet mają jawne atrybuty nextFocus*. Atrybuty te określa się w XML lub programowo:
| Atrybut | Przeznaczenie | Przykład |
|---|---|---|
| nextFocusDown | Element przy nawigacji w dół | @+id/field_email |
| nextFocusUp | Element przy nawigacji w górę | @+id/field_name |
| nextFocusLeft | Element z lewej | @+id/btn_back |
| nextFocusRight | Element z prawej | @+id/btn_next |
Przykład dla formularza rejestracji:
<EditText
android:id="@+id/field_email"
android:nextFocusDown="@+id/field_password" />
<EditText
android:id="@+id/field_password"
android:nextFocusDown="@+id/btn_submit" />
Dla RecyclerView kolejność fokusu jest dynamiczna — określa ją adapter. Jeśli komórki mają złożoną strukturę, ustaw descendantFocusability = „beforeDescendants" i określ kolejność w węźle elementu listy. Dla Jetpack Compose kolejność fokusu określa się przez Modifier.focusOrder() i FocusOrder. Priorytet: previous (potomny), next (następny), custom key.
Jeśli element jest zbyt mały dla fokusu (mniejszy niż 44 pt), zwiększ hit area przez TouchDelegate w iOS lub minWidth/minHeight w Androidzie. Według Google Material Design, 2024, minimalny obszar dotyku to 48×48 dp. VoiceOver i TalkBack skupiają się na bounding box elementu. Elementy mniejsze niż 30 pt mogą być niedostępne dla fokusu gestami — użytkownik fizycznie nie może w nie trafić palcem.
Skaczący fokus — gdy po akcji (np. usunięciu elementu) fokus przenosi się na początek listy lub systemowy przycisk „Wstecz". Użytkownik VoiceOver traci kontekst. Rozwiązanie: programowo przenosić fokus na element najbliższy usuniętemu.
Niewidoczny fokus — element otrzymuje fokus, ale nie ma wskaźnika wizualnego (użytkownicy klawiatury nie widzą, gdzie się znajdują). W iOS sprawdzaj UIAccessibility.isVoiceOverRunning dla niestandardowych wskaźników. Według Deque University, 2024, niewidoczny fokus to druga najczęstsza przyczyna niepowodzenia audytu dostępności.
Okna modalne — fokus pozostaje na treści tła po otwarciu okna modalnego. W iOS modalny widok automatycznie przechwytuje fokus, jeśli ustawiono modalPresentationStyle = .pageSheet. W Androidzie użyj setFocusable(true) na kontenerze dialogu.
Odwrotny problem: fokus więźnie wewnątrz okna modalnego i nie może wyjść (poza zamknięciem). Jest to dopuszczalne tylko w przypadku okien modalnych — użytkownik musi świadomie zamknąć okno. W przypadku zwykłych ekranów pułapka fokusu to krytyczny błąd. Rozwiązanie: upewnij się, że ostatni element okna modalnego (przycisk „Zamknij") przekazuje fokus z powrotem.
Dla niestandardowych ekranów (mapy, canvasy, gry) automatyczna kolejność geometryczna nie ma zastosowania. Programista musi ręcznie zbudować drzewo dostępności. W iOS w tym celu nadpisuje się metodę UIAccessibilityContainer.
Przykład dla niestandardowego canvasu:
class CanvasView: UIView {
var shapes: [ShapeView] = []
override var accessibilityElements: [Any]? {
get {
// Sortujemy kształty według indeksu Z, a nie geometrii
return shapes.sorted { $0.zIndex < $1.zIndex }
}
set {}
}
}
W Androidzie dla niestandardowego View nadpisz onInitializeAccessibilityNodeInfo:
override fun onInitializeAccessibilityNodeInfo(
info: AccessibilityNodeInfo
) {
super.onInitializeAccessibilityNodeInfo(info)
info.addChild(firstElement)
info.addChild(secondElement)
info.isFocusable = true
}
Dla list dynamicznych (czat, kanał newsów) po dodaniu elementu wywołaj przeniesienie fokusu na pierwszy nowy element. W iOS: UIAccessibility.post(notification: .layoutChanged, argument: newMessage). W Androidzie: sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED).
iOS automatycznie określa obszar fokusu na podstawie ramki elementu. Jeśli element ma transformację (transform, rotation), VoiceOver może skupiać się na nieprawidłowym obszarze. Jawnie ustaw accessibilityFrame we współrzędnych ekranu: element.accessibilityFrame = UIAccessibility.convertToScreenCoordinates(element.bounds, in: element). Gwarantuje to, że VoiceOver podświetli prawidłowy obszar.
Dla ekranów animowanych (UIKit Dynamics, Lottie, SpriteKit) programowy fokus jest szczególnie ważny. VoiceOver nie może zbudować drzewa dostępności dla dynamicznie poruszających się elementów. Ustaw isAccessibilityElement = false na kontenerach animacji i true tylko na elementach interaktywnych wewnątrz.
Testowanie ręczne: włącz VoiceOver (iOS) lub TalkBack (Android), przesuń gestem w prawo przez całą sekwencję. Fokus powinien podążać za kolejnością wizualną — od lewej do prawej, od góry do dołu. Każdy element interaktywny powinien otrzymać fokus dokładnie raz.
Testowanie zautomatyzowane jest trudne, ale możliwe:
func testKeyboardFocusOrder() {
let app = XCUIApplication()
app.launch()
app.textFields["Email"].tap()
// Tab — tylko z klawiaturą sprzętową
}
Dla Android użyj 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()))
}
Najbardziej niezawodną metodą jest test scenariusza UI: wypełnij formularz krok po kroku (Email → Hasło → Wyślij), sprawdzając, czy każdy krok kończy się pomyślnie. Jeśli kolejność fokusu jest zaburzona, scenariusz zakończy się niepowodzeniem przy próbie interakcji z elementem poza fokusem.
Narzędzie Accessibility Inspector w Xcode pokazuje pełne drzewo dostępności. Możesz przejść przez elementy w kolejności VoiceOver i zobaczyć dokładną ścieżkę fokusu. Użyj zakładki „Audit" do automatycznego wyszukiwania naruszeń Focus Order.
Często zadawane pytania
WCAG 2.4.3 (Focus Order) to kryterium sukcesu poziomu A. Wymaga, aby kolejność fokusu zachowywała znaczenie treści podczas nawigacji sekwencyjnej. Naruszenie jest uznawane za krytyczne i blokuje certyfikację.
Ukryte elementy powinny mieć isAccessibilityElement = false w iOS lub visibility = gone/invisible w Androidzie. Po pojawieniu się — programowo przenieś fokus przez UIAccessibility.post(notification: .layoutChanged).
iOS zarządza przez accessibilityElements i shouldGroupAccessibilityElement, Android — przez atrybuty nextFocus* i AccessibilityNodeInfo. Zasada jest taka sama: domyślna kolejność geometryczna z możliwością nadpisania.
Ustaw descendantFocusability = „beforeDescendants" na elemencie głównym i skonfiguruj kolejność w adapterze przez onInitializeAccessibilityNodeInfo dla każdej komórki.
Podłącz klawiaturę sprzętową przez Bluetooth lub USB. W iOS naciśnij Tab, aby przenieść fokus. W Androidzie włącz TalkBack i użyj klawiszy Tab oraz strzałek.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również