Install
$ agentstack add skill-koshkinvv-ios-agent-skills-ios-accessibility ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
iOS Accessibility Skill
Core Rules
- EVERY interactive element must have a meaningful
accessibilityLabel.
Never use generic labels like "button", "image", or "icon". Describe what it does: "Delete message", "Add to favorites", "Share photo".
- Use native SwiftUI controls (
Button,Toggle,Link,Picker,Slider,Stepper) whenever possible. They carry correct accessibility traits automatically.
- Custom views with
onTapGestureMUST add.accessibilityAddTraits(.isButton).
Better yet, wrap them in a Button so VoiceOver announces them as interactive.
- Decorative images must be hidden from VoiceOver.
Use Image(decorative:) or .accessibilityHidden(true). Never let VoiceOver read "image" for decorative content.
- Support Dynamic Type at ALL sizes including accessibility sizes (AX1-AX5). Use system text styles (
.body,.title,.headline) and@ScaledMetricfor custom dimensions.
- Minimum touch target: 44x44 points. Use
.frame(minWidth: 44, minHeight: 44)and.contentShape(.rect)to expand small icons.
- Never re-prompt after
.userCancel. When the user dismisses a biometric prompt or permission dialog, respect their decision. Do not show the prompt again immediately.
- Test with VoiceOver on a real device, not just the Simulator. The Simulator does not fully replicate VoiceOver behavior, gestures, or focus management.
- Run
performAccessibilityAudit()in UI tests. Automated audits catch contrast issues, missing labels, small hit regions, and clipped text.
- Add
NSFaceIDUsageDescriptionto Info.plist when using Face ID. Without it the app crashes on first biometric attempt.
- Color must not be the sole indicator. Always pair color with text, icons, or shapes. Check
accessibilityDifferentiateWithoutColor.
- Respect Reduce Motion. Check
accessibilityReduceMotionand provide crossfade alternatives to spring/slide animations.
- Logical reading order matters. VoiceOver reads left-to-right, top-to-bottom. Use
.accessibilitySortPriority()to fix order when layout does not match logical flow.
- Move focus to important changes. When an error appears or content updates, post
AccessibilityNotification.LayoutChangedor use@AccessibilityFocusState.
- Group related elements with
.accessibilityElement(children: .combine)to reduce swipe count and create meaningful compound descriptions.
Quick Checklist
Before shipping any screen, verify:
- [ ] Every
Button,Link, and tappable element has anaccessibilityLabel - [ ] Decorative images use
Image(decorative:)or.accessibilityHidden(true) - [ ] Section headers are marked with
.accessibilityAddTraits(.isHeader) - [ ] Dynamic Type renders correctly at all sizes including
.accessibilityExtraExtraExtraLarge - [ ] Touch targets are at least 44x44 points
- [ ] Color is not the only way to convey information (errors, status, selection)
- [ ] Animations respect
accessibilityReduceMotion - [ ] Complex interactions have custom actions as swipe alternatives
- [ ] Reading order is logical (matches visual and semantic order)
- [ ] Focus moves to errors, alerts, or newly inserted content
- [ ] Modal views use
.accessibilityAddTraits(.isModal)to trap focus - [ ] Adjustable controls (sliders, steppers) work with swipe up/down
- [ ]
accessibilityValuereflects current state for toggles and sliders - [ ]
accessibilityHintis set for non-obvious actions (starts with verb phrase) - [ ] UI tests include
performAccessibilityAudit()
SwiftUI Accessibility Modifier Quick Reference
Labels, Hints, Values
// Static label
.accessibilityLabel("Delete message")
// Closure label (iOS 18+)
.accessibilityLabel { label in
Text("Unread") + label
}
// Hint — describes the result of activating
.accessibilityHint("Double tap to delete this message")
// Value — current state of a control
.accessibilityValue("50 percent")
// Input labels — Voice Control alternate names
.accessibilityInputLabels(["Delete", "Remove", "Trash"])
Traits
.accessibilityAddTraits(.isButton)
.accessibilityAddTraits(.isHeader)
.accessibilityAddTraits(.isLink)
.accessibilityAddTraits(.isSelected)
.accessibilityAddTraits(.isImage)
.accessibilityRemoveTraits(.isImage)
Grouping and Hiding
// Combine children into one element
.accessibilityElement(children: .combine)
// Hide from VoiceOver
.accessibilityHidden(true)
// Decorative image (hidden automatically)
Image(decorative: "background-pattern")
// Replace entire subtree with custom element
.accessibilityRepresentation {
Toggle("Wi-Fi", isOn: $isEnabled)
}
Actions
// Named action (appears in custom actions rotor)
.accessibilityAction(named: "Mark as read") {
markAsRead()
}
// Adjustable (swipe up/down)
.accessibilityAdjustableAction { direction in
switch direction {
case .increment: value += 1
case .decrement: value -= 1
@unknown default: break
}
}
Focus Management
@AccessibilityFocusState private var isFocused: Bool
TextField("Name", text: $name)
.accessibilityFocused($isFocused)
// Move focus programmatically
Button("Show Error") {
errorMessage = "Invalid input"
isFocused = true
}
Dynamic Type Support
// System text style (scales automatically)
Text("Hello").font(.body)
// Custom font that scales with Dynamic Type
Text("Hello").font(.custom("Avenir", size: 17, relativeTo: .body))
// Scaled dimension
@ScaledMetric(relativeTo: .body) private var iconSize: CGFloat = 24
// Respond to accessibility sizes
@Environment(\.dynamicTypeSize) private var typeSize
if typeSize.isAccessibilitySize {
// Switch to vertical layout
}
Common Patterns
Card with Combined Accessibility
VStack(alignment: .leading) {
Text(item.title).font(.headline)
Text(item.subtitle).font(.subheadline)
Text(item.date).font(.caption)
}
.accessibilityElement(children: .combine)
.accessibilityAddTraits(.isButton)
.accessibilityHint("Double tap to view details")
Custom Toggle
// BAD: VoiceOver does not know this is interactive
HStack {
Text("Dark Mode")
Image(systemName: isDark ? "moon.fill" : "moon")
}
.onTapGesture { isDark.toggle() }
// GOOD: Use a real Toggle
Toggle("Dark Mode", isOn: $isDark)
// GOOD: If custom UI is needed, add representation
HStack {
Text("Dark Mode")
Image(systemName: isDark ? "moon.fill" : "moon")
}
.onTapGesture { isDark.toggle() }
.accessibilityRepresentation {
Toggle("Dark Mode", isOn: $isDark)
}
Swipeable List Row with Actions
ForEach(messages) { message in
MessageRow(message: message)
.accessibilityAction(named: "Delete") {
delete(message)
}
.accessibilityAction(named: "Archive") {
archive(message)
}
.accessibilityAction(named: "Mark as read") {
markAsRead(message)
}
}
Error Announcement
@AccessibilityFocusState private var isErrorFocused: Bool
VStack {
TextField("Email", text: $email)
if let error = validationError {
Text(error)
.foregroundStyle(.red)
.accessibilityFocused($isErrorFocused)
}
Button("Submit") {
if !validate() {
isErrorFocused = true
}
}
}
Responsive Layout for Large Type
struct AdaptiveRow: View {
@Environment(\.dynamicTypeSize) private var typeSize
var body: some View {
let layout = typeSize.isAccessibilitySize
? AnyLayout(VStackLayout(alignment: .leading, spacing: 8))
: AnyLayout(HStackLayout(spacing: 12))
layout {
Image(systemName: "star.fill")
.accessibilityHidden(true)
Text("Favorites")
Spacer()
Text("12 items")
.foregroundStyle(.secondary)
}
}
}
References
For detailed API coverage, see:
- [VoiceOver](references/voiceover.md) — labels, hints, values, traits, actions, rotors, focus
- [Dynamic Type](references/dynamic-type.md) — text styles, @ScaledMetric, layout adaptation, color and contrast, motion
- [Auditing](references/auditing.md) — Xcode Inspector, XCTest audits, environment values, best practices
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: koshkinvv
- Source: koshkinvv/ios-agent-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.