agentleFS
GuillemRocaGuillemRoca/agent-skills-android/skills/android-accessibility/SKILL.md

android-accessibility

Skill2 starsChanged 40 days agoMITView on GitHub

Use when building or reviewing Android UI for accessibility. Covers TalkBack, content descriptions, touch targets, Compose semantics, focus order, and Accessibility Scanner testing.

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

8 rules. About 0 are common, 6 are distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈6

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Before shipping any user-facing feature

    line 20
  2. Very specific:

    Use null for purely decorative elements

    line 75
  3. Distinctive:

    Verify announcements make sense without seeing the screen

    line 260
  4. Distinctive:

    Check that all interactive elements are reachable

    line 261
  5. Distinctive:

    Verify no elements are skipped or read in wrong order

    line 262
  6. Distinctive:

    Run on every screen before shipping

    line 266
  7. Very specific:

    Use android layout --diff after an interaction to inspect only what changed — keeps assertions tight when verifying live regions, error …

    line 285
  8. Distinctive:

    No TalkBack or Accessibility Scanner testing before release

    line 334

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Android Accessibility

Overview

Accessibility is not optional — it's a requirement for reaching all users. This skill covers building accessible Android UIs with Jetpack Compose semantics, proper content descriptions, touch target sizing, TalkBack compatibility, and systematic testing with Accessibility Scanner.

When to Use

  • Building new UI screens or components
  • Reviewing existing UI for accessibility compliance
  • Fixing accessibility issues flagged by Accessibility Scanner or users
  • Before shipping any user-facing feature

Skip when: Working on non-UI code (repositories, use cases, networking).

Core Standards

RequirementStandard
Touch targetsMinimum 48dp x 48dp
Color contrast (text)>= 4.5:1 (normal), >= 3:1 (large 18sp+)
Color contrast (UI components)>= 3:1 against adjacent colors
Content descriptionsAll meaningful visual elements
Focus orderLogical reading order
No color-only informationAdditional indicators required

Core Process

Step 1: Content Descriptions

  1. Every meaningful visual element needs a description:
// Icons that convey information
Icon(
    imageVector = Icons.Default.Delete,
    contentDescription = "Delete task", // Descriptive, not "delete icon"
)

// Decorative elements — explicitly null
Icon(
    imageVector = Icons.Default.Circle,
    contentDescription = null, // Decorative, TalkBack skips it
)

// Images
Image(
    painter = painterResource(R.drawable.user_avatar),
    contentDescription = "Profile photo of ${user.name}",
)

// IconButtons — the button gets the description
IconButton(
    onClick = onDelete,
) {
    Icon(
        imageVector = Icons.Default.Delete,
        contentDescription = "Delete task", // On the Icon, read by TalkBack
    )
}
  1. Description rules:
    • Describe the action or meaning, not the visual appearance
    • "Delete task" not "red trash can icon"
    • "Search" not "magnifying glass"
    • Use null for purely decorative elements
    • Include dynamic content: "Profile photo of John" not just "profile photo"

Step 2: Touch Targets

  1. Enforce minimum 48dp touch targets:
// Compose handles this automatically for clickable elements,
// but verify small elements:
IconButton(
    onClick = onToggle,
    modifier = Modifier.size(48.dp), // At least 48dp
) {
    Icon(
        imageVector = Icons.Default.Check,
        contentDescription = "Mark complete",
        modifier = Modifier.size(24.dp), // Icon can be smaller
    )
}

// For custom clickable elements, ensure minimum touch area:
@Composable
fun SmallChip(
    label: String,
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
) {
    Surface(
        onClick = onClick,
        modifier = modifier.defaultMinSize(minHeight = 48.dp, minWidth = 48.dp),
    ) {
        Text(label, modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp))
    }
}

Step 3: Compose Semantics

  1. Use semantics for rich accessibility information:
// Merge child semantics for a single TalkBack announcement
@Composable
fun TaskItem(
    task: Task,
    onToggle: () -> Unit,
    modifier: Modifier = Modifier,
) {
    Row(
        modifier = modifier
            .semantics(mergeDescendants = true) {
                // Custom state description
                stateDescription = if (task.completed) "Completed" else "Not completed"
                // Custom action
                customActions = listOf(
                    CustomAccessibilityAction("Toggle completion") {
                        onToggle()
                        true
                    }
                )
            }
            .clickable(onClick = onToggle),
    ) {
        Checkbox(checked = task.completed, onCheckedChange = null)
        Text(task.title)
    }
}

// Headings for navigation
Text(
    text = "My Tasks",
    style = MaterialTheme.typography.headlineMedium,
    modifier = Modifier.semantics { heading() },
)

// Live regions for dynamic content
Text(
    text = "3 items remaining",
    modifier = Modifier.semantics { liveRegion = LiveRegionMode.Polite },
)
  1. Semantics rules:
    • mergeDescendants = true for compound components (card with title + subtitle)
    • heading() for section titles (enables TalkBack heading navigation)
    • liveRegion for content that updates dynamically (counters, status)
    • stateDescription for custom state (not just "checked/unchecked")
    • Role for custom interactive elements

Step 4: Focus Order

  1. Ensure logical focus order:
// Default: top-to-bottom, start-to-end (matches visual order)
// Override when visual order differs from reading order:

@Composable
fun HeaderWithAction(
    title: String,
    onAction: () -> Unit,
) {
    Row {
        // TalkBack reads title first, then action button
        Text(
            text = title,
            modifier = Modifier
                .weight(1f)
                .semantics { heading() },
        )
        IconButton(onClick = onAction) {
            Icon(Icons.Default.Settings, contentDescription = "Settings")
        }
    }
}

// Focus management for dialogs and sheets:
@Composable
fun TaskDialog(onDismiss: () -> Unit) {
    val focusRequester = remember { FocusRequester() }

    AlertDialog(
        onDismissRequest = onDismiss,
        title = {
            Text(
                "Delete Task?",
                modifier = Modifier.focusRequester(focusRequester),
            )
        },
        // Request focus when dialog appears
        confirmButton = { /* ... */ },
    )

    LaunchedEffect(Unit) {
        focusRequester.requestFocus()
    }
}

Step 5: Color and Contrast

  1. Don't rely on color alone:
// BAD: only color distinguishes status
Text(
    text = task.title,
    color = if (task.overdue) Color.Red else Color.Black,
)

// GOOD: color + icon + text
Row {
    if (task.overdue) {
        Icon(
            Icons.Default.Warning,
            contentDescription = null, // Read as part of merged semantics
            tint = MaterialTheme.colorScheme.error,
        )
    }
    Text(
        text = task.title,
        color = if (task.overdue) MaterialTheme.colorScheme.error
                else MaterialTheme.colorScheme.onSurface,
    )
    if (task.overdue) {
        Text(
            text = "Overdue",
            color = MaterialTheme.colorScheme.error,
            style = MaterialTheme.typography.labelSmall,
        )
    }
}
  1. Use Material theme colors — they're designed for contrast compliance:
    • onSurface on surface — guaranteed contrast
    • onPrimary on primary — guaranteed contrast
    • error and onError — guaranteed contrast

Step 6: Testing

  1. TalkBack testing (manual):
    • Enable TalkBack: Settings → Accessibility → TalkBack
    • Swipe right to move to next element
    • Verify announcements make sense without seeing the screen
    • Check that all interactive elements are reachable
    • Verify no elements are skipped or read in wrong order
  2. Accessibility Scanner (automated):
    • Install from Play Store: "Accessibility Scanner" by Google
    • Run on every screen before shipping
    • Fixes: touch target size, contrast ratio, content descriptions
  3. android layout for scriptable accessibility assertions:

    android layout --pretty returns JSON with content-desc, role, and bounds per node — turn it into automated scans you can run from a script or CI step.

    # Fail the build if any Button is missing content-desc
    android layout --pretty \
      | jq -e '.nodes[] | select(.role=="Button" and ((.["content-desc"] // "") == ""))' \
      && echo "FAIL: unlabeled button" && exit 1
    
    # Flag touch targets smaller than 48dp (assuming density-converted bounds)
    android layout --pretty \
      | jq '.nodes[] | select(.role=="Button") | {id: .["resource-id"], bounds}' \
      | review_target_sizes.sh

    Use android layout --diff after an interaction to inspect only what changed — keeps assertions tight when verifying live regions, error toasts, or focus shifts. When layout returns nothing useful (WebView, animation), fall back to android screen capture --annotate -o screen.png and visually verify focus order. See ../../references/android-cli-reference.md.

    No device needed for composables: android studio render-compose-preview --print-semantics <file> <composable> emits the accessibility semantics tree of a @Preview as JSON — assert on contentDescription, role, and heading semantics straight from the preview, before anything is installed (requires a running Android Studio with Gemini; see ../../references/android-cli-reference.md).

  4. Compose UI tests for accessibility:
@Test
fun taskItem_hasContentDescription() {
    composeTestRule.setContent {
        TaskItem(task = Task("1", "Buy groceries", false), onToggle = {})
    }

    composeTestRule
        .onNodeWithContentDescription("Buy groceries")
        .assertExists()
}

@Test
fun deleteButton_hasMeaningfulDescription() {
    composeTestRule.setContent {
        TaskItem(task = sampleTask, onToggle = {}, onDelete = {})
    }

    composeTestRule
        .onNodeWithContentDescription("Delete task")
        .assertExists()
        .assertHasClickAction()
}

Common Rationalizations

ShortcutWhy It Fails
"We'll add accessibility later"Retrofitting is 5-10x harder than building accessible from the start.
"Our users don't use TalkBack"15% of the world's population has a disability. You don't know your users.
"ContentDescription is visual boilerplate"It's the only way screen reader users interact with your app.
"Material components handle accessibility"Material provides a foundation. You still need content descriptions, focus order, and semantic grouping.
"Touch targets look too big"The visual element can be 24dp. The touch target must be 48dp. They're independent.

Red Flags

  • Images or icons without content descriptions
  • Touch targets smaller than 48dp
  • Color as the only differentiator
  • Missing heading semantics on section titles
  • No mergeDescendants on compound components
  • Hardcoded colors that may not meet contrast ratios
  • No TalkBack or Accessibility Scanner testing before release
  • contentDescription = "" (should be null for decorative)

Verification

  • All meaningful images/icons have descriptive contentDescription
  • Decorative elements use contentDescription = null
  • All touch targets >= 48dp x 48dp
  • Color is never the only differentiator
  • Section headings use semantics { heading() }
  • Compound components use mergeDescendants = true
  • Dynamic content uses liveRegion
  • Focus order matches logical reading order
  • TalkBack tested on all screens
  • Accessibility Scanner reports zero critical issues
  • Compose tests verify content descriptions exist

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.