accessibilityLabel & accessibilityRole
Semántica correcta para componentes personalizados. Un recetario para conectar nombres , roles , estados e indicaciones para que VoiceOver y TalkBack entiendan tus primitivas del sistema de diseño - no solo llamadas a Pressable dispersas en las características.
Tarjeta de referencia rápida - lista para copiar y pegar.
import type { PressableProps } from "react-native" ;
import { Pressable, Text, StyleSheet } from "react-native" ;
type AppButtonProps = PressableProps & {
label : string ;
hint ?: string ;
variant ?: "primary" | "ghost" ;
};
export function AppButton ({
label ,
hint ,
variant = "primary" ,
accessibilityState ,
... rest
} : AppButtonProps ) {
return (
< Pressable
accessibilityRole = "button"
accessibilityLabel = {label}
accessibilityHint = {hint}
accessibilityState = {accessibilityState}
style = {[styles.base, variant === "primary" ? styles.primary : styles.ghost]}
{ ... rest}
>
< Text style = {styles.text} importantForAccessibility = "no" >
{label}
</ Text >
</ Pressable >
);
}
const styles = StyleSheet. create ({
base: { minHeight: 44 , paddingHorizontal: 16 , borderRadius: 8 , justifyContent: "center" },
primary: { backgroundColor: "#2563eb" },
ghost: { backgroundColor: "transparent" },
text: { color: "#fff" , fontWeight: "600" , textAlign: "center" },
});
Cuándo usar esto: Cada vez que envuelves Pressable, View o gesture handlers en una interfaz reutilizable - tarjetas, botones con iconos, chips, steppers y acciones de bottom-sheet necesitan un contrato semántico explícito.
Una fila de Configuración compuesta con icono líder, título, subtítulo y chevron - anunciada una sola vez, activada una sola vez.
import { Pressable, Text, View, StyleSheet } from "react-native" ;
type SettingsRowProps = {
title : string ;
subtitle ?: string ;
onPress : () => void ;
};
export function SettingsRow ({ title , subtitle , onPress } : SettingsRowProps ) {
const label = subtitle ? `${ title }, ${ subtitle }` : title;
return (
< Pressable
accessibilityRole = "button"
accessibilityLabel = {label}
accessibilityHint = "Abre configuración"
onPress = {onPress}
style = {styles.row}
>
< View
style = {styles.content}
importantForAccessibility = "no-hide-descendants"
accessibilityElementsHidden
>
< Text style = {styles.title}>{title}</ Text >
{subtitle ? < Text style = {styles.subtitle}>{subtitle}</ Text > : null }
< Text style = {styles.chevron} accessible = { false }>
›
</ Text >
</ View >
</ Pressable >
);
}
const styles = StyleSheet. create ({
row: { paddingVertical: 14 , paddingHorizontal: 16 , minHeight: 48 },
content: { flexDirection: "row" , alignItems: "center" , gap: 8 },
title: { flex: 1 , fontSize: 16 , fontWeight: "600" },
subtitle: { fontSize: 14 , color: "#64748b" },
chevron: { fontSize: 20 , color: "#94a3b8" },
});
Lo que esto demuestra:
El Pressable padre es propietario del nombre accesible único
Los elementos secundarios visuales ocultos del árbol - sin "Notificaciones, Notificaciones, chevron"
accessibilityHint añade propósito sin repetir el título
minHeight: 48 satisface la orientación de destino táctil
Los componentes del sistema de diseño deberían aceptar y propagar props de accesibilidad sin eliminarlas.
import type { PressableProps, ViewProps } from "react-native" ;
import { Pressable, View } from "react-native" ;
type CardProps = ViewProps & {
onPress ?: PressableProps [ "onPress" ];
accessibilityLabel : string ;
};
export function Card ({ onPress , accessibilityLabel , children , ... viewProps } : CardProps ) {
if (onPress) {
return (
< Pressable
accessibilityRole = "button"
accessibilityLabel = {accessibilityLabel}
onPress = {onPress}
style = {viewProps.style}
>
{children}
</ Pressable >
);
}
return (
< View
accessibilityRole = "summary"
accessibilityLabel = {accessibilityLabel}
{ ... viewProps}
>
{children}
</ View >
);
}
Usa ComponentProps<typeof Pressable> en TypeScript - ver Typing Components & Props
Por defecto, accessibilityLabel es obligatorio en wrappers interactivos - haz que olvidar una etiqueta sea un error de tipo
Cuando el texto de la etiqueta es visible, refuerza con nativeID en lugar de duplicar strings.
import { Text, TextInput, View, StyleSheet } from "react-native" ;
export function Field ({ label , value , onChangeText } : { label : string ; value : string ; onChangeText : ( t : string ) => void }) {
const labelId = `field-${ label . replace ( / \s / g , "-" ). toLowerCase () }` ;
return (
< View style = {styles.field}>
< Text nativeID = {labelId} style = {styles.label}>
{label}
</ Text >
< TextInput
value = {value}
onChangeText = {onChangeText}
accessibilityLabelledBy = {labelId}
accessibilityLabel = {label}
/>
</ View >
);
}
const styles = StyleSheet. create ({
field: { gap: 4 },
label: { fontWeight: "600" },
});
Proporciona tanto accessibilityLabelledBy como accessibilityLabel para objetivos de SO más antiguos
nativeID debe ser único por pantalla - prefiéralo con ID de pantalla o formulario en asistentes
Patrón de UI Rol Fuente de etiqueta CTA primario buttonTexto visible o accessibilityLabel Texto que navega linkContexto de destino - "Ver detalles del pedido" Título de sección headerTexto del encabezado Fila de alternancia con Switch switch en SwitchTítulo de fila; estado en Switch Checkbox personalizado checkboxNombre del elemento + accessibilityState.checked Campo de búsqueda search"Buscar productos" Párrafo estático text o ningunoNormalmente auto desde Text Icono de barra de herramientas buttonObligatorio accessibilityLabel
< Pressable
accessibilityRole = "button"
accessibilityLabel = "Agregar a favoritos"
accessibilityState = {{
disabled: isLoading,
selected: isFavorite,
busy: isLoading,
}}
/>
Clave de estado Se anuncia como Usar cuando disabled"atenuado" / no disponible Bloqueo asincrónico o validación selected"seleccionado" Pestañas, filtros, controles segmentados checked"marcado" / "desmarcado" Alternadores personalizados expanded"expandido" / "contraído" Acordeones busyEn progreso Envío en vuelo
import { Pressable, Text, View, StyleSheet } from "react-native" ;
export function QuantityStepper ({
value ,
onIncrement ,
onDecrement ,
} : {
value : number ;
onIncrement : () => void ;
onDecrement : () => void ;
}) {
return (
< View
accessible
accessibilityRole = "adjustable"
accessibilityLabel = "Cantidad"
accessibilityValue = {{ text: String (value) }}
accessibilityActions = {[
{ name: "increment" , label: "Aumentar cantidad" },
{ name: "decrement" , label: "Disminuir cantidad" },
]}
onAccessibilityAction = {( e ) => {
if (e.nativeEvent.actionName === "increment" ) onIncrement ();
if (e.nativeEvent.actionName === "decrement" ) onDecrement ();
}}
style = {styles.row}
>
< Pressable accessibilityLabel = "Disminuir" onPress = {onDecrement}>
< Text >−</ Text >
</ Pressable >
< Text >{value}</ Text >
< Pressable accessibilityLabel = "Aumentar" onPress = {onIncrement}>
< Text >+</ Text >
</ Pressable >
</ View >
);
}
const styles = StyleSheet. create ({
row: { flexDirection: "row" , alignItems: "center" , gap: 12 },
});
El rol adjustable se adapta a los ajustes del rotor de iOS - empareja con accessibilityActions para steppers personalizados
Los botones físicos permanecen para usuarios videntes - no elimines controles visibles
< Pressable
accessibilityRole = "button"
accessibilityLabel = { `Mensajes, ${ unread } sin leer` }
accessibilityHint = "Abre la bandeja de entrada"
/>
Incluye conteos dinámicos en la etiqueta cuando transmiten estado
No llames a announceForAccessibility en cada encuesta - resérvalo para transiciones significativas
Configurar un contenedor accessible fusiona elementos secundarios en un anuncio en iOS.
Dejar elementos secundarios expuestos proporciona navegación granular - elige por intención de UX.
importantForAccessibility en Android anula la visibilidad del elemento secundario cuando los padres están ocultos.
Las pistas describen qué sucede después - "Abre hoja de pago", no "Botón"
Omite pistas cuando la etiqueta es autoexplicativa - la verbosidad ralentiza a los usuarios expertos del lector de pantalla
Localiza pistas con la misma prioridad que las etiquetas
Hace cumplir las etiquetas en CI con eslint-plugin-react-native-a11y - luego prohíbe Pressable sin procesar en características:
features/** → debe importar { AppButton, IconButton } desde @/shared/ui
Ver Custom ESLint Rules for RN y Building an Internal Component Library .
Botones de gesture-handler - los wrappers GestureDetector no son accesibles hasta que un Pressable o vista accessible interno expone la semántica.
Roles duplicados - accessibilityRole="button" tanto en padre como en elemento secundario causa anuncios de "botón" anidados.
Fuentes de iconos - los puntos de código de glifos no se hablan; siempre proporciona una etiqueta de texto.
Strings traducidas en etiquetas - interpola valores, no suposiciones de orden de oración del inglés.
accessibilityElementsHidden en Android - empareja con importantForAccessibility para ocultamiento confiable.
¿Deberían los componentes personalizados establecer accessibilityLabel de forma predeterminada?
Sí para primitivas interactivas - requiere la propiedad label o un default sensato del texto de elementos secundarios.
Los contenedores de diseño estático generalmente no necesitan etiqueta a menos que resuman una tarjeta compleja.
¿link o button para la navegación?
Usa link cuando la acción abre contenido relacionado u otra pantalla en un contexto de navegación.
Usa button para modales, envíos y confirmaciones destructivas - coincide con el modelo mental del usuario en lectores de pantalla móviles.
¿Cómo pruebo la semántica sin un dispositivo?
React Native Testing Library: getByRole("button", { name: "Save" }) - ver Mobile Testing Basics .
Prueba en dispositivo aún requerida para pistas, agrupamiento y granularidad de TalkBack.
Versiones de Stack: Esta página fue escrita para React 19.2.3 , React Native 0.86.0 y Expo SDK 57 (expo ~57.0.4).