The Backlog Item That Became a Legal Requirement
Are You in Scope? Four Questions
| Question | What the directive says | Engineering consequence |
|---|---|---|
| Is it a consumer service in a listed sector? | Banking, e-commerce, e-books, passenger transport, electronic communications (Article 2(2)) | If yes, the app is part of the service |
| Are you a microenterprise? | Fewer than 10 persons and annual turnover or balance sheet not exceeding EUR 2 million; service providers of that size "shall be exempt" | Exempt from the service requirements, not from good practice |
| Were contracts signed before 28 June 2025? | They "may continue without alteration until they expire, but no longer than five years from that date" | A deadline for legacy contracts, not a pass for new releases |
| Are you relying on older products? | A transitional period "ending on 28 June 2030" for products lawfully used before that date | Plan the migration now; 2030 is a hard stop |
What "Accessible" Means, and Why the Answer Just Changed
| Criterion | Level | What it means in an Android app |
|---|---|---|
| 2.4.11 Focus Not Obscured (Minimum) | AA | Sticky bars and sheets must not hide the focused element |
| 2.5.7 Dragging Movements | AA | Every drag (reorder, slider, swipe-to-dismiss) needs a single-pointer alternative |
| 2.5.8 Target Size (Minimum) | AA | Targets at least 24 by 24 CSS pixels, with spacing exceptions |
| 3.2.6 Consistent Help | A | Help and contact entry points in a consistent place |
| 3.3.7 Redundant Entry | A | Do not make users re-type what they already entered in the flow |
| 3.3.8 Accessible Authentication (Minimum) | AA | No cognitive-test logins; allow paste and password managers |
Enforcement Is National, and Some of It Is Criminal
| Member State | Instrument | Maximum exposure for a non-compliant service |
|---|---|---|
| Germany | BFSG § 37 | Administrative fine up to EUR 100,000 for offering or providing a non-compliant service |
| Ireland | S.I. No. 636/2023 | On conviction on indictment, a fine up to EUR 60,000, imprisonment up to 18 months, or both |
The Android Checklist, in Compose
contentDescription "conveys in text what the meaning of the icon is." Set it to null for decorative images: that tells the framework the element "does not have associated actions or state."
- Size targets generously. WCAG 2.2 sets a 24-pixel floor; Google's Compose guidance says to "set the minimum size to 48dp to correctly follow the Material Design accessibility guidelines." Material components already do; custom clickable Boxes are where targets shrink.
- Give every drag a tap. Reorderable lists, swipe-to-delete and custom sliders need a visible single-pointer alternative (2.5.7) and, for screen reader users, custom accessibility actions.
- Group what belongs together. A product card read as six separate focus stops is technically labelled and practically unusable; merge descendants and expose actions.
- Mark structure. Section titles as headings, so TalkBack users can jump between them.
- Let authentication be pasted. Support password managers and passkeys; never block paste on credential fields (3.3.8).@Composable
fun ProductCard(product: Product, onAddToCart: () -> Unit, onFavourite: () -> Unit) {
Card(
Modifier.semantics(mergeDescendants = true) {
// One focus stop for the card, with its secondary action exposed to TalkBack.
customActions = listOf(
CustomAccessibilityAction("Add to favourites") { onFavourite(); true }
)
}
) {
Image(painterResource(product.imageRes), contentDescription = null) // decorative: the title names it
Text(product.name, Modifier.semantics { heading() })
Text(product.priceLabel)
IconButton(onClick = onAddToCart) { // 48dp target by default
Icon(Icons.Filled.Add, contentDescription = "Add ${product.name} to cart")
}
}
}
// 2.5.7 Dragging Movements: reordering must not require a drag.
@Composable
fun ReorderableRow(item: Item, index: Int, onMove: (from: Int, to: Int) -> Unit) {
Row(
Modifier.semantics {
customActions = listOf(
CustomAccessibilityAction("Move up") { onMove(index, index - 1); true },
CustomAccessibilityAction("Move down") { onMove(index, index + 1); true },
)
}
) {
Text(item.title, Modifier.weight(1f))
IconButton(onClick = { onMove(index, index - 1) }) { // visible single-pointer path
Icon(Icons.Filled.KeyboardArrowUp, contentDescription = "Move ${item.title} up")
}
}
}Make Accessibility a Build Gate, Not an Audit
performClick runs the checks as well, so existing UI tests become accessibility tests for free. View-based screens get the same from Espresso: "You can enable and configure accessibility testing using the AccessibilityChecks class," and "by default, the checks run when you perform any view action defined in ViewActions."
Automation catches missing labels, small targets and low contrast. It does not catch a confusing focus order or a flow that is technically labelled and practically impossible. For that, keep two manual habits: a TalkBack walkthrough of every changed flow before release, and an Accessibility Scanner pass, which "uses the Accessibility Test Framework and provides specific suggestions after looking at content labels, clickable items, contrast, and more."
Then close the loop the directive asks for. Keep the test results, the walkthrough notes and the known-issues list together, because Article 13's duty to "explain how the services meet the applicable accessibility requirements" is much easier to discharge with evidence than with good intentions.@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
@Before
fun enableChecks() {
composeTestRule.enableAccessibilityChecks() // needs ui-test-junit4-accessibility, Compose 1.8.0+
}
@Test
fun checkout_isAccessible() {
composeTestRule.setContent { CheckoutScreen(cart = sampleCart) }
// Explicit check of the whole tree...
composeTestRule.onRoot().tryPerformAccessibilityChecks()
// ...and every action from here on is checked too.
composeTestRule.onNodeWithText("Pay now").performClick()
}Key Takeaways
- 1Since 28 June 2025 the EAA has covered consumer banking, e-commerce, e-books, passenger transport and electronic communications services, explicitly including their mobile apps.
- 2Only microenterprises providing services (fewer than 10 persons, EUR 2 million or less turnover or balance sheet) are exempt; legacy contracts run at most five years and products used before the date only until 28 June 2030.
- 3EN 301 549 v4.1.1, published September 2026, moves the benchmark to WCAG 2.2; v3.2.1 (WCAG 2.1 AA) remains the cited reference until v4.1.1 is cited in the Official Journal.
- 4Web views inside an app are assessed as software under clause 11, not as web pages.
- 5Penalties are national: up to EUR 100,000 in Germany, and a criminal offence with fines up to EUR 60,000 and up to 18 months in Ireland.
- 6Turn on enableAccessibilityChecks() in Compose tests and AccessibilityChecks in Espresso so common failures break the build.
Frequently Asked
Does the European Accessibility Act apply to Android apps?
Yes, for in-scope consumer services. The directive names mobile device-based services, including mobile applications, for passenger transport, and its e-commerce definition covers services concluded through mobile apps. Banking, e-books and electronic communications are also in scope.
Which WCAG version does the EAA require?
The directive sets outcomes; the harmonised standard is EN 301 549. v3.2.1 (WCAG 2.1 AA) remains the cited reference until the Commission cites v4.1.1, published September 2026, which adopts WCAG 2.2.
Can Compose tests check accessibility automatically?
Yes, from Compose 1.8.0. Add ui-test-junit4-accessibility, call enableAccessibilityChecks() on the AndroidComposeTestRule, and actions or tryPerformAccessibilityChecks() run the Accessibility Test Framework.
Ready to architect your next Android app?
ANDROID-ARCHITECT generates production-ready Kotlin code, architecture blueprints, and CI/CD configurations from plain-language descriptions. Start building for free.