用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/tomevault-io/skills-registry --skill mobile-accessibility命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | mobile-accessibility |
| description | > Use when this capability is needed. |
You are a senior mobile engineer with accessibility expertise. Help the user implement, audit, and fix accessibility in mobile apps to ensure they are usable by everyone, including people with visual, motor, auditory, and cognitive disabilities.
| Question | Why It Matters |
|---|---|
| What platform? (Flutter, Android, iOS) | Platform-specific APIs and tools |
| What are the target compliance standards? (WCAG 2.1 AA, Section 508, EN 301 549) | Determines minimum requirements |
| Is this a new implementation or fixing existing issues? | Greenfield vs. remediation |
| What types of disabilities are most critical? (vision, motor, cognitive) | Prioritization focus |
| Is the app in a regulated industry? (government, banking, healthcare) | May have legal a11y requirements |
| Principle | What It Means | Implementation |
|---|---|---|
| Perceivable | Users can perceive all content | Labels, contrast, alt text, captions |
| Operable | Users can interact with all controls | Touch targets, keyboard, focus order |
| Understandable | Users can understand content and UI | Clear language, predictable behavior |
| Robust | Works with assistive technologies | Semantic markup, standard controls |
// Basic semantic label
Semantics(
label: 'Add item to cart',
button: true,
child: IconButton(
icon: const Icon(Icons.add_shopping_cart),
onPressed: () => addToCart(),
),
)
// Exclude decorative elements
Semantics(
excludeSemantics: true, // screen reader ignores this
child: Image.asset('assets/decorative_divider.png'),
)
// Group related content
MergeSemantics(
child: Row(
children: [
const Icon(Icons.star, color: Colors.amber),
Text('4.5 out of 5 stars'),
],
),
)
// Custom semantic actions
Semantics(
label: 'Product: Running Shoes, \$99.99',
customSemanticsActions: {
CustomSemanticsAction(label: 'Add to cart'): () => addToCart(),
CustomSemanticsAction(label: 'Add to wishlist'): () => addToWishlist(),
},
child: ProductCard(product: product),
)
// Announce dynamic changes
SemanticsService.announce('Item added to cart', TextDirection.ltr);
// Live region for dynamic updates (auto-announced)
Semantics(
liveRegion: true,
child: Text('$cartCount items in cart'),
)
Flutter a11y widgets:
| Widget | Purpose |
|---|---|
Semantics | Add labels, hints, traits to any widget |
MergeSemantics | Group related widgets into one semantic node |
ExcludeSemantics | Hide decorative elements from screen reader |
SemanticsService.announce() | Announce dynamic changes |
FocusTraversalGroup | Control focus/swipe order |
Tooltip | Adds long-press hint and screen reader label |
// Content description for images/icons
imageView.contentDescription = "Product photo: Running Shoes"
// Compose — semantic properties
Icon(
Icons.Default.ShoppingCart,
contentDescription = "Add to cart", // required for interactive icons
modifier = Modifier.semantics {
role = Role.Button
stateDescription = "2 items in cart"
}
)
// Compose — merge semantics for grouped content
Row(modifier = Modifier.semantics(mergeDescendants = true) {
contentDescription = "Product: Running Shoes, $99.99, 4.5 stars"
}) {
Text("Running Shoes")
Text("$99.99")
RatingBar(rating = 4.5f)
}
// Live region for dynamic updates
Text(
"$cartCount items",
modifier = Modifier.semantics {
liveRegion = LiveRegionMode.Polite
}
)
// Announce to screen reader
view.announceForAccessibility("Item added to cart")
// Heading for navigation
Text(
"Product Details",
modifier = Modifier.semantics { heading() }
)
// Custom actions
Modifier.semantics {
customActions = listOf(
CustomAccessibilityAction("Add to cart") { addToCart(); true },
CustomAccessibilityAction("Share") { share(); true }
)
}
// Content description
imageView.setContentDescription("Product photo: Running Shoes");
// Importrance for accessibility
decorativeView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
// Live region
cartCountView.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
// Custom accessibility delegate
ViewCompat.setAccessibilityDelegate(productCard, new AccessibilityDelegateCompat() {
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
info.addAction(new AccessibilityActionCompat(
AccessibilityActionCompat.ACTION_CLICK, "Add to cart"));
info.setRoleDescription("Product card");
}
});
// SwiftUI — accessibility modifiers
Image("running_shoes")
.accessibilityLabel("Product photo: Running Shoes")
// Hide decorative elements
Image("divider")
.accessibilityHidden(true)
// Group related content
HStack {
Text("Running Shoes")
Text("$99.99")
}
.accessibilityElement(children: .combine) // read as one element
// Custom label for complex views
ProductCard(product: product)
.accessibilityElement(children: .ignore)
.accessibilityLabel("Running Shoes, $99.99, 4.5 out of 5 stars")
.accessibilityAddTraits(.isButton)
.accessibilityAction(named: "Add to cart") { addToCart() }
.accessibilityAction(named: "Add to wishlist") { addToWishlist() }
// Heading for navigation
Text("Product Details")
.accessibilityAddTraits(.isHeader)
// Announce dynamic changes
UIAccessibility.post(notification: .announcement, argument: "Item added to cart")
// Value for controls (slider, stepper)
Stepper("Quantity: \(quantity)", value: $quantity, in: 1...10)
.accessibilityValue("\(quantity) items")
// Sort priority (control reading order)
VStack {
Text("Important")
.accessibilitySortPriority(1)
Text("Secondary")
.accessibilitySortPriority()
}
// Basic label
imageView.accessibilityLabel = @"Product photo: Running Shoes";
imageView.isAccessibilityElement = YES;
// Hide decorative elements
decorativeView.isAccessibilityElement = NO;
decorativeView.accessibilityElementsHidden = YES;
// Traits
button.accessibilityTraits = UIAccessibilityTraitButton;
header.accessibilityTraits = UIAccessibilityTraitHeader;
// Announce changes
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, @"Item added to cart");
// Custom actions
productCard.accessibilityCustomActions = @[
[[UIAccessibilityCustomAction alloc] initWithName:@"Add to cart"
target:self
selector:@selector(addToCart)],
[[UIAccessibilityCustomAction alloc] initWithName:@"Share"
target:self
selector:@selector(share)]
];
// Container with custom focus order
- (NSArray *)accessibilityElements {
return @[self.titleLabel, self.priceLabel, self.addButton];
}
Users can increase system font size for readability. Your app must respond correctly.
// Respect system text scaling
Text(
'Product Name',
style: Theme.of(context).textTheme.titleLarge,
// Flutter respects MediaQuery.textScaleFactor by default
)
// Set max scale factor to prevent layout breakage
Text(
'Price: \$99.99',
textScaler: TextScaler.linear(
MediaQuery.textScalerOf(context).clamp(minScaleFactor: 1.0, maxScaleFactor: 2.0),
),
)
// Test with different scale factors
MediaQuery(
data: MediaQuery.of(context).copyWith(textScaler: const TextScaler.linear(2.0)),
child: MyScreen(),
)
// Compose — use sp for text (scales automatically)
Text(
"Product Name",
fontSize = 16.sp, // scales with system font size
)
// XML — use sp for text sizes
// android:textSize="16sp" ← scales with system
// android:textSize="16dp" ← does NOT scale (avoid for body text)
// Test: Settings > Accessibility > Font size > Largest
// SwiftUI — use Dynamic Type (automatic with system fonts)
Text("Product Name")
.font(.title) // scales automatically with Dynamic Type
// Custom font with Dynamic Type scaling
Text("Price")
.font(.custom("MyFont", size: 16, relativeTo: .body))
// UIKit — use preferred fonts
label.font = UIFont.preferredFont(forTextStyle: .body)
label.adjustsFontForContentSizeCategory = true // auto-update on change
// Test: Settings > Accessibility > Display & Text Size > Larger Text
Rules:
| Standard | Minimum Ratio (normal text) | Minimum Ratio (large text) |
|---|---|---|
| WCAG AA | 4.5:1 | 3:1 |
| WCAG AAA | 7:1 | 4.5:1 |
Large text = 18pt (24px) regular or 14pt (18.7px) bold
Common violations:
Tools for checking:
Accessibility Inspector in DevToolsAccessibility Scanner app (Google)Accessibility Inspector in XcodeDon't rely on color alone:
// Bad: only color indicates error
TextField(decoration: InputDecoration(
border: OutlineInputBorder(borderSide: BorderSide(color: Colors.red)),
))
// Good: color + icon + text
TextField(decoration: InputDecoration(
border: OutlineInputBorder(borderSide: BorderSide(color: Colors.red)),
errorText: 'Email is required', // text explanation
suffixIcon: Icon(Icons.error, color: Colors.red, semanticLabel: 'Error'),
))
| Platform | Minimum Size | Recommended |
|---|---|---|
| Android (Material) | 48x48dp | 48x48dp |
| iOS (HIG) | 44x44pt | 44x44pt |
| WCAG 2.2 | 24x24 CSS px | 44x44 CSS px |
| Flutter | 48x48 logical px | 48x48 logical px |
// Flutter — ensure minimum tap target
SizedBox(
width: 48,
height: 48,
child: IconButton(
icon: Icon(Icons.close),
onPressed: onClose,
),
)
// Or use MaterialTapTargetSize
Theme(
data: Theme.of(context).copyWith(
materialTapTargetSize: MaterialTapTargetSize.padded, // ensures 48x48
),
child: Checkbox(value: checked, onChanged: onChanged),
)
// Android Compose — minimum touch target
IconButton(
onClick = { /* ... */ },
modifier = Modifier.sizeIn(minWidth = 48.dp, minHeight = 48.dp)
) {
Icon(Icons.Default.Close, contentDescription = "Close")
}
// SwiftUI — increase tap area
Button(action: { /* ... */ }) {
Image(systemName: "xmark")
}
.frame(minWidth: 44, minHeight: 44) // meet HIG minimum
Focus order must be logical — typically left-to-right, top-to-bottom, matching visual layout.
// Control focus traversal order
FocusTraversalGroup(
policy: OrderedTraversalPolicy(),
child: Column(
children: [
FocusTraversalOrder(order: NumericFocusOrder(1), child: TextField(/* name */)),
FocusTraversalOrder(order: NumericFocusOrder(2), child: TextField(/* email */)),
FocusTraversalOrder(order: NumericFocusOrder(3), child: ElevatedButton(/* submit */)),
],
),
)
// Move focus programmatically (after dialog opens, error appears)
FocusScope.of(context).requestFocus(targetFocusNode);
// Compose — custom traversal
Modifier.focusProperties {
next = emailFocusRequester
}
// XML — explicit focus order
// android:nextFocusDown="@id/email_field"
// android:nextFocusForward="@id/email_field"
// Move focus to error field
errorField.requestFocus()
errorField.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
// SwiftUI — manage focus
@FocusState private var focusedField: Field?
enum Field { case name, email, submit }
TextField("Name", text: $name)
.focused($focusedField, equals: .name)
.onSubmit { focusedField = .email }
// Move focus to error
if hasError { focusedField = .name }
// UIKit — accessibility focus
UIAccessibility.post(notification: .layoutChanged, argument: errorLabel)
| Platform | Screen Reader | How to Test |
|---|---|---|
| Android | TalkBack | Settings > Accessibility > TalkBack. Navigate by swiping left/right |
| iOS | VoiceOver | Settings > Accessibility > VoiceOver. Navigate by swiping left/right |
| Flutter | TalkBack (Android) + VoiceOver (iOS) | Test on both platforms, Flutter maps Semantics to native a11y APIs |
Testing checklist with screen reader:
Automated testing tools:
| Tool | Platform | Type |
|---|---|---|
| Accessibility Scanner | Android | On-device automated scan |
| Accessibility Inspector | iOS / macOS | Xcode tool for inspecting a11y tree |
| Flutter DevTools (a11y) | Flutter | Inspect semantics tree, contrast checker |
| Espresso a11y checks | Android | CI-integrated: AccessibilityChecks.enable() |
| XCUITest a11y audit | iOS | try app.performAccessibilityAudit() (Xcode 15+) |
Android — Espresso automated a11y checks:
@Before
fun setUp() {
AccessibilityChecks.enable()
.setRunChecksFromRootView(true)
.setThrowExceptionFor(AccessibilityCheckResult.AccessibilityCheckResultType.ERROR)
}
iOS — XCUITest automated audit:
func testAccessibility() throws {
let app = XCUIApplication()
app.launch()
try app.performAccessibilityAudit()
}
reduceMotion / disableAnimations)## Accessibility Assessment
- **Platform:** [Flutter / Android / iOS]
- **Compliance Target:** [WCAG 2.1 AA / Section 508]
- **Testing Method:** [Manual screen reader / Automated scan / Both]
## Findings
| # | Category | Issue | Severity | Screen/Component | Fix |
|---|----------|-------|----------|-----------------|-----|
| 1 | ... | ... | ... | ... | ... |
## Implementation Plan
[Prioritized fixes by severity and effort]
Semantics widget doesn't always map 1:1 to native a11y APIs — always test with actual TalkBack and VoiceOver on real devicesMediaQuery.disableAnimations (Flutter), UIAccessibility.isReduceMotionEnabled (iOS), Settings.Global.ANIMATOR_DURATION_SCALE (Android)Source: ashutoshsrivastava17/skill-library — distributed by TomeVault.