Accessibility Audit (Android)
Generated 2026-05-11 · Costco Android
Why this matters
Every ImageView, ImageButton, Image, Icon, and IconButton that conveys meaning needs an android:contentDescription (XML) or contentDescription = stringResource(...) (Compose). Without it:
- Users with vision impairments — TalkBack announces "image" or skips silently. The user cannot tell what the icon is for. They can't tap it confidently. Effectively, the feature doesn't exist for them.
- Automation tests — Espresso, UI Automator, Compose UI Test, MagicPod, BrowserStack App Live, and every other framework rely on accessibility labels as the primary way to find elements.
onView(withContentDescription("Cart"))is the right way to write a cart-button assertion. Without labels, tests fall back towithId(R.id.cart_image)— brittle, breaks on any layout refactor. - Compliance — WCAG 1.1.1 (Non-text Content), Section 508, AODA, EN 301 549. Missing labels are a direct fail.
- Voice control + Switch Access — Android's voice-control feature relies on contentDescription to know what the user means by "tap cart" — without labels, the user can't drive the app by voice.
Decision matrix — when to label, when to mark decorative
| Image purpose | What to set |
|---|---|
| Conveys information (logo, status icon, illustration) | contentDescription = stringResource(R.string.…) |
| Acts on tap (icon button, cart icon, close button) | contentDescription describing the action ("Close", "Add to cart") |
| Pure decoration (separator, background, ornamental) | XML: android:importantForAccessibility="no"Compose: contentDescription = null with explicit comment |
| Repetitive (50 product cards each with their own image) | Label on the card, not each child element. Card describes "View Bounty paper towels, $19.99" |
Audit findings
Missing contentDescription on actionable XML images
15 sites identified — each is an icon that does something on tap, with no announcement for assistive technology.
| File | Line | Icon | Action |
|---|---|---|---|
| Costco/.../view_warehouse_details.xml | 64 | ic_blue_driving_directions | Open driving directions to warehouse |
| Costco/.../view_warehouse_details.xml | 86 | ic_blue_telephone | Call warehouse |
| Costco/.../row_search_suggestion.xml | 26 | arrow_insert (ImageButton) | Insert search suggestion |
| Costco/.../view_warehouse_comingsoon_details.xml | 95 | ic_blue_driving_directions | Driving directions (duplicate) |
| Costco/.../view_standalone_gas_details.xml | 96 | ic_blue_driving_directions | Driving directions |
| Costco/.../fragment_event_detail.xml | 19 | Event image (ViewFlipper) | Visual content carrying meaning |
| Costco/.../list_item_saving_card.xml | 51 | ic_calendar_blue | Indicates expiration date |
| Costco/.../fragment_warehouse_offer_details.xml | 24 | Offer detail image | Offer artwork carrying meaning |
| Costco/.../list_item_warehouse_offer.xml | 50 | ic_calendar_blue | Date indicator |
| Costco/.../view_main_toolbar.xml | 54 | ic_logo_transparent | Logo (decorative — should be marked so) |
| Costco/.../quick_action.xml | 36 | ic_user_icon | User profile shortcut |
Empty / null contentDescription on actionable elements (anti-pattern)
| File | Line | Issue |
|---|---|---|
| Costco/.../fragment_findastore.xml | 11 | RelativeLayout with contentDescription="" |
| Costco/.../fragment_findastore.xml | 21 | MapView with contentDescription="" — TalkBack reads as silent |
| Costco/.../view_warehouse_navigation_button.xml | 20 | ImageView with contentDescription="" |
| Costco/.../list_item_nearby_warehouses.xml | 44 | Gas-station icon with contentDescription="@null" on an actionable element (drawable name suggests action, not decoration) |
Hardcoded font sizes — Dynamic Type / "Large Text" ignored
WCAG 1.4.4 requires text to scale up to 200%. The following sites hardcode android:textSize or Compose fontSize instead of using MaterialTheme.typography / ?attr/textAppearanceBody1. Users with the system "Large Text" setting see clipped, overlapping content.
| File | Line | textSize |
|---|---|---|
| Costco/.../fragment_onboarding.xml | 56 | 24sp (header) |
| Costco/.../fragment_onboarding.xml | 74 | 16sp (body) |
| Costco/.../fragment_onboarding.xml | 96 | 16sp (instruction) |
| Costco/.../view_warehouse_details.xml | 127, 141, 154 | 14sp × 3 (warehouse details) |
| Costco/.../fragment_feature_highlights.xml | 87 | 24sp (headline) |
| Costco/.../view_warehouse_comingsoon_details.xml | 81 | 12sp (caption) |
| shared/geofence/.../layout_notification_collapsed.xml | 22, 31 | 14sp + 13sp (notification) |
| feature/warehouse/.../layout_notification_expanded.xml | 23, 32 | 14sp + 13sp (notification) |
Plus 30+ more sites across the codebase. Recommendation: sweep all hardcoded textSize values; replace with ?attr/textAppearanceHeadline6, textAppearanceBody1, etc., or with explicit Material/Material3 type tokens.
Hardcoded text strings — bypass localization + accessibility services
Hardcoded strings in XML are not picked up by translators, can't be intercepted by per-app language preferences (Android 13+), and break TalkBack's pronunciation in non-English locales. Sample (likely many more):
- row_media_card.xml:36 —
"Auto Sales Event" - view_warehouse_details.xml:140 —
"Warehouse coming soon!" - view_warehouse_comingsoon_details.xml:117 —
"warehouse coming soon"(note inconsistent capitalization) - list_item_nearby_warehouses.xml:102 —
"Warehouse coming soon" - view_warehouse_gas_comingsoon_details.xml:78 —
"warehouse coming soon"
Hot files (most violations)
- Costco/src/main/res/layout/view_warehouse_details.xml — 6 violations (3 missing img descriptions + 3 hardcoded fonts)
- Costco/src/main/res/layout/fragment_onboarding.xml — 5 violations (all DYNAMIC_TYPE)
- shared/geofence/.../layout_notification_collapsed.xml — 4 violations (all fonts)
- Costco/src/main/res/layout/view_warehouse_comingsoon_details.xml — 4 violations (1 missing desc + 3 fonts)
- Costco/src/main/res/layout/fragment_feature_highlights.xml — 4 violations (all fonts)
How fixing this helps automation
The same labels that announce intent to users are what automation frameworks use to find elements. After labeling:
| Framework | Before (brittle) | After (resilient) |
|---|---|---|
| Espresso | onView(withId(R.id.fragment_warehouseDetails_telephone_image)) | onView(withContentDescription("Call warehouse")) |
| Compose UI Test | onNodeWithTag("phone_icon") | onNodeWithContentDescription("Call warehouse") |
| UI Automator (E2E) | By.res(...) | By.desc("Call warehouse") |
| BrowserStack App Live / SauceLabs | XPath into resource-id | Accessibility ID query |
The automation team can drop XPath/resource-id selectors entirely once labels exist. Tests stop breaking on every layout refactor. New features can ship UI tests without first wiring up resource IDs. Cross-platform tests (Appium, etc.) can use the same accessibility ID on Android and iOS — your labels become a stable contract.
Migration plan
- Sprint 1 — Add
android:contentDescriptionto the 15 actionable XML icons. Each goes via a string resource so localization picks it up. Sweepable in 1 day. - Sprint 1 — Replace
contentDescription=""and incorrect"@null"with proper resource strings orandroid:importantForAccessibility="no". - Sprint 2 — Sweep hardcoded
android:textSizevalues; convert to text-appearance attributes. Snapshot test atfontScale = 2.0. - Sprint 2 — Sweep hardcoded UI strings into
strings.xml. - Ongoing — Enable Android Lint rule
ContentDescriptionaterrorseverity in CI. EnableHardcodedTextrule. Re-baseline. - Ongoing — Add a
fontScale = 2.0snapshot test pass in the existing screenshot test plan.
Verification
- Manual TalkBack pass — turn TalkBack on, navigate Home → Warehouse Detail → Offers. Every actionable element should announce its purpose.
- Accessibility Scanner app (free, Google) — scan key screens, fix flagged sites until count reaches zero on critical screens.
- Lint baseline —
ContentDescriptionrule should report 0 new findings on any PR. - UI test coverage gain — pick one feature and rewrite its UI test to use
withContentDescriptioninstead of resource IDs; verify it survives a layout refactor.