Elemento enfocable anidado
Los elementos enfocables no deben estar anidados dentro de elementos padre clicables, porque no anunciarán adecuadamente su función a tecnologías de asistencia como TalkBack y Voice Access.
Acerca de esta regla
La regla Nested Focusable Element verifica todos los controles que pueden recibir el foco de accesibilidad y que no son interactivos. Si dicho control está contenido dentro de un elemento padre clicable, es inherentemente interactivo, pero no anunciará un rol. Por lo tanto, este elemento se marcará como una violación de accesibilidad.
Impacto para los usuarios
Cuando las tecnologías de asistencia no anuncian un rol, los usuarios ciegos o con baja visión que dependen de tecnologías de asistencia como TalkBack no sabrán que pueden activar una acción. Por ejemplo, TalkBack se enfoca en un elemento interno y anuncia su texto "Launch a Toast", y nada más. No anuncia que es un botón ni que el usuario debe tocar dos veces el elemento para activarlo.
Confirmación
- Activar TalkBack / lector de pantalla.
- Enfoque la vista y cada uno de sus descendientes.
- Ocurrirá uno de los siguientes casos:
- Inaccesible: TalkBack lee el texto pero no anuncia un rol ni la capacidad para interactuar.
- Accesible: TalkBack leerá todo el texto. También anunciará un rol y/o indicará cómo interactuar con el elemento.
Cómo solucionarlo
No anide usted un control enfocable y no interactivo dentro de un control padre clicable. Asegúrese de que el control anidado no tenga ninguna propiedad establecida que lo haga enfocable. De esta manera, el elemento padre interactivo puede obtener el foco y anunciar su rol. Vea ejemplos específicos a continuación.
Para evitar un hallazgo de accesibilidad falso positivo, asegúrese de que las vistas contenedoras que no hacen nada al ser tocadas no sean clicables. Nuestras herramientas no pueden determinar si una vista clicable tiene una acción programada asociada, por lo que debemos asumir que la tiene para señalar un comportamiento potencialmente inaccesible.
Examine las vistas contenedoras circundantes, como los Frame Layouts, Card Views o Drawers, para asegurarse de que cualquier vista que no deba tener una acción asociada no esté configurada para permitir clics.
XML
Ejemplo de fallo
En este ejemplo, el MaterialCardView es clickeable, pero el elemento hijo LinearLayout recibe el enfoque en su lugar. Cuando los usuarios de tecnología de asistencia interactúan con el LinearLayout, se activa la acción de clic de la tarjeta pero no se indica que sea clickeable. Esto da lugar a una experiencia que no es accesible mediante tecnología de asistencia, porque el rol o la descripción del rol del elemento no indican que hay una acción presente ni qué hará la interacción.
<com.google.android.material.card.MaterialCardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="true"
android:layout_margin="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/launch_a_toast"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>Guía de remediación: Establezca LinearLayout:focusable en falso
Asegúrese de que LinearLayout no tenga la propiedad enfocable configurada en verdadero. En su estado predeterminado es falso.
<com.google.android.material.card.MaterialCardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/launch_a_toast"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>Compose
Ejemplo de fallo
En este ejemplo, el Card es clickeable, pero el elemento hijo Row recibe el foco en su lugar. Cuando los usuarios de tecnología de asistencia interactúan con el Row, esto activa la acción de clic de la tarjeta pero no anuncia que es clickeable. Esto da lugar a una experiencia que no es accesible mediante tecnología de asistencia, porque el rol o la descripción del rol del elemento no indican que hay una acción presente ni qué hará la interacción.
Card(
modifier = Modifier
.clickable {
launchToast(context)
}
) {
Row(
modifier = Modifier
.focusable()
.padding(10.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.CenterHorizontally)
) {
Text(stringResource(R.string.launch_a_toast))
}
}Guía de remediación: Eliminar la capacidad de que el elemento descendiente reciba el enfoque
Aquí se ha eliminado la posibilidad de que el elemento Row reciba el enfoque. El Card ahora tendrá texto disponible para ser leído, porque recibirá información de Text. El Card ahora podrá recibir el foco con tecnología de asistencia y anunciará el contenido del texto. La tarjeta informará al usuario que es pulsable mediante una descripción de rol como "Doble toque para activar", lo que resulta en una experiencia accesible para el usuario.
Card(
modifier = Modifier
.clickable {
launchToast(context)
}
) {
Row(
modifier = Modifier
.padding(10.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.CenterHorizontally)
) {
Text(stringResource(R.string.launch_a_toast))
}
}.NET MAUI
Ejemplo de fallo
En este ejemplo, el diseño es clicable; sin embargo, ha sido ocultado en el árbol de accesibilidad. Al eliminar esta vista, se pierde el contexto de que funciona como un botón, y los usuarios de tecnologías de asistencia no sabrán cómo pueden interactuar con ella.
<Grid
HorizontalOptions="FillAndExpand"
RowDefinitions="Auto,Auto"
RowSpacing="10"
VerticalOptions="FillAndExpand"
AutomationProperties.IsInAccessibleTree="false">
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Function}"/>
</Grid.GestureRecognizers>
<Label
Grid.Row="1"
FontAttributes="Bold"
FontSize="16"
HorizontalOptions="CenterAndExpand"
VerticalOptions="FillAndExpand">
<Label.FormattedText>
<FormattedString>
<Span Text="Text with a clickable parent"/>
</FormattedString>
</Label.FormattedText>
</Label>
</Grid>Guía de remediación: Utilice la propiedad SemanticProperties.Hint
Los errores de accesibilidad se pueden abordar de una de dos maneras. La forma más sencilla es eliminar el elemento AutomationProperties.IsInAccessibleTree="false" de la vista de cuadrícula. Si eso entra en conflicto con otras funcionalidades de la aplicación, otra forma de proporcionar a los usuarios las acciones de clic es establecer la propiedad SemanticProperties.Hint con información útil sobre el tipo de vista con la que el usuario está interactuando, como "Botón" o "Interruptor". Esto permitirá que la tecnología de asistencia agregue correctamente la indicación "Doble toque para activar" a su lectura y brinde a los usuarios la retroalimentación que necesitan para interactuar con la vista.
<Grid
HorizontalOptions="FillAndExpand"
RowDefinitions="Auto,Auto"
RowSpacing="10"
VerticalOptions="FillAndExpand">
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Function}"/>
</Grid.GestureRecognizers>
<Label
Grid.Row="1"
FontAttributes="Bold"
FontSize="16"
HorizontalOptions="CenterAndExpand"
VerticalOptions="FillAndExpand">
<Label.FormattedText>
<FormattedString>
<Span Text="Text with a clickable parent"/>
</FormattedString>
</Label.FormattedText>
</Label>
</Grid>React Native
Ejemplo de fallo
En este ejemplo, TouchableOpacity es interactivo, pero el elemento anidado View tiene accessible={true}, lo que impide que el elemento principal reciba el foco de accesibilidad. El View recibirá el foco en lugar del elemento interactivo TouchableOpacity, pero no anunciará que es interactivo. Los usuarios de tecnología de asistencia escucharán la información del producto, pero no sabrán que pueden tocar para ver los detalles.
<TouchableOpacity
accessibilityRole="button"
style={styles.productCard}
onPress={() => navigateTo("product")}
>
<View accessible={true}>
<Text>Product Price</Text>
<Text>$10.00</Text>
</View>
</TouchableOpacity>Guía de remediación: Agregar propiedades de accesibilidad
En este ejemplo remediado, TouchableOpacity tiene las propiedades de accesibilidad adecuadas configuradas directamente en él. Las propiedades accessible={true} y accessibilityRole="button" garantizan que las tecnologías de asistencia lo anuncien como un elemento interactivo, de modo que los usuarios comprendan el contenido y sepan que pueden interactuar con él.
<TouchableOpacity
accessible={true}
accessibilityRole="button"
style={styles.productCard}
onPress={() => navigateTo("product")}
>
<Text>Product Price</Text>
<Text>$10.00</Text>
</TouchableOpacity>Flutter
Ejemplo de fallo
En el siguiente ejemplo, el GestureDetector funciona como un envoltorio padre clickeable para el Card, pero no puede recibir el foco por sí mismo. El Card funciona como el contenedor de sus elementos hijos, por lo que recibirá el foco y anunciará todo el contenido de los elementos hijos. Cuando los usuarios de tecnología de asistencia ponen el foco en el Card, no se anunciará "Toca dos veces para activar" al usuario, ya que el Card en sí no es clicable, aunque el elemento padre GestureDetector sí lo sea.
<img src="../../img/nested_focusable_element_flutter_failing_1.png" alt="Tarjeta que muestra el texto "Texto con padre clicable", con contorno de enfoque en el interior" />
GestureDetector(
onTap: () => ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Card clicked"),
behavior: SnackBarBehavior.floating,
)
),
child: const Card(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Text(
"Card with clickable parent"
),
),
),
);Recomendación de remediación: No utilice usted un envolvente GestureDetector
Aquí, el InkWell actúa como un elemento hijo clicable del Card y ocupará la misma dimensión que el Card. El InkWell ahora sirve como el contenedor que presenta todo el contenido de texto a los usuarios de tecnología de asistencia. El resultado es un elemento clicable que anuncia «Toque dos veces para activar» para indicar que es interactivo.
<img src="../../img/nested_focusable_element_flutter_inapplicable.png" alt="Tarjeta con texto: "Texto con padre clicable", con contorno de enfoque en el exterior" />
Card(
child: InkWell(
onTap: () => ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Card clicked"),
behavior: SnackBarBehavior.floating,
)
),
child: const Padding(
padding: EdgeInsets.all(16.0),
child: Text(
"Text with clickable parent"
),
),
),
)