4.
Perceivable — Layout & Labeling
Written by Victoria Gonda
In this chapter, you’ll continue making improvements to Taco Tuesday. By the end of the chapter, you’ll have applied what you learned to give the app helpful descriptions, labeling and layouts that are more perceivable for people using screen readers and other assistive technologies.
In Chapter 2, “Hello, Accessibility!”, you learned how to add content descriptions and in Chapter 3, “Testing & Tools”, you learned about screen readers. You should read both chapters before starting this one.
To learn these topics, you’ll continue working on the Taco Tuesday app. Either open the project you’ve been using in the previous chapters or find and open the starter project in the materials for this chapter.
Defining Perceivable
Perceivable is the first category that the WCAG defines for how to make your app accessible. Here’s the definition provided in the WCAG documentation:
1. Perceivable: Information and user interface components must be presentable to users in ways they can perceive.
This means that no matter if someone is consuming your app visually or through audio or touch, they can identify what’s on the screen.
Throughout this book, you’ll see these quotes from the WCAG documentation to guide you on your way and give you a reference if there’s a topic you’d like to look deeper into. When it applies, you’ll also see the level they rated the guideline so you know if you’re reaching Level A, AA or AAA conformity by applying it.
To focus on perceiving the app in a way you might not normally, this chapter will have a heavy focus on screen readers.
Understanding Screen Readers
While you’ll mostly use TalkBack in this book, there are many screen readers out there. For example, BrailleBack gives feedback through a braille readout.
Before moving on, it’s good to have a rough idea of how screen readers work. Here are a couple of tips on screen reader behaviors to help inform the decisions you make for your app:
- When screen readers traverse your screen, they read out text and content descriptions. They also announce an item, such as a button, and any actions that you can perform.
- Readers typically traverse a screen from the top down and from start to end. However, this isn’t always the case — sometimes they follow the view hierarchy. While the experience is consistent on a given device, it can vary widely across devices.
- Finally, readers often have shortcuts to jump to specific parts of the screen. For example, if you have headers, the user can skip ahead to the next header and avoid hearing text that doesn’t interest them.
You’ll use TalkBack and the Accessibility Scanner to verify your work. For a refresher on how to use these tools, go back to Chapter 3, “Testing & Tools”.
Including Effective Text Alternatives
In Chapter 2, “Hello, Accessibility!”, you learned how to add a content description to a view, which informs a screen reader about the contents of that view. Content descriptions are especially important for views without text, such as images and icons. They help your app adhere to the first guideline:
Guideline 1.1 Text Alternatives: Provide text alternatives for any non-text content so that it can be changed into other forms people need, such as large print, braille, speech, symbols or simpler language.
This guideline has one criterion that you need to meet. Lucky for you, you can largely meet this one using content descriptions.
Success Criterion 1.1.1 Non-text Content: All non-text content that is presented to the user has a text alternative that serves the equivalent purpose.
Level A
To round out your understanding, here’s a review of what you covered in Chapter 2, “Hello, Accessibility!”.
In that chapter, you used a linter to identify non-text elements that were missing a content description. This lets accessibility services know what to do with icons like the thumbs-up Try It button.
Build and run the Taco Tuesday app. Run TalkBack on the main screen to hear the content descriptions you added. In fragment_discover.xml, remove the contentDescription
attribute from the ImageView
with the ID discover_button_discard
and run it again to notice the difference.
Imagine if you couldn’t see the screen. That description makes so much of a difference!
Replace the contentDescription
you removed:
android:contentDescription="@string/shared_discard"
This fixes the temporary change you made so you could see how it behaves with the screen reader.
Hiding Decorative Content
In Chapter 2, “Hello, Accessibility!”, you also used android:contentDescription="@null"
to signal to the accessibility services that it can skip that view. This meets one of the detail items under Success Criterion 1.1.1:
Decoration, Formatting, Invisible: If non-text content is pure decoration, is used only for visual formatting, or is not presented to users, then it is implemented in a way that it can be ignored by assistive technology.
You used this on the header image on the main Discover screen. Now, follow the same exercise you did above; try out TalkBack with and without contentDescription
set on the view with ID discover_recipe_image
in fragment_discover.xml.
Make sure you end with that contentDescription
back in place:
android:contentDescription="@null"
There are two different ways you can signal accessibility services that they should ignore a view. Often, they behave the same way but they do behave slightly differently in some cases:
Setting the Content Description to null
This is the option you’ve used before in this book:
android:contentDescription="@null"
In many cases, when used on a non-textual element, accessibility services will ignore the view. In some cases, because this view is still available to the screen reader, accessibility services will still read out some text while exploring the screen. This is especially true if you’re using this on a textual element, such as hiding decorative text. The reader will still announce the decorative text.
Setting the ImportantForAccessibility XML Attribute
Android supports the importantForAccessibility
attribute for API 16 and higher. If a device is running at least Android 4.1, the accessibility services won’t highlight or read a view with this attribute.
This app includes a good place to apply this option. With TalkBack off, make sure you have some recipes saved in the app along with the Thanksgiving Tacos recipe, then go to the list. Notice you can swipe to discard an item.
For this exercise, treat Discard as decorative text.
Now, view the Thanksgiving Tacos recipe with TalkBack turned on. Notice that it reads Discard along with the rest of the card. The same goes for the rest of the favorite recipes.
To make it so the accessibility services ignore this Discard text, open item_try_it_recipe.xml. Find the TextView
with the text @string/shared_discard
, and add the following attribute:
android:importantForAccessibility="no"
Build and run. Look at the Thanksgiving Tacos view with TalkBack again. This time the reader ignores Discard.
Writing Clear Descriptions
You now know a lot about why and where you should use content descriptions. It’s also helpful to know how to write a good content description. The best way to write a description changes depending on what you’re describing.
Describing Icons
Icons are often used as an actionable button or to show a state. When an icon is used as a button, you often use a verb for the description. If you have a pencil icon button to edit a contact, Edit contact is much more meaningful than Pencil. You want to describe what the icon does.
When you have an icon representing a state, the description should generally describe what the state is. For example, you might have a padlock icon where the state, and therefore the content description, is Private.
If you have a toggleable icon that’s both actionable and shows state, you usually describe the state it represents. This is especially true when you’re using a Switch
or Checkbox
with a custom-selectable drawable. The system will know to say whether it’s checked or not. The accessibility service would read: “Favorited, checked” or “Favorited, unchecked”.
Describing Text
In most cases, you don’t need to add content descriptions to text. The text should speak for itself. Exceptions include situations where you have a compound drawable that the user should understand along with the text, or if you have symbols in the text that the user should perceive differently.
For example, if you have the text: “$5/month” you might want the content description to read “$5 per month.”
Describing Images
Perhaps one of the hardest types of items to describe are images. There can be a lot of content in a single image. How much should you share?
You want to be concise enough that it’s consumable, while descriptive enough that the user understands all the essential parts of the image. One pattern that can help you achieve this is object-action-context.
To follow this pattern, you start by describing the main object of the photo. Then, you describe any action that the object might be doing, following the context. An example of this might be “A person eating a taco at a home dining room table.”
You can judge how much detail your user needs. If you need to add more detail, try not to be repetitive. Either include the details inline like “A happy person eating a fish taco at a blue home dining room table with some friends.”
Or, start with the object-action-context pattern, followed by the details. “A person eating a taco at a home dining room table. The person has long hair and is happy.”
You can also apply these guidelines to GIFs and some videos. As you’re writing these content descriptions for your app, keep in mind that you should treat them as copy. People consume these descriptions the same way as other copy in your app.
That’s a lot of information about text alternatives. Now, it’s time to switch gears and talk about layouts.
Creating Adaptable Layouts
How you lay out your app changes the experience for your users. You want your layouts to be adaptable for however people are perceiving them. There’s an entire section of the WCAG about making your app adaptable.
Guideline 1.3 Adaptable: Create content that can be presented in different ways (for example simpler layout) without losing information or structure.
At a high level, you want to organize your information logically. Keep in mind that most screen readers (and humans!) start at the top of the screen, and work their way down. There are lots of other ways you can improve your layout as well.
Notating Headers
Using headers is an effective way of organizing your content. It allows a person to identify and skip directly to the section that interests them. It associates the content in that section with a common topic.
Screen readers also use this organization. A person using assistive technology can navigate and perceive the content in your app more easily if you provide this structure. Headers are one way of meeting Success Criterion 1.3.1:
Success Criterion 1.3.1 Info and Relationships: Information, structure, and relationships conveyed through presentation can be programmatically determined or are available in text.
Level A
Assistive technologies don’t automatically know which TextView
s are headers in your app. You need to inform them. Much like the other topics covered here, you can do this through an attribute in the XML: android:accessibilityHeading
.
Open the app and look at the details of a recipe. There are sections for different parts of the recipe such as the base layer and mixin. These sections each have a label that you can treat as a header.
Go to fragment_recipe_detail.xml. Your next step is to add this heading attribute to each of these TextView
s. There are seven TextView
s with the style @style/AppTheme.TextKicker
. Search for each and add the following attribute:
android:accessibilityHeading="true"
You might see a warning that this attribute is only available on API level 28 and higher. That’s OK! There’s a way to accomplish the same thing using AccessibilityNodeInfoCompat
. You’ll learn more about this class later in the book.
Now, for accessibility services that support it, users can jump between headers. The header is also announced as a header.
While using TalkBack, you can navigate through the headings by opening the local context menu using the swipe up then to the right gesture or Alt-Shift-Space with the keyboard.
Build and run to see your changes. Go to the details for any recipe. With TalkBack turned on, open the local context menu and pick Heading. Navigate this screen as you would normally, this time skipping from heading to heading.
Note: If you don’t see a “Headings” or “Navigation” option, you may need to go to the TalkBack settings an add it under Customize TalkBack menu.
When you’re done, open the local context menu again and switch back to Default so you can move on to the next topic.
Grouping Related Items
Another way you can make the screen easier to navigate is by grouping items. It’s tedious to walk through every single item to get to the content you want. By grouping related items together, you can remove unnecessary steps.
Success Criterion 1.3.2 Meaningful Sequence: When the sequence in which content is presented affects its meaning, a correct reading sequence can be programmatically determined.
Level A
For an example of how this looks, imagine you have a label and a value. Rather than requiring the user to read Total, then swipe to the next item to hear $5.43, they can hear the group: Total: $5.43, altogether.
You can group items by making the shared parent view focusable. Try this by grouping the nacho and spice rating bars with their labels that show on the Detail screen when you have a recipe saved.
Open fragment_recipe_detail.xml and find the LinearLayout
with the ID recipe_nacho_rating_container
. This layout contains both the rating bar and the heading label.
Add the following attributes to the LinearLayout
:
android:focusable="true"
android:focusableInTouchMode="false"
android:focusable="true"
says that this view should be focusable, while android:focusableInTouchMode="false"
makes it so it’s not focusable in touch mode. This means you can focus it when using a screen reader, but not otherwise by touch. You do this because this parent view isn’t clickable.
Add those same two attributes to the view with ID recipe_spice_rating_container
as well. Then, build and run.
Now, when you use TalkBack on these items, it selects the heading and ratings together, rather than traversing them separately. Note that the rating is still missing a content description. You’ll take care of that custom view in Chapter 10, “Robust”.
Allowing All Screen Orientations
It may come as a surprise that supporting both portrait and landscape in your app makes it more accessible.
Success Criterion 1.3.4 Orientation: Content doesn’t restrict its view and operation to a single display orientation, such as portrait or landscape, unless a specific display orientation is essential.
Level AA
Imagine having your device mounted and not being able to change the orientation. For example, you could have a motor disability and have your phone mounted to a wheelchair or you could have it mounted to the dashboard of your car. Imagine your navigation app not rotating to match the orientation of the device. This makes the app much more difficult to use.
Right now, Taco Tuesday’s configuration locks it in portrait. To allow rotation, head to AndroidManifest.xml.
On both the OnboardingActivity
and MainActivity
, delete the android:screenOrientation="portrait"
attribute.
Build and run. You can now rotate the device and see the contents rotate as well. Most of the screens even look okay! The exception is the Discover view. The cards are way too tall for the screen.
Note: Make sure you enable the Auto-rotate option on your device to be able to change screen orientation.
Create a new dimens.xml for landscape. It should be at app/src/main/res/values-land/dimens.xml.
Override the following dimensions in this new file:
<dimen name="discover_card_width">424dp</dimen>
<dimen name="discover_card_height">230dp</dimen>
This changes the card width and height for landscape mode. Build and run to see the improvements.
Great, now the card looks good regardless of your device’s orientation.
Labeling Inputs
Inputs are a place for potential confusion. When working with them, it’s important to make it clear what the input does.
Success Criterion 1.3.5 Identify Input Purpose: The purpose of each input field collecting information about the user can be programmatically determined.
Level AA
On the bottom of the details screen of a saved recipe, there’s a place to add notes.
Right now, the Notes label is a separate TextView
from the input field. With this layout, you need to let the screen reader know that this is a label for the EditText
. To do this, use the labelFor
attribute.
Open fragment_recipe_detail.xml and find the view with ID recipe_detail_notes_label
. This is the label for the input immediately below it. Add the following attribute to the label:
android:labelFor="@+id/recipe_detail_notes_edit_text"
Build and run the app. Now, turn TalkBack on and select the EditText
. It reads “Edit box for notes”, where before it wouldn’t describe what the edit box was for.
Using a View to Describe Input
There’s another way to accomplish this: using TextInputLayout
. Start by deleting the TextView
that serves as a label for this input. Then, surround the input with this view:
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/recipe_detail_notes_input_layout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/recipe_detail_hint_notes">
<com.google.android.material.textfield.TextInputEditText
... />
</com.google.android.material.textfield.TextInputLayout>
This creates a wrapping TextInputLayout
. Note that it has a hint
to describe what the input is for.
Because you show and hide this view depending on the saved state, you need to update this logic before you can run the app. Open RecipeDetailFragment.kt and find showEditableFields()
and hideEditableFields()
.
In showEditableFields()
, delete the following lines:
recipeDetailNotesEditText.visibility = View.VISIBLE
recipeDetailNotesLabel.visibility = View.VISIBLE
And replace them with this one:
recipeDetailNotesInputLayout.visibility = View.VISIBLE
Likewise, in hideEditableFields()
, delete these lines:
recipeDetailNotesEditText.visibility = View.GONE
recipeDetailNotesLabel.visibility = View.GONE
And add this one instead:
recipeDetailNotesInputLayout.visibility = View.GONE
All this does is show and hide your new TextInputLayout
, rather than the separate label and EditText
.
Build and run. Not only does the input have a fancy new style, but it also helpfully reads: Edit box, notes when using the screen reader.
Supporting Text Scaling
As a quick detour from the Text Alternatives and Adaptable categories above, here you have a sneak peek of Distinguishable.
Guideline 1.4 Distinguishable: Make it easier for users to see and hear content including separating foreground from background.
While other chapters will cover most of the criteria under distinguishable, one way to make your text easier for users to see is closely related to the layouts discussed in this chapter.
The way you lay out your views affects whether people can still read the text when it’s scaled.
Success Criterion 1.4.4 Resize text: Except for captions and images of text, text can be resized without assistive technology up to 200 percent without loss of content or functionality.
Level AA
On your device, go to Settings ‣ Accessibility and look for Font size. Depending on your device, this could be at the root level or under Display size and text ‣ Font size and style. Scale the font size up to Largest, then return to the Taco Tuesday app.
You can see that all the text is now larger! This is because, when you set the font size on all these views, you use sp for the unit: scalable pixels. If you were to use another unit, such as dp or density pixels, they wouldn’t scale. Because of this, you always want to use scalable pixels when defining text size.
Once you’ve taken care of that, you need to make sure your view’s height scales correctly. You shouldn’t set a static height on your text views — otherwise, you could have some text cut off.
Take a look at your saved recipes list. If you have a recipe with a long name, it cuts off at the bottom.
The full name of this recipe is: “Chorizo scramble (aka Mandy’s Life-Saving Tacos)”, but you can only see part of it. And the bottom of the last row of words is partly cut off.
The first resolution for this is to make sure the height is wrap_content
. Go to item_try_it_recipe.xml and find the view with the ID item_recipe_title
. Notice that the height is set to 100dp
? Change the height to wrap_content
:
android:layout_height="wrap_content"
Now, build and run the app. The text no longer cuts off.
Making the height of your TextView
s wrap_content
scalable is the minimum you need to do. If you need to restrict how many lines your text has, use the maxLines
attribute, along with ellipsize
. Make sure you only do this with text where it’s okay if the entirety isn’t readable.
Key Points
- The first thing to consider when making your app accessible is whether it’s perceivable.
- You should provide content descriptions for any non-textual elements.
- Notating headers makes your app easier to navigate.
- By grouping related items, users can consume related information together.
- To make your app accessible, you should support all screen orientations.
- Labeling inputs makes it clear what they’re for.
- By using scalable pixels and avoiding static height text views, you allow for scaling text.
Where to Go From Here?
You now have some foundational knowledge about what it means to make your app perceivable. Great work! Continue thinking about how you can apply these things to your own apps and what might be stopping your content from being perceivable.
You’ll continue this journey to make the app more perceivable in the next chapter, where you’ll learn how to handle cues such as animation and color indicators.