agentsclimarketplace

Swift accessibility

Skill Mattakushi432/Claude-Code-Skills-Custom-DevTools-Pack/plugins/devtools-pack/skills/swift-accessibility

A curated pack of custom Claude Code skills for developers — installable as a Claude Code plugin marketplace.

Install
npx -y skills add Mattakushi432/Claude-Code-Skills-Custom-DevTools-Pack --skill swift-accessibility

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

When to activate: iOS/macOS accessibility, VoiceOver, accessibilityLabel, accessibilityHint, Dynamic Type, high contrast, SwiftUI accessibility modifiers

SKILL.md

5.5 KB, as published. Nobody here has run it

Swift Accessibility Patterns

SwiftUI Accessibility Modifiers

struct ArticleCard: View {
    let article: Article
    @State private var isFavorite = false

    var body: some View {
        VStack(alignment: .leading) {
            // Merge child elements into one VoiceOver element
            VStack(alignment: .leading) {
                Text(article.title).font(.headline)
                Text(article.summary).font(.subheadline).foregroundStyle(.secondary)
            }
            .accessibilityElement(children: .combine)
            .accessibilityLabel("\(article.title). \(article.summary)")

            Button {
                isFavorite.toggle()
            } label: {
                Image(systemName: isFavorite ? "heart.fill" : "heart")
            }
            .accessibilityLabel(isFavorite ? "Remove from favorites" : "Add to favorites")
            .accessibilityHint("Double tap to toggle favorite status")
            .accessibilityAddTraits(isFavorite ? .isSelected : [])
        }
    }
}

Dynamic Type

// Always use dynamic type styles — never hardcode font sizes
Text("Article Title")
    .font(.headline)                      // scales with user font size setting
    .dynamicTypeSize(.small ... .xxxLarge) // clamp if layout breaks at extremes

// Adaptive layout for large text
ViewThatFits(in: .horizontal) {
    HStack { icon; text }  // preferred: side by side
    VStack { icon; text }  // fallback: stacked for large text
}

VoiceOver Custom Actions

struct SwipeableCard: View {
    let item: Item

    var body: some View {
        CardView(item: item)
            .accessibilityActions {
                Button("Mark as Read") { markRead(item) }
                Button("Archive") { archive(item) }
                Button("Delete", role: .destructive) { delete(item) }
            }
    }
}

Focus Management

struct LoginView: View {
    @AccessibilityFocusState private var isUsernameFocused: Bool

    var body: some View {
        VStack {
            TextField("Username", text: $username)
                .accessibilityFocused($isUsernameFocused)

            if showError {
                Text("Invalid username")
                    .foregroundStyle(.red)
                    .accessibilityAddTraits(.isStaticText)
            }

            Button("Log In") { login() }
        }
        .onAppear { isUsernameFocused = true }
    }
}

UIKit Accessibility

// Custom accessible element
class CustomSliderView: UIView {
    var value: Float = 0 {
        didSet { accessibilityValue = "\(Int(value * 100))%" }
    }

    override var accessibilityTraits: UIAccessibilityTraits {
        get { [.adjustable] }
        set { }
    }

    override func accessibilityIncrement() { value = min(1, value + 0.1) }
    override func accessibilityDecrement() { value = max(0, value - 0.1) }
}

// Announce changes to VoiceOver
UIAccessibility.post(notification: .announcement, argument: "Upload complete")

// Screen changed (after modal presentation)
UIAccessibility.post(notification: .screenChanged, argument: firstFocusableElement)

High Contrast and Color Independence

// Never rely on color alone to convey information
// Bad: red = error, green = success (color-blind users can't tell)
// Good: add icon + label

struct StatusBadge: View {
    let status: Status

    var body: some View {
        Label(status.label, systemImage: status.iconName)
            .foregroundStyle(status.color)  // color as secondary signal only
    }
}

// Check for increased contrast
@Environment(\.colorSchemeContrast) var contrast
if contrast == .increased {
    // Use higher contrast variant
}

// Use adaptive colors
Color("AccentColor")  // defined in Asset Catalog with dark/light/high-contrast variants

Testing Accessibility

// UI Test with VoiceOver
func testFavoriteButtonAccessibility() {
    let app = XCUIApplication()
    app.launch()

    let favoriteButton = app.buttons["Add to favorites"]
    XCTAssertTrue(favoriteButton.exists)

    favoriteButton.tap()
    XCTAssertTrue(app.buttons["Remove from favorites"].exists)
}

// Check for accessibility audit in Xcode 15+
func testA11yAudit() throws {
    let app = XCUIApplication()
    app.launch()
    try app.performAccessibilityAudit()
}

Accessibility Checklist

  • All interactive elements have accessibilityLabel
  • Image-only buttons have descriptive labels (not "button")
  • Custom controls implement accessibilityTraits correctly
  • Dynamic Type tested at all sizes (especially xxxLarge)
  • Color is not the only differentiator
  • Reduced motion respected
  • Focus order is logical
  • Error messages announced via UIAccessibility.post

Common Anti-Patterns

  • Image("icon").accessibilityLabel("icon") — describe the purpose, not the visual
  • accessibilityHidden(true) on meaningful elements — only hide decorative elements
  • Hardcoded font sizes — always use Dynamic Type
  • Layout that breaks at xxxLarge text — test and use ViewThatFits
  • Ignoring accessibilityAudit — run it in CI to catch regressions early

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.