Focus Order é a sequência na qual os elementos da interface recebem foco ao navegar com um teclado, Switch Control, VoiceOver ou TalkBack. Em aplicações móveis, a ordem de foco determina como o utilizador se move entre os controlos através de gestos ou botões. De acordo com W3C WCAG 2.2, Success Criterion 2.4.3, 2023, o foco deve seguir uma ordem lógica que preserve o significado do conteúdo. A violação deste princípio é uma das causas comuns de falha numa auditoria de acessibilidade.
Pontos principais
Focus Order é a sequência na qual o utilizador se move entre elementos interativos usando métodos de entrada alternativos: teclado (Tab), Switch Control (passo a passo), VoiceOver (deslizar direita/esquerda) ou TalkBack. Ao contrário de um rato ou ecrã tátil, onde o utilizador seleciona um elemento diretamente, a navegação por foco é linear — cada passo move o foco para o próximo elemento.
De acordo com Apple HIG, 2024, o VoiceOver utiliza a ordem dos elementos na árvore de acessibilidade, que é construída com base na localização visual: canto superior esquerdo → canto inferior direito. Se o ecrã tiver um layout complexo (colunas, Grid, ZStack), a árvore pode não corresponder à ordem visual.
Princípio da WCAG 2.4.3: “Se uma página web pode ser navegada sequencialmente através de secções e a ordem de foco afeta o significado, então o foco deve seguir uma ordem que preserve o significado e a operacionalidade”. Exceção: conteúdo dinâmico onde o foco pode saltar para chamar a atenção (alertas, janelas modais).
Um utilizador de Switch Control (pessoas com deficiências motoras) move-se pelos elementos automaticamente — ciclo após ciclo. Se a ordem estiver quebrada, o utilizador demora 3 vezes mais a preencher o formulário. De acordo com Deque University, 2024, um Focus Order correto reduz o tempo de preenchimento do formulário em 60% para utilizadores de tecnologias de assistência.
Atenção especial — janelas modais. Depois de abrir uma modal, o foco deve mover-se imediatamente para o primeiro elemento interativo dentro da modal (normalmente um botão “Fechar” ou “Confirmar”). Ao fechar — voltar ao elemento que acionou a modal. Este é um requisito da WCAG 2.4.3 e também um erro comum.
No iOS, o VoiceOver constrói automaticamente a ordem com base na geometria: os elementos são ordenados por Y, depois por X. Para ecrãs com estrutura complexa, esta ordem pode estar incorreta — o desenvolvedor deve intervir.
Principais ferramentas:
Exemplo de definição de ordem personalizada para um cartão de produto:
class ProductCardView: UIView {
let titleLabel = UILabel()
let priceLabel = UILabel()
let buyButton = UIButton()
override var accessibilityElements: [Any]? {
get {
return [titleLabel!, priceLabel!, buyButton!]
}
set {}
}
}
Para movimento programático do foco após uma ação:
UIAccessibility.post(
notification: .layoutChanged,
argument: newlyAddedItem
)
A propriedade shouldGroupAccessibilityElement é útil para cartões em coleções. Se definida como true no cartão pai, o VoiceOver percebe o cartão inteiro como um único elemento. O utilizador pode tocar duas vezes para ativar o cartão completo, ou configurar o rotor para navegação interna. Recomendado para UICollectionViewCell e UITableViewCell.
No Android, o TalkBack também utiliza a ordem geométrica, mas é dada prioridade aos atributos explícitos nextFocus*. Estes atributos são definidos em XML ou programaticamente:
| Atributo | Propósito | Exemplo |
|---|---|---|
| nextFocusDown | Elemento ao navegar para baixo | @+id/field_email |
| nextFocusUp | Elemento ao navegar para cima | @+id/field_name |
| nextFocusLeft | Elemento à esquerda | @+id/btn_back |
| nextFocusRight | Elemento à direita | @+id/btn_next |
Exemplo para um formulário de registo:
<EditText
android:id="@+id/field_email"
android:nextFocusDown="@+id/field_password" />
<EditText
android:id="@+id/field_password"
android:nextFocusDown="@+id/btn_submit" />
Para RecyclerView, a ordem de foco é dinâmica — determinada pelo adaptador. Se as células tiverem uma estrutura complexa, defina descendantFocusability = “beforeDescendants” e defina a ordem no nó do item da lista. Para Jetpack Compose, a ordem de foco é definida através de Modifier.focusOrder() e FocusOrder. Prioridade: previous (filho), next (seguinte), chave personalizada.
Se um elemento for demasiado pequeno para o foco (menor que 44pt), aumente a área de impacto através de TouchDelegate no iOS ou minWidth/minHeight no Android. De acordo com Google Material Design, 2024, a área tátil mínima é de 48×48dp. O VoiceOver e o TalkBack focam-se na caixa delimitadora do elemento. Elementos com menos de 30pt podem estar inacessíveis para o foco gestual — o utilizador fisicamente não consegue tocá-los.
Foco saltitante — quando após uma ação (por exemplo, eliminar um elemento) o foco se move para o início da lista ou para o botão “Voltar” do sistema. O utilizador do VoiceOver perde o contexto. Solução: mover programaticamente o foco para o elemento mais próximo do eliminado.
Foco invisível — um elemento recebe foco mas não há indicador visual (os utilizadores de teclado não veem onde estão). No iOS, verifique UIAccessibility.isVoiceOverRunning para indicadores personalizados. De acordo com Deque University, 2024, o foco invisível é a segunda causa mais comum de falha numa auditoria de acessibilidade.
Modais — o foco permanece no conteúdo de fundo após abrir uma modal. No iOS, a vista modal captura automaticamente o foco se modalPresentationStyle = .pageSheet estiver definido. No Android, use setFocusable(true) no contentor do diálogo.
O problema inverso: o foco fica preso dentro de uma modal e não consegue sair (exceto fechando-a). Isto é aceitável apenas para janelas modais — o utilizador deve fechar a janela intencionalmente. Para ecrãs normais, a armadilha de foco é um erro crítico. Solução: certifique-se de que o último elemento da modal (o botão “Fechar”) devolve o foco.
Para ecrãs personalizados (mapas, canvas, jogos) a ordem geométrica automática não é aplicável. O desenvolvedor deve construir a árvore de acessibilidade manualmente. No iOS, o método UIAccessibilityContainer é sobrescrito para este fim.
Exemplo para um canvas personalizado:
class CanvasView: UIView {
var shapes: [ShapeView] = []
override var accessibilityElements: [Any]? {
get {
// Ordenar formas por Z-index, não por geometria
return shapes.sorted { $0.zIndex < $1.zIndex }
}
set {}
}
}
No Android, para uma View personalizada, sobrescreva onInitializeAccessibilityNodeInfo:
override fun onInitializeAccessibilityNodeInfo(
info: AccessibilityNodeInfo
) {
super.onInitializeAccessibilityNodeInfo(info)
info.addChild(firstElement)
info.addChild(secondElement)
info.isFocusable = true
}
Para listas dinâmicas (chat, feed de notícias), depois de adicionar um elemento, mova o foco para o primeiro novo elemento. No iOS: UIAccessibility.post(notification: .layoutChanged, argument: newMessage). No Android: sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED).
O iOS determina automaticamente a área de foco com base no frame do elemento. Se um elemento tiver uma transformação (transform, rotation), o VoiceOver pode focar a área errada. Defina explicitamente accessibilityFrame em coordenadas de ecrã: element.accessibilityFrame = UIAccessibility.convertToScreenCoordinates(element.bounds, in: element). Isto garante que o VoiceOver destaca a área correta.
Para ecrãs animados (UIKit Dynamics, Lottie, SpriteKit), o foco programático é especialmente importante. O VoiceOver não consegue construir uma árvore de acessibilidade para elementos em movimento dinâmico. Defina isAccessibilityElement = false nos contentores de animação e true apenas nos elementos interativos no interior.
Teste manual: ative o VoiceOver (iOS) ou TalkBack (Android), deslize para a direita por toda a sequência. O foco deve seguir a ordem visual — da esquerda para a direita, de cima para baixo. Cada elemento interativo deve receber foco exatamente uma vez.
Testes automatizados são difíceis mas possíveis:
func testKeyboardFocusOrder() {
let app = XCUIApplication()
app.launch()
app.textFields["Email"].tap()
// Tab — apenas com teclado físico
}
Para Android, utilize o 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()))
}
O método mais fiável é um teste de cenário de UI: preencha o formulário passo a passo (Email → Palavra-passe → Enviar), verificando se cada passo é concluído com sucesso. Se a ordem de foco estiver quebrada, o cenário falhará ao tentar interagir com um elemento fora de foco.
A ferramenta Accessibility Inspector no Xcode mostra a árvore de acessibilidade completa. Pode percorrer os elementos na ordem do VoiceOver e ver o caminho de foco exato. Utilize o separador “Audit” para deteção automática de violações de Focus Order.
Perguntas frequentes
WCAG 2.4.3 (Focus Order) é um critério de sucesso de nível A. Requer que a ordem de foco preserve o significado do conteúdo durante a navegação sequencial. A violação é considerada crítica e bloqueia a certificação.
Os elementos ocultos devem ter isAccessibilityElement = false no iOS ou visibility = gone/invisible no Android. Quando aparecerem, mova programaticamente o foco através de UIAccessibility.post(notification: .layoutChanged).
O iOS gere através de accessibilityElements e shouldGroupAccessibilityElement, o Android através dos atributos nextFocus* e AccessibilityNodeInfo. O princípio é o mesmo: ordem geométrica por defeito com possibilidade de substituição.
Defina descendantFocusability = “beforeDescendants” no elemento raiz e configure a ordem no adaptador através de onInitializeAccessibilityNodeInfo para cada célula.
Conecte um teclado físico através de Bluetooth ou USB. No iOS prima Tab para mover o foco. No Android ative o TalkBack e use a tecla Tab e as setas.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também