How to Add Content Descriptions in Compose - A Guide for Android Devs

Eevis - Nov 16 '23 - - Dev Community

When it comes to the contentDescription-attribute, I've noticed a couple of things Android devs do that are wrong and make apps harder to use with assistive technology. The first thing I see a lot is setting the content description of an icon to the icon name, such as icon_cat_sleeping. Another thing is suppressing the warnings Android Studio gives about missing content descriptions.

I decided to write about this theme, and in this blog post, you'll learn about content descriptions: the problems and solutions. And yes, you'll understand why those things I mentioned in the previous paragraph are problematic.

Let's start discussing content description, then continue with how to write one, and finally, how to set the content description.

What Content Description Is

When I talk about content description, I mean text alternative. If you have any background in web development or, e.g., using any social media platforms, you might have heard about the word "alt text". They mean the same thing and different environments have different names for this concept.

In Android, the attribute is called contentDescription, and I think that content description is actually a very explanatory name for what we're trying to accomplish with it: describing the content of an image or a graphic.

One thing that's important to remember is who we are describing the content for: Users who utilize the accessibility APIs. This means, for example, screen reader and voice access users. Screen reader users rely on the content descriptions for visual content (that they usually can't see, depending on their sight level), and voice access users can use these values as labels to interact with elements, such as buttons.

Content descriptions should be added for meaningful visual elements like images, icons, and graphs. For some of the elements, it's more straightforward, but for, for example, different types of graphs, it's more complicated. This blog post won't go into details on adding content descriptions for graphs, but I've written a blog post as one example around the theme if you're interested: More Accessible Graphs with Jetpack Compose Part 1: Adding Content Description.

Now, if you've been working with testing tools such as Appium, you might remember adding the test id as contentDescription. Unfortunately, some tools use the so-called "accessibility id," which maps to contentDescription on Android. On iOS, there is a separate property called accessibilityId. This is a problem because content description actually overrides the text value for many elements, meaning that for these elements, a screen reader user would hear the value of a button being button_submit (the id set for testing) instead of the "Submit" that's a text for that button.

I've had to fix issues like this, and my solution was to check that if a screen reader is running, we would set the element's content as contentDescription; otherwise, use that testing id. That is not an optimal solution, but it was the best we could do at the time. And this would fail with some of the automated accessibility checks, even if it works for real screen reader users.

Content Description is not Available for All Users

The content description is invisible to most users. This means that it's not the all-powerful solution to fix accessibility issues.

A common misconception is that adding it as a label to an icon would make it available to every user. But that's not the case - a user who is not using any sort of assistive technology can't see it, nor can someone with, for example, a keyboard. So, it's available just for assistive technologies that utilize the accessibility API underneath - including, but not limited to, screen readers, switch access, and voice navigation.

I want to underline this because many users usually would benefit from things like text labels with icons, but they tend to be dismissed when asking for them because of the abovementioned misconception. I'm one of them, so I've experienced this firsthand.

How to Write a Good Content Description

The actual content for the contentDescription attribute varies, depending on the purpose and type of the element. As mentioned, more complex visualizations are outside the scope of this blog post, so we will discuss meaningful and decorative images. We'll also discuss icons as a separate, unique use case.

One important thing to note is that whenever you write a content description, remember to localize it!

Meaningful Images

What does a "meaningful image" mean? It means any image (or graphic) that has meaning and adds something to the content. It includes icons, and pictures that contain instructions is another good example. The text alternative (so, content description) should contain what the image is trying to communicate.

An image's content description is also affected by the context where the image is. WebAIM has a list of instructions for writing text alternatives:

The alt attribute should typically:

  • be accurate and equivalent in representing content and function.
  • be succinct. Content (if any) and function (if any) should be presented as succinctly as possible, without sacrificing accuracy. Typically, only a few words are necessary, though rarely a short sentence or two may be appropriate.
  • not be redundant or provide the same information as text near the image.
  • not include phrases like "image of ..." or "graphic of ...", etc. This would be redundant since screen readers already announce "graphic" along with the alt text. If the fact that an image is a photograph or illustration, etc. is important content, it may be useful to include this in alternative text.

Source and read more: WebAIM: Alternative text

Decorative Images

In the previous section, we've talked about meaningful images. Sometimes, illustrations or images are there for purely decorative purposes and don't have any other meaning. In these cases, you should mark the content description as null:

Image(
  ...
  contentDescription=null
)
Enter fullscreen mode Exit fullscreen mode

Now, you might wonder, why not leave it as an empty string? The reason is that when left empty, the accessibility API doesn't get the info that this is decorative and keeps this image in the accessibility tree. This leads to a situation where, when a user encounters that image, they would hear "Unnamed" instead of not encountering it in the first place.

That "Unnamed" is redundant information and can be really confusing. Is that image really decorative, and a developer didn't know how to set it as such? Or is it meaningful, but the developer forgot to add the actual text alternative?

In the Compose world, components like Image and Icon require contentDescription to be set explicitly. However, it was (or, is) possible to leave the attribute out in the View world and XML-layouts.

That would lead to similar situations, with "Unnamed" being announced. This would also create a warning in the Android Studio about missing content descriptions. It's possible to suppress that warning, and I've fixed multiple instances where the warning was suppressed.

So, don't suppress that warning - set the content description to null or provide a descriptive content description, depending on the image.

Icons

As mentioned, icons are a special case. Icons are often used as the only action indicator (like an edit button with just some icon representing editing). I usually have problems with this because of how I process information. I keep asking for text labels but typically don't get them. But this is not a blog post about text labels; it's about content descriptions, so let's move on.

When an icon is the only thing inside a button, it's even more critical to include meaningful content descriptions because it's the text used for the button. So, when a screen reader user encounters a button with an icon that is a quill, they'd hear something like "Button, Edit" instead of "Button, icon underscore quill" if the content description is set to the icon's name. Or, in the case I mentioned in the intro, "Icon underscore cat underscore sleeping." Remember that whatever text you enter there will be what non-sighted screen reader users hear (or read from braille) - they can't see the icon.

So, when adding a content description for an icon, ask yourself if it's used to convey meaning or if it's purely decorative. If it's the latter, use null. Otherwise, describe the action or meaning, not the actual icon. So, usually, an "X" icon represents something like "Close," and a chevron pointing to the left means something like "Back".

How to Set the Content Description

contentDescription-attribute

You can set the content description through the contentDescription-attribute for Image and Icon -components. Other components don't have this attribute, so you'll need the help of semantics or clearAndSetSemantics-modifiers.

semantics and clearAndSetSemantics-modifiers

Both semantics and clearAndSetSemantics modifiers change the element's semantics. The difference is that the first builds on top of existing semantics, and the latter clears all the other semantics.

While Image and Icon components have the contentDescription, other elements, such as graphics created with Canvas, would require using these modifiers. Also another case is text with an emoji - for example, if your app is a chat app with reactions, and you're using Text-element for displaying the reaction, you'd need to set the content description for those Text components.

You can use the semantics-modifier in any element and set the content description within its semantic properties lambda:

Canvas(
    modifier = Modifier.semantics {
        contentDescription = "Put the content description here"
    }
) {
    ...
}
Enter fullscreen mode Exit fullscreen mode

Wrapping Up

In this blog post, we've covered content descriptions, what they are, how to write them, and how you can set them.

Do you have questions? Or comments? Please do ask or share!

Links in Blog Post

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .