Noções Básicas de Acessibilidade
10 exemplos para você começar com acessibilidade mobile - 7 básicos e 3 intermediários. Toda tela de produção deve passar nessas verificações do VoiceOver e TalkBack antes de ser lançada.
Busque em todas as páginas da documentação
10 exemplos para você começar com acessibilidade mobile - 7 básicos e 3 intermediários. Toda tela de produção deve passar nessas verificações do VoiceOver e TalkBack antes de ser lançada.
Acessibilidade é integrada aos componentes principais do React Native. Nenhuma instalação extra é necessária para rótulos, papéis e anúncios no Expo SDK 57:
npx create-expo-app@latest MyA11yApp --template blank-typescript
cd MyA11yAppHabilite leitores de tela em um dispositivo físico - simuladores funcionam para testes rápidos, mas o tempo do TalkBack e o comportamento do rotor do VoiceOver diferem em hardware real.
| Plataforma | Como habilitar | Gesto de teste rápido |
|---|---|---|
| iOS | Ajustes → Acessibilidade → VoiceOver | Clique triplo no botão lateral (se configurado) |
| Android | Ajustes → Acessibilidade → TalkBack | Atalho das teclas de volume (dependente do dispositivo) |
Ferramentas: Estes exemplos visam Expo SDK 57 (
expo~57.0.4), React Native 0.86.0 e React 19.2.3. As props de semântica se aplicam aos componentes principaisView,Text,Pressable,TextInput,ImageeSwitch.
Leitores de tela anunciam o nome acessível de um elemento focado. Se um Pressable não tem texto visível e nenhum rótulo, os usuários ouvem "botão" sem contexto.
import { Pressable, Text, StyleSheet } from "react-native";
export function SaveButton({ onPress }: { onPress: () => void }) {
return (
<Pressable
accessibilityRole="button"
accessibilityLabel="Save changes"
onPress={onPress}
style={styles.button}
>
<Text style={styles.label}>Save</Text>
</Pressable>
);
}
const styles = StyleSheet.create({
button: { backgroundColor: "#2563eb", padding: 14, borderRadius: 8 },
label: { color: "#fff", fontWeight: "600", textAlign: "center" },
});Text frequentemente se torna o nome padrão - accessibilityLabel explícito ainda é recomendado para controles apenas com íconeGlifos são sem sentido para tecnologia assistiva, a menos que você os descreva.
import { Pressable, Text, StyleSheet } from "react-native";
export function IconButton({
label,
onPress,
}: {
label: string;
onPress: () => void;
}) {
return (
<Pressable
accessibilityRole="button"
accessibilityLabel={label}
hitSlop={12}
onPress={onPress}
style={styles.hit}
>
<Text style={styles.icon} accessibilityElementsHidden>
⋮
</Text>
</Pressable>
);
}
const styles = StyleSheet.create({
hit: { minWidth: 44, minHeight: 44, alignItems: "center", justifyContent: "center" },
icon: { fontSize: 22 },
});accessibilityElementsHidden em um glifo decorativo Text impede anúncios duplicados ("mais, mais opções")hitSlop ou minWidth / minHeight de 44 alcança o alvo de toque mínimo sem inflar o layoutaccessibilityHint apenas quando a ação não for óbvia - "Abre as configurações da conta" para um ícone de engrenagemPapéis mapeiam para características da plataforma - botão, link, cabeçalho, interruptor e mais.
import { Pressable, Text, View, StyleSheet } from "react-native";
export function ArticleCard({ title, onOpen }: { title: string; onOpen: () => void }) {
return (
<View style={styles.card}>
<Text accessibilityRole="header" style={styles.title}>
{title}
</Text>
<Pressable accessibilityRole="link" accessibilityLabel={`Read ${title}`} onPress={onOpen}>
<Text style={styles.link}>Read article</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
card: { padding: 16, gap: 8 },
title: { fontSize: 20, fontWeight: "700" },
link: { color: "#2563eb" },
});| Papel | Expectativa do usuário | Erro comum |
|---|---|---|
button | Ativa uma ação | Usar em Text estático |
link | Navega para conteúdo relacionado | Usar em exclusão destrutiva |
header | Título da seção, navegação rápida | Pular cabeçalhos em telas longas |
image | Gráfico descritivo | Deixar o padrão em ícones decorativos |
switch | Alterna o estado ligado/desligado | Usar em Pressable em vez de Switch |
Comunique disabled, selected, checked e expanded explicitamente - não confie apenas na cor.
import { Pressable, Text, StyleSheet } from "react-native";
type FilterChipProps = {
label: string;
selected: boolean;
onPress: () => void;
};
export function FilterChip({ label, selected, onPress }: FilterChipProps) {
return (
<Pressable
accessibilityRole="button"
accessibilityLabel={label}
accessibilityState={{ selected }}
onPress={onPress}
style={[styles.chip, selected && styles.chipSelected]}
>
<Text style={styles.chipText}>{label}</Text>
</Pressable>
);
}
const styles = StyleSheet.create({
chip: { paddingHorizontal: 12, paddingVertical: 8, borderRadius: 16, backgroundColor: "#e2e8f0" },
chipSelected: { backgroundColor: "#2563eb" },
chipText: { fontWeight: "600" },
});accessibilityState={{ selected: true }}Switch nativo, prefira o próprio componente - ele gerencia o estado checked automaticamenteaccessibilityState={{ disabled: isLoading }} enquanto o trabalho assíncrono estiver em andamentoOrnamentos de fundo, ícones redundantes e divisores visuais não devem roubar o foco.
import { Image, Text, View, StyleSheet } from "react-native";
export function PromoBanner() {
return (
<View style={styles.banner}>
<Image
source={require("./assets/sparkle.png")}
style={styles.sparkle}
accessible={false}
importantForAccessibility="no"
/>
<Text accessibilityRole="header">Summer sale - 20% off</Text>
<View
accessible={false}
importantForAccessibility="no-hide-descendants"
style={styles.rule}
/>
</View>
);
}
const styles = StyleSheet.create({
banner: { padding: 16, gap: 8 },
sparkle: { width: 24, height: 24, position: "absolute", right: 8, top: 8 },
rule: { height: 1, backgroundColor: "#cbd5e1" },
});| Prop | Plataforma | Efeito |
|---|---|---|
accessible={false} | iOS + Android | Remove o elemento do foco de acessibilidade |
importantForAccessibility="no" | Android | Oculta uma única visualização |
importantForAccessibility="no-hide-descendants" | Android | Oculta a visualização e todos os filhos |
accessibilityElementsHidden | iOS | Oculta a subárvore do VoiceOver |
Avatares, gráficos e fotos de produtos precisam de contexto; ativos puramente decorativos não.
import { Image, StyleSheet } from "react-native";
export function UserAvatar({ uri, name }: { uri: string; name: string }) {
return (
<Image
source={{ uri }}
style={styles.avatar}
accessibilityRole="image"
accessibilityLabel={`${name} profile photo`}
/>
);
}
export function DecorativeDivider() {
return (
<Image
source={require("./assets/wave.png")}
style={styles.wave}
accessible={false}
importantForAccessibility="no"
/>
);
}
const styles = StyleSheet.create({
avatar: { width: 48, height: 48, borderRadius: 24 },
wave: { width: "100%", height: 8 },
});accessibilityLabel em imagens - "Gráfico de barras mostrando receita em alta de 12% no 3º trimestre"expo-imageQuando o status muda sem mover o foco, informe explicitamente os usuários de leitores de tela.
import { useEffect, useState } from "react";
import { AccessibilityInfo, Pressable, Text, View, StyleSheet } from "react-native";
export function CartBadge({ count }: { count: number }) {
const [lastAnnounced, setLastAnnounced] = useState(count);
useEffect(() => {
if (count !== lastAnnounced) {
AccessibilityInfo.announceForAccessibility(
count === 0 ? "Cart is empty" : `${count} items in cart`,
);
setLastAnnounced(count);
}
}, [count, lastAnnounced]);
return (
<View
accessibilityRole="text"
accessibilityLabel={`Cart, ${count} items`}
accessibilityLiveRegion="polite"
style={styles.badge}
>
<Text>{count}</Text>
</View>
);
}
const styles = StyleSheet.create({
badge: { minWidth: 28, minHeight: 28, borderRadius: 14, backgroundColor: "#dc2626", alignItems: "center", justifyContent: "center" },
});AccessibilityInfo.announceForAccessibility envia uma mensagem única para VoiceOver/TalkBackaccessibilityLiveRegion="polite" (Android) agrupa alterações de texto não urgentesassertive para erros e alertas sensíveis ao tempo - veja Acessibilidade em FormuláriosUm cartão com título, preço e avaliação deve ser uma unidade lógica - ou deliberadamente dividido para escaneamento.
import { Pressable, Text, View, StyleSheet } from "react-native";
export function ProductRow({
name,
price,
rating,
onPress,
}: {
name: string;
price: string;
rating: string;
onPress: () => void;
}) {
return (
<Pressable
accessibilityRole="button"
accessibilityLabel={`${name}, ${price}, rated ${rating} out of five`}
onPress={onPress}
style={styles.row}
>
<View importantForAccessibility="no-hide-descendants" accessibilityElementsHidden>
<Text style={styles.name}>{name}</Text>
<Text style={styles.price}>{price}</Text>
<Text style={styles.rating}>{rating} ★</Text>
</View>
</Pressable>
);
}
const styles = StyleSheet.create({
row: { padding: 16, borderBottomWidth: StyleSheet.hairlineWidth, borderColor: "#e2e8f0" },
name: { fontSize: 16, fontWeight: "600" },
price: { color: "#64748b" },
rating: { color: "#f59e0b" },
});Consulte AccessibilityInfo para configurações de redutor de movimento e leitor de tela - incorpore acessibilidade na UI padrão, não envie um "modo acessível" separado.
import { useEffect, useState } from "react";
import { AccessibilityInfo, Text, View } from "react-native";
export function useA11yPreferences() {
const [screenReaderEnabled, setScreenReaderEnabled] = useState(false);
const [reduceMotionEnabled, setReduceMotionEnabled] = useState(false);
useEffect(() => {
AccessibilityInfo.isScreenReaderEnabled().then(setScreenReaderEnabled);
AccessibilityInfo.isReduceMotionEnabled().then(setReduceMotionEnabled);
const screenReaderSub = AccessibilityInfo.addEventListener(
"screenReaderChanged",
setScreenReaderEnabled,
);
const motionSub = AccessibilityInfo.addEventListener(
"reduceMotionChanged",
setReduceMotionEnabled,
);
return () => {
screenReaderSub.remove();
motionSub.remove();
};
}, []);
return { screenReaderEnabled, reduceMotionEnabled };
}
export function MotionAwareHint() {
const { screenReaderEnabled } = useA11yPreferences();
return (
<View>
<Text accessibilityRole="header">Orders</Text>
{screenReaderEnabled ? (
<Text>Swipe right through the list to hear each order.</Text>
) : null}
</View>
);
}Percorra cada tela com VoiceOver e TalkBack usando um script repetível.
Per-screen checklist (5 minutes):
1. Turn on VoiceOver / TalkBack
2. Swipe through every focusable element - no unlabeled "button"
3. Activate primary action - hear confirmation or focus move
4. Rotate to landscape (tablet) - order still logical
5. Increase font scale to maximum - no clipped primary actions
6. Turn on Reduce Motion - no required animation to complete task// Dev-only helper - log the tree in __DEV__
import { AccessibilityInfo, Pressable, Text } from "react-native";
export function DevA11yLog({ label }: { label: string }) {
if (!__DEV__) return null;
return (
<Pressable
accessibilityRole="button"
accessibilityLabel={`Log accessibility tree for ${label}`}
onPress={() => AccessibilityInfo.isScreenReaderEnabled().then(console.log)}
>
<Text>Log a11y state ({label})</Text>
</Pressable>
);
}accessibilityLabel - os mesmos seletores que a tecnologia assistiva utilizaaccessible={true} (padrão para Text e elementos clicáveis) ou que contêm filhos acessíveis se tornam paradas de foco.testID é apenas para automação - ele não substitui rótulos para leitores de tela, a menos que seus testes o simulem incorretamente.| Comportamento | VoiceOver (iOS) | TalkBack (Android) |
|---|---|---|
| Gesto de exploração | Deslizar para a direita / esquerda | Deslizar para a direita / esquerda |
| Ativar | Toque duplo | Toque duplo |
| Modos de leitura | Rotor (cabeçalhos, links) | Leitura de controles/granularidade |
| Dicas | accessibilityHint falado após o rótulo | Dica menos consistentemente usada |
| Regiões ao vivo | Limitado vs web | accessibilityLiveRegion suportado |
Teste ambas as plataformas - passar apenas no iOS não é suficiente para as expectativas de acessibilidade da Play Store.
O HIG da Apple e o Material recomendam alvos efetivos de 44×44 pt / 48×48 dp:
<Pressable hitSlop={10} style={{ minHeight: 44, justifyContent: "center" }} />Ícones visuais pequenos são aceitáveis quando a região de toque é ampliada.
TextInput precisa de accessibilityLabel ou accessibilityLabelledBy.accessible com texto sobreposto causam interrupção.disabled ou accessibilityState={{ disabled: true }}.accessibilityViewIsModal nas raízes de sobreposição para que o conteúdo de fundo seja ignorado.eslint-plugin-react-native-a11y perde a ordem de foco quebrada e dicas enganosas.testID é bom para elementos sem nome visível para o usuário, mas não deixe que ele substitua rótulos ausentes.aria-* para equivalentes de acessibilidade.accessibilityLabel, accessibilityRole) para consistência multiplataforma no SDK 57.allowFontScaling e sobrevivência do layoutVersões da Stack: Esta página foi escrita para React 19.2.3, React Native 0.86.0 e Expo SDK 57 (
expo~57.0.4).
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026