Skip to content
cccskills
FreePublished on GitHub

mobile-accessibility

Use when building or changing any interactive mobile UI - VoiceOver/TalkBack labels, grouping, headings, focus order, Dynamic Type/font scale, reduce motion, contrast, and the automated check for each stack.

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md5.4 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Mobile Accessibility

Overview

VoiceOver and TalkBack are not an edge case — they're how a meaningful share of your users, and every store review, go through the screen. Every control that is interactive needs a label a screen reader can speak; every screen needs a sane reading and focus order; text needs to scale.

Core principle: build it accessible the first time — retrofitting labels after the fact means re-testing the whole screen.

Per-stack API table

ConcernFlutterComposeSwiftUI
LabelSemantics(label:) / tooltip / semanticLabel on Image/IconcontentDescription (null = decorative)accessibilityLabel / Button("…", systemImage:)
GroupingMergeSemanticsModifier.semantics(mergeDescendants = true).accessibilityElement(children: .combine)
HeadingSemantics(header: true)semantics { heading() }.accessibilityAddTraits(.isHeader)
Hidden/decorativeExcludeSemanticsModifier.clearAndSetSemantics {}Image(decorative:)
Custom actionCustomSemanticsActionModifier.semantics { customActions = … }.accessibilityAction(named:)
Live regionSemantics(liveRegion: true)Modifier.semantics { liveRegion = … }.accessibilityAddTraits(.updatesFrequently)

Rules that apply everywhere:

  • Every interactive control (button, icon button, tappable row) has a non-empty, human-readable label — never a bare icon with no text or label.
  • Don't rely on a swipe-only gesture for something achievable another way; add a custom action or a visible control alongside it.
  • Group a composite control (e.g. an icon + label that act as one tap target) with the merge/combine API so a screen reader announces it once, not as separate stops.
  • Headings get the heading trait so screen-reader users can jump by section.
  • @ScaledMetric (SwiftUI) / theme text styles (Flutter, Compose) everywhere text appears — never a hardcoded point/sp size that ignores the system font-scale setting.
  • Respect reduce-motion: MediaQuery.disableAnimationsOf(context) (Flutter), LocalAccessibilityManager/animation duration checks (Compose), @Environment(\.accessibilityReduceMotion) (SwiftUI) — skip or shorten a decorative animation, never a functional state change.
  • Contrast: body text ≥4.5:1, large text ≥3:1 against its background in both light and dark — check this when picking theme token values, not per-screen.

Automated checks

  • Flutter (verified): meetsGuideline — androidTapTargetGuideline (48×48), iOSTapTargetGuideline (44×44), labeledTapTargetGuideline, textContrastGuideline. Run all four against any new interactive widget's test (see mobile-visual-self-review's worked example).
  • Compose: rule.enableAccessibilityChecks() (ui-test-junit4-accessibility) runs the Accessibility Test Framework before every UI-mutating test action. It requires an API 34+ device/emulator — on Robolectric it only warns, so a Robolectric-only pass is inconclusive and must be said so in the closing message. tryPerformAccessibilityChecks() for a manual, one-off gate inside a test.
  • XCUITest: try app.performAccessibilityAudit() (Xcode 15+) inside a UI test — fails the test on a real finding (e.g. insufficient contrast, missing label).

The device proxy

When you have a device but not a screen reader to drive, mobile_read_ui {filter: "clickable=\"true\""} is your proxy for "what would TalkBack/VoiceOver say here": every line needs a non-empty text or content-desc, and the bounds must meet the tap-target floor. This is not a substitute for the automated checks above — it catches missing labels, not contrast or reading order.

Worked Example

❌ IconButton(icon: Icon(Icons.delete), onPressed: delete)                 // no label at all
✅ IconButton(
     icon: const Icon(Icons.delete),
     tooltip: 'Delete task',                                               // spoken by TalkBack/VoiceOver
     onPressed: delete,
   )
// ❌ Button(action: delete) { Image(systemName: "trash") }                 // icon-only, silent
// ✅
Button("Delete task", systemImage: "trash", action: delete)
    .labelStyle(.iconOnly)                                                 // visually icon-only, still spoken

Common Mistakes

  • An icon-only button with no tooltip/contentDescription/accessibilityLabel.
  • Hardcoding a font size instead of a scalable text style, so Dynamic Type/font-scale does nothing.
  • Treating a Robolectric-only enableAccessibilityChecks() pass as a verified result.
  • A decorative animation that ignores the reduce-motion setting.
  • A composite row (icon + title + chevron) left as three separate screen-reader stops instead of merged into one.

Red Flags

  • Icon/Image used as the entire content of a tappable control with no label anywhere in the diff.
  • A contrast value picked without checking it against both light and dark theme backgrounds.
  • A swipe-to-reveal action with no equivalent reachable another way.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper118Updated Oct 21, 2026

Use when the diff adds or changes an endpoint, resolver, RPC, job or query that takes an object id, a role check, a request binding or a tenant filter - BOLA/IDOR, function-level authorization, mass assignment and tenant scoping

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper118Updated Oct 21, 2026

Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper118Updated Oct 21, 2026

Use when a task changes any screen, form, dialog, menu or control - Lighthouse/axe scan of the changed screens, a keyboard walk, and the thresholds that fail a task

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper118Updated Oct 21, 2026

How to work a task returned with review, QA or UAT findings. Use when a task is in need_revision or PR review comments are in your context.

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper118Updated Oct 21, 2026

Use when the repository keeps its alerting in code, or a person asks for alerts outside TaskTrooper - PrometheusRule YAML, a Cloud Monitoring alert policy in Terraform, a Datadog monitor, each routed to TaskTrooper's alert ingest URL so it opens an incident here

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper118Updated Oct 21, 2026

See all skills by makifbaysal

Report a problem with this skill