From cc-mobile-ios
iOS accessibility patterns — accessibility labels/hints/values/traits, Dynamic Type, VoiceOver, Reduce Motion, high contrast, RTL mirroring. Load whenever adding or reviewing a SwiftUI screen.
How this skill is triggered — by the user, by Claude, or both
Slash command
/cc-mobile-ios:ios-accessibilityThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Every interactive element has:
Every interactive element has:
.isButton, .isHeader, .isToggle)..contentShape(Rectangle()) + .frame(minWidth: 44, minHeight: 44) on tight layouts).SwiftUI infers a lot of this, but custom controls don't.
Image(systemName: "trash")
.accessibilityLabel("Delete")
.accessibilityHint("Removes the selected order")
// Decorative image:
Image("splash-illustration").accessibilityHidden(true)
VoiceOver reads each subview separately by default. For cards / rows, collapse:
HStack {
AsyncImage(url: episode.artwork)
VStack { Text(episode.title); Text(episode.duration) }
Image(systemName: "play.fill").accessibilityHidden(true)
}
.contentShape(Rectangle())
.onTapGesture { play(episode) }
.accessibilityElement(children: .combine)
.accessibilityLabel("Play \(episode.title), \(episode.duration)")
.accessibilityAddTraits(.isButton)
.accessibilityElement(children: .combine) merges children, keeping their labels joined..accessibilityElement(children: .ignore) takes full control; you supply one label..body, .headline, .footnote) via Font.body.accessibilityExtraExtraExtraLarge. Test in previews:#Preview("Large font") {
OrderRow(order: .sample)
.environment(\.dynamicTypeSize, .accessibility3)
}
Font.custom("YourFont", size: UIFontMetrics.default.scaledValue(for: 16)) to opt into scaling, or better, use Font.system(.body, design: .rounded)..frame(maxWidth: .infinity, alignment: .leading) and let the text wrap.Honor the user preference for screen transitions:
@Environment(\.accessibilityReduceMotion) private var reduceMotion
.animation(reduceMotion ? nil : .spring(), value: isOpen)
For parallax / auto-play / looping animations: pause or disable entirely when reduceMotion is on.
@Environment(\.colorSchemeContrast) private var contrast
Text(status.label)
.foregroundStyle(contrast == .increased ? Color.accessibleAccent : Color.accent)
For UI tests:
let row = app.cells.element(matching: .init(format: "label CONTAINS[c] %@", "Order #1"))
row.tap()
XCTAssertTrue(app.navigationBars["Order #1"].exists)
.leading / .trailing in alignment & edges, never .left / .right..flipsForRightToLeftLayoutDirection(true).#Preview { OrderRow(order: .sample).environment(\.layoutDirection, .rightToLeft) }
For form-validation or async errors:
UIAccessibility.post(notification: .announcement, argument: "Card declined. Check your billing details.")
Or put the error in a visible Text with .accessibilityAddTraits(.updatesFrequently) so VoiceOver re-reads.
.accessibilityHidden(true) on something with interactive behavior..font(.system(size: 17)) outside design-system token generation..accessibilityLabel("") — use .accessibilityHidden(true).Searches MemPalace before answering questions about past work, people, projects, or prior decisions. Returns verbatim stored content instead of guessing from model memory.
Guides Payload CMS config (payload.config.ts), collections, fields, hooks, access control, APIs. Debugs validation errors, security, relationships, queries, transactions, hook behavior.
Implements vector databases with Pinecone, Weaviate, Qdrant, Milvus, pgvector for semantic search, RAG, recommendations, and similarity systems. Optimizes embeddings, indexing, and hybrid search.
npx claudepluginhub dimitriremoiville/cc-mobile --plugin cc-mobile-ios