Automated WCAG accessibility checks for Jetpack Compose — catch violations in CI before your users do.
Accessibility is hard!!!
- Dual-engine — Static lint rules (no emulator) + runtime test assertions (rendered UI)
- Compose-first — Built specifically for Jetpack Compose, not retrofitted from the View system
- Zero config — One Gradle line per engine. No annotation processors, no setup
- WCAG-mapped — Every rule links to its WCAG 2.1 success criterion
- Low noise — 5 touch-target exemptions, 1% pixel noise filter, and Compose API guards prevent false positives
- Tested — 30+ tests across both engines. Contrast pipeline uses CIE-94 color distance (same approach as Google's ATF internally)
dependencies {
lintChecks("io.github.lehan0328:touchstone-lint:0.1.0")
}./gradlew lintThat's it. Touchstone will flag accessibility violations alongside your existing lint checks:
ProfileScreen.kt:21: Error: Image is missing required contentDescription
parameter. Pass a descriptive string for screen readers, or null if
decorative. [TouchstoneMissingContentDescription]
Image(painter = painterResource(R.drawable.avatar))
~~~~~
ProfileScreen.kt:35: Warning: fontSize uses .dp which won't scale with
user font size preference. Use .sp instead. [TouchstoneHardcodedFontSize]
Text("Hello", fontSize = 14.dp)
~~~~~
dependencies {
androidTestImplementation("io.github.lehan0328:touchstone-compose:0.1.0")
}Add two lines to any Compose test:
@get:Rule val composeTestRule = createComposeRule()
@get:Rule val a11yCheck = A11yCheckRule()
@Test
fun myScreen_isAccessible() {
composeTestRule.setContent { MyScreen() }
val report = a11yCheck.check(composeTestRule)
assert(report.passed) { report.format() }
}Sample output (from a real run against a test app with intentional violations):
Touchstone Accessibility Report
═══════════════════════════════
Score: 57/100
Violations: 5 (4 critical, 1 warning)
1. [CRITICAL] Touch target is 24.0×24.0dp (minimum: 48×48dp)
Rule: touch-target-size (WCAG 2.5.5 AA)
Fix: Add Modifier.size(48.dp) or Modifier.sizeIn(minWidth = 48.dp, minHeight = 48.dp)
2. [CRITICAL] Contrast ratio is 1.61:1 for normal text (minimum: 4.5:1)
Rule: color-contrast (WCAG 1.4.3 AA)
Fix: Increase contrast between text and background colors
3. [WARNING] Editable field has no accessible label
Rule: editable-text-semantics (WCAG 4.1.2 A)
Fix: Add a label parameter to TextField, or use Modifier.semantics { contentDescription = "..." }
| Rule | Severity | WCAG | What it catches |
|---|---|---|---|
TouchstoneMissingContentDescription |
Error | 1.3.1, 4.1.2 | Image() or Icon() without contentDescription |
TouchstoneHardcodedFontSize |
Warning | 1.4.4 | fontSize = 14.dp instead of 14.sp |
TouchstoneClickableWithoutRole |
Warning | 4.1.2 | Modifier.clickable {} without role parameter |
| Rule | Severity | WCAG | What it catches |
|---|---|---|---|
touch-target-size |
Critical | 2.5.5 | Interactive elements < 48×48dp (with 5 exemptions) |
color-contrast |
Critical | 1.4.3 | Text contrast below 4.5:1 (normal) or 3.0:1 (large) |
editable-text-semantics |
Warning | 4.1.2 | Text fields missing editableText, ImeAction, or label |
duplicate-clickable-bounds |
Warning | 2.5.5 | Overlapping clickable regions (≥ 10%) |
Touchstone uses a dual-engine architecture:
| Lint Engine | TestRule Engine | |
|---|---|---|
| Runs during | ./gradlew lint |
./gradlew connectedAndroidTest |
| Analysis type | Static (code patterns) | Runtime (rendered properties) |
| Catches | Missing labels, hardcoded sizes, missing roles | Touch target sizes, contrast ratios, duplicate bounds |
| Requires emulator | No | Yes |
| Speed | < 1 second | 50–200ms per screen |
- Android Gradle Plugin 7.0+
- Kotlin 1.7+
touchstone-lint: No emulator requiredtouchstone-compose: Requires emulator or device for instrumented tests
- Android Lint rules (static analysis)
- Compose TestRule engine (runtime — touch targets, contrast, semantics)
- GitHub Action + PR commenting
See CONTRIBUTING.md for development setup, how to add new detectors, and coding standards.
# Lint (unit tests, no emulator)
./gradlew :touchstone-lint:test
# Compose (unit tests, no emulator)
./gradlew :touchstone-compose:testDebugUnitTest
# Compose (instrumented tests, requires emulator)
./gradlew :touchstone-compose:connectedDebugAndroidTestCopyright 2026 Lehan Ouyang
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
If you found this tool useful, a ⭐ on the repo would be much appreciated!