ios-accessibility — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited ios-accessibility (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Never use generic labels like "button", "image", or "icon". Describe what it does: "Delete message", "Add to favorites", "Share photo".
Button, Toggle, Link, Picker, Slider, Stepper) whenever possible. They carry correct accessibility traits automatically.Better yet, wrap them in a Button so VoiceOver announces them as interactive.
Use Image(decorative:) or .accessibilityHidden(true). Never let VoiceOver read "image" for decorative content.
.body, .title, .headline) and @ScaledMetric for custom dimensions..frame(minWidth: 44, minHeight: 44) and .contentShape(.rect) to expand small icons.accessibilityDifferentiateWithoutColor.accessibilityReduceMotion and provide crossfade alternatives to spring/slide animations..accessibilitySortPriority() to fix order when layout does not match logical flow.AccessibilityNotification.LayoutChanged or use @AccessibilityFocusState..accessibilityElement(children: .combine) to reduce swipe count and create meaningful compound descriptions.Before shipping any screen, verify:
Button, Link, and tappable element has an accessibilityLabelImage(decorative:) or .accessibilityHidden(true).accessibilityAddTraits(.isHeader).accessibilityExtraExtraExtraLargeaccessibilityReduceMotion.accessibilityAddTraits(.isModal) to trap focusaccessibilityValue reflects current state for toggles and slidersaccessibilityHint is set for non-obvious actions (starts with verb phrase)performAccessibilityAudit()// 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"]).accessibilityAddTraits(.isButton)
.accessibilityAddTraits(.isHeader)
.accessibilityAddTraits(.isLink)
.accessibilityAddTraits(.isSelected)
.accessibilityAddTraits(.isImage)
.accessibilityRemoveTraits(.isImage)// 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)
}// 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
}
}@AccessibilityFocusState private var isFocused: Bool
TextField("Name", text: $name)
.accessibilityFocused($isFocused)
// Move focus programmatically
Button("Show Error") {
errorMessage = "Invalid input"
isFocused = true
}// 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
}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")// 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)
}ForEach(messages) { message in
MessageRow(message: message)
.accessibilityAction(named: "Delete") {
delete(message)
}
.accessibilityAction(named: "Archive") {
archive(message)
}
.accessibilityAction(named: "Mark as read") {
markAsRead(message)
}
}@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
}
}
}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)
}
}
}For detailed API coverage, see:
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.