Elemento enfocable anidado

This page is not available in the language you requested. You have been redirected to the English version of the page.
Link to this page copied to clipboard
Not for use with personal data

WCAG 2.0 - 4.1.2 A Impacto - Crítico

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

  1. Activar TalkBack / lector de pantalla.
  2. Enfoque la vista y cada uno de sus descendientes.
  3. 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.

tip

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.

Lanza una tarjeta Toast con el contorno de enfoque en el interior
<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.

Lanza una tarjeta Toast con el contorno de enfoque en el exterior
<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.

Lanza una tarjeta Toast con el contorno de enfoque fuera
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.

Lanza una tarjeta Toast con el contorno de enfoque externo
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.

Tarjeta que muestra el texto 'Texto con ancestro accesible que se puede pulsar', delineada en rojo para indicar que no está en el árbol de accesibilidad
<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.

Tarjeta con la leyenda 'Texto con ancestro accesible y clickeable', resaltada en verde para indicar que forma parte del árbol de accesibilidad
<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.

Tarjeta que muestra 'Precio del producto 10,00 $' con contorno de enfoque interno
<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.

Tarjeta que muestra 'Precio del producto $10.00' con contorno de enfoque en el exterior
<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"
        ),
      ),
    ),
  )

Recursos

Deque University Course Pages (Subscription Required)

Otros recursos