Skip to content

Repository files navigation

ComposeA11yLint

Catches Jetpack Compose accessibility issues in your code at build time — before you ever open the app.

ComposeA11yLint is a free, open-source detekt ruleset that statically analyzes Jetpack Compose / Kotlin source for accessibility problems and maps each finding to a WCAG success criterion. No device, no emulator, no runtime — issues surface in code review and CI. Your source never leaves your machine; zero network calls.

How this is different

Runtime tools (Accessibility Scanner, on-device TalkBack testing) ComposeA11yLint
Stage Runtime — running app, manual testing Build time — source code, automatic
Mechanism ADB / on-device overlays Static analysis of Compose source (AST)
You learn of an issue After build + deploy + manual navigation In code review / on every commit
Needs a device Yes No

It's complementary to runtime tools: ComposeA11yLint flags issues in code; on-device tools help verify the fixes.

Install

ComposeA11yLint is a detekt ruleset — it runs inside detekt, so you need the detekt Gradle plugin applied. It targets detekt 2.0 (plugin id dev.detekt); a detekt 1.x setup (io.gitlab.arturbosch.detekt) can't load it.

Adding the ruleset is additive — it does not change any rules or reports you already run. In the build.gradle.kts of the module you want analyzed (for an Android app, the app module):

plugins {
    // ...your existing plugins...
    id("dev.detekt") version "2.0.0-alpha.3"   // only if you don't already apply detekt
}

repositories {
    mavenCentral()                             // where the ruleset is published
}

dependencies {
    detektPlugins("io.github.pardip235:composea11ylint:0.1.0")
}

The rules are off by default (as all detekt custom rules are) — turn them on in your detekt config. If you already have a config/detekt.yml, just add this block to it; otherwise create the file:

compose-a11y:
  MissingContentDescription:
    active: true
  TouchTargetSize:
    active: true
  ClickableWithoutRole:
    active: true
  CustomComponentMissingSemantics:
    active: true
  VisualHeadingWithoutHeading:
    active: true
  ColorContrast:
    active: true

Point detekt at your config (skip if it's already configured):

detekt {
    buildUponDefaultConfig = true                 // keep detekt's defaults; add ours on top
    config.setFrom("$rootDir/config/detekt.yml")  // can list several files
}

Run it

./gradlew detekt

The ComposeA11yLint rules now run alongside whatever else your detekt setup already does. (On Android you can also use detektMain; the plain detekt task analyzes src/main/kotlin regardless of your AGP version.)

Rules

Rule Flags WCAG
MissingContentDescription Image/Icon with empty or junk contentDescription 1.1.1
TouchTargetSize Clickable smaller than 48dp 2.5.8
ClickableWithoutRole Modifier.clickable {} with no role/label 4.1.2
CustomComponentMissingSemantics Custom interactive composable, no semantics 4.1.2
VisualHeadingWithoutHeading Heading-styled Text without heading() 1.3.1
ColorContrast Literal foreground/background colors below the 4.5:1 ratio 1.4.3

Precision first — each rule fires only when the violation is statically determinable, so a few rock-solid rules beat many that misfire.

Reports

Every detekt run prints a one-line console summary from ComposeA11yLint. To also write the ruleset's own report files, map the report ids to output paths on the detekt tasks. This is additive — it leaves detekt's built-in reports untouched:

tasks.withType<dev.detekt.gradle.Detekt>().configureEach {
    reports {
        custom {
            reportId = "composeA11yJson"                                     // stable JSON schema
            outputLocation.set(file("build/reports/detekt/composea11y.json"))
        }
        custom {
            reportId = "composeA11yMarkdown"                                 // grouped-by-file Markdown
            outputLocation.set(file("build/reports/detekt/composea11y.md"))
        }
    }
}
Output Where to find it What it is
Console summary stdout of the detekt task ComposeA11yLint: N issues across M files (h high, m medium, l low)
composeA11yJson build/reports/detekt/composea11y.json Stable machine-readable schema — for CI, dashboards, other tools.
composeA11yMarkdown build/reports/detekt/composea11y.md Human-readable, grouped by file, with WCAG + detail columns.

Paths are relative to the module you configured (for the app module → app/build/reports/detekt/). The JSON schema is documented in CONTRIBUTING.md.

Optional: run only the accessibility rules

If you want a run that reports only ComposeA11yLint findings — nothing from detekt's built-in rules — set disableDefaultRuleSets = true and (optionally) silence the built-in report formats. This changes detekt's behavior for that module, so prefer a dedicated setup over a shared one:

detekt {
    disableDefaultRuleSets = true                 // load ONLY rulesets from detektPlugins (this one)
    buildUponDefaultConfig = true
    config.setFrom("$rootDir/config/detekt.yml")
}

tasks.withType<dev.detekt.gradle.Detekt>().configureEach {
    reports {
        // ...the two custom { } blocks from above...
        checkstyle.required.set(false)
        html.required.set(false)
        sarif.required.set(false)
        markdown.required.set(false)
    }
}

Status

Published to Maven Central as io.github.pardip235:composea11ylint. All six rules and the JSON / Markdown / console reports are implemented with unit tests.

Build & test (contributors)

./gradlew :rules:test

The :rules module is a pure Kotlin/JVM detekt ruleset (no Android dependencies).

Conformance reports

Looking for compliance reporting? A separate companion project builds reports from this ruleset's output — see composea11ylint-conformance.

License

Apache-2.0.

About

No description, website, or topics provided.

Resources

Contributing

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages