Nested Focusable Element

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 Impact - Critical

Focusable elements should not be nested within clickable parent elements, because they will not properly announce their role to assistive technologies such as TalkBack and Voice Access.

Rule Logic: Nested Focusable Element

Nested Focusable Element is only applicable for accessibility focusable controls that are not interactive but represent a non-accessibility focusable control ancestor that is interactive.

About this Rule

This rule checks for an interactive role on focusable elements.

Impact to Users

Assistive technology normally announces the text content of a clickable element such as a button. However, if there is a focusable item inside of the clickable element, the assistive technology stops scanning and focuses on that inner item instead. This leaves the button with nothing to announce, and so it gets skipped over. The content of the inner item will be announced. The problem is that the user is not made aware of the role of the inner item, or that they can interact with it to trigger an action.

Confirmation

  1. Turn on TalkBack / screen reader.
  2. Focus on the view and each of its descendents.
  3. One of the following will happen:
    • Inaccessible: TalkBack reads the text but does not announce a role or ability to interact.
    • Accessible: TalkBack will read all text. It will also announce a role and/or indicate how to interact with the element.

How to Fix

XML

Failing Example

In this example, the MaterialCardView is clickable, but the child LinearLayout gets focus instead. When assistive technology users interact with the LinearLayout, it activates the card's click action but doesn't announce that it's clickable. This results in an experience that is not accessible with assistive technology, because the element's role or role description do not indicate that an action is present or what the interaction will do.

Launch a Toast card with the focus outline inside
<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>

Remediation Guidance: Make LinearLayout:focusable False

Ensure that LinearLayout does not have a focusable property set to true. In its default state it is false.

Launch a Toast card with the focus outline outside
<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

Failing Example

In this example, the Card is clickable but the child Row is focused instead. When assistive technology users interact with the Row, this acitvates the card's click action but doesn't announce that it is clickable. This results in an experience that is not accessible with assistive technology, because the element's role or role description do not indicate that an action is present or what the interaction will do.

Launch a Toast card with the focus outline outside
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))
    }
}

Remediation Guidance: Remove Ability to Focus on Descendent Element

Here, the ability to focus has been removed from the Row element. The Card will now have text to speak, because it will get information from the Text. The Card will now be able to gain focus with assistive technology and will announce the text content. The card will convey to the user that it is clickable via a role description such as "Double-tap to activate", resulting in an accessible experience for the user.

Launch a Toast card with the focus outline outside
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

Failing Example

In this example, the layout is clickable; however, it has been hidden in the accessibility tree. By removing this view, we have stripped the context that this view is functioning as a button, and assistive technology users will not be aware of how they can interact with this view.

Card reading 'Text with accessible clickable ancestor' outlined in red to indicate it is not in the accessiblity tree
<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>

Remediation Guidance: Use SemanticProperties.Hint Property

Accessibility errors can be addressed in one of two ways. The most straightforward way is to remove the AutomationProperties.IsInAccessibleTree="false" element from the grid view. If that conflicts with other functionality in the app, another way of providing users with the click actions is to set the SemanticProperties.Hint property with useful context about the type of view a user is interacting with - such as "Button" or "Switch". This will allow assistive technology to properly add the "Double-tap to activate" hint to its readout and give users the feedback they need to interact with the view.

Card reading 'Text with accessible clickable ancestor' outlined in green to indicate it is in the accessiblity tree
<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

Failing Example

In this example, TouchableOpacity is clickable but the nested View has accessible={true}, which prevents the parent from gaining accessibility focus. The View will receive focus instead of the clickable TouchableOpacity, but won't announce that it is interactive. Assistive technology users will hear the product information, but won't know they can tap to view details.

Card reading 'Product Price $10.00' with focus outline on the inside
<TouchableOpacity
    accessibilityRole="button"
    style={styles.productCard}
    onPress={() => navigateTo("product")}
>
    <View accessible={true}>
        <Text>Product Price</Text>
        <Text>$10.00</Text>
    </View>
</TouchableOpacity>

Remediation Guidance: Add Accessibility Properties

In this remediated example, TouchableOpacity has proper accessibility properties set directly on it. The properties accessible={true} and accessibilityRole="button" ensure assistive technologies announce it as an interactive element, so users understand the content and that it is actionable.

Card reading 'Product Price $10.00' with focus outline on the outside
<TouchableOpacity
    accessible={true}
    accessibilityRole="button"
    style={styles.productCard}
    onPress={() => navigateTo("product")}
>
    <Text>Product Price</Text>
    <Text>$10.00</Text>
</TouchableOpacity>

Flutter

Failing Example

In the example below, the GestureDetector acts as a clickable parent wrapper to the Card but is not able to gain focus on its own. The Card acts as the container for its children, so it will gain focus and announce all the content from the child nodes. When assistive technology users focus on the Card, it will not announce "Double-tap to activate" to the user since the Card itself is not clickable, even though the parent GestureDetector is.

Card reading 'Text with clickable parent', with focus outline on the inside
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"
      ),
    ),
  ),
);

Remediation Guidance: Do not use a GestureDetector wrapper

Here, the InkWell acts as a clickable child of the Card and will occupy the same dimension as the Card. The InkWell now serves as the container that presents all the text content to assistive technology users. The result is one clickable element that announces "Double-tap to activate" to indicate it is interactive.

Card reading 'Text with clickable parent', with focus outline on the outside
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"
        ),
      ),
    ),
  )

Resources

Deque University Course Pages (Subscription Required)

Other Resources