Skip to main content

Accessibility Module

Every app deserves to be usable by everyone. The Accessibility module makes building inclusive iOS apps effortless with pre-defined labels, SwiftUI modifiers, and comprehensive support for iOS accessibility features.
Package location: Packages/DesignSystem/Sources/DesignSystem/Accessibility/

Overview

The Accessibility module is part of the DesignSystem package and provides ready-to-use accessibility features that work seamlessly with VoiceOver, Dynamic Type, Reduce Motion, and High Contrast modes.

50+ VoiceOver Labels

Pre-defined labels for common UI patterns

Dynamic Type

Text scales beautifully with user preferences

Reduce Motion

Respects motion sensitivity settings

High Contrast

Colors adapt for better readability

Quick Start

VoiceOver Labels

One line for full accessibility:
import DesignSystem

// Apply pre-defined accessibility
Button("Send") { }
    .saiAccessible(A11y.Chat.sendButton)
// VoiceOver: "Send message, button. Double tap to send your message."

// Works for all common UI patterns
Image("avatar")
    .saiAccessible(A11y.Profile.photo)
// VoiceOver: "Profile photo. Double tap to change your profile photo."

Dynamic Type Support

Text scales beautifully with user preferences:
Text("Respects user settings")
    .saiScaledFont(size: 18, weight: .semibold, relativeTo: .body)
// Automatically scales with iOS text size settings
// Capped to prevent layout issues at extreme sizes

Reduce Motion

Respects users who experience discomfort with animations:
Button("Animate") { }
    .scaleEffect(isPressed ? 0.95 : 1.0)
    .saiReducedMotionAnimation(.spring())
// Animation plays normally OR instant state change
// Based on user's Reduce Motion setting

High Contrast Mode

Colors adapt automatically for users who need more contrast:
Text("Adapts to user needs")
    .saiContrastAwareForeground(
        normal: DSColors.textSecondary,
        highContrast: DSColors.textPrimary
    )

File Structure

Packages/DesignSystem/Sources/DesignSystem/Accessibility/
├── A11y.swift          # Pre-defined labels
├── A11yModifiers.swift # SwiftUI modifiers
└── A11yAudit.swift     # Debug tools

Pre-Defined Labels

The module includes 50+ pre-defined VoiceOver labels:
  • Send button
  • Message input
  • Message bubble
  • Typing indicator
  • Attachment button
  • Voice input
  • Sign in button
  • Sign up button
  • Email field
  • Password field
  • Social login buttons
  • Forgot password
  • Settings row
  • Toggle switch
  • Navigation link
  • Destructive actions
  • Theme picker
  • Loading indicator
  • Error message
  • Success message
  • Empty state
  • Refresh control

Adding Custom Labels

1. Define Your Label

extension A11y {
    public enum MyFeature {
        public static let action = A11yLabel(
            label: "Custom action",
            hint: "Double tap to perform custom action"
        )
        
        public static let toggle = A11yLabel(
            label: "Enable feature",
            hint: "Double tap to toggle"
        )
    }
}

2. Apply to Views

Button("Action") { }
    .saiAccessible(A11y.MyFeature.action)

Toggle("Feature", isOn: $enabled)
    .saiAccessible(A11y.MyFeature.toggle)

SwiftUI Modifiers

.saiAccessible(_:)

Apply a pre-defined accessibility label:
Button("Send") { }
    .saiAccessible(A11y.Chat.sendButton)

.saiAccessibilityHidden()

Hide decorative elements from VoiceOver:
Image("background")
    .saiAccessibilityHidden()

.saiScaledFont(size:weight:relativeTo:)

Dynamic Type-aware font sizing:
Text("Title")
    .saiScaledFont(size: 24, weight: .bold, relativeTo: .title)

.saiReducedMotionAnimation(_:)

Motion-sensitive animation:
.saiReducedMotionAnimation(.spring(response: 0.3))

.saiContrastAwareForeground(normal:highContrast:)

High contrast color adaptation:
.saiContrastAwareForeground(
    normal: .secondary,
    highContrast: .primary
)

Debug Tools

Accessibility Audit

Run accessibility audits during development:
#if DEBUG
struct ContentView: View {
    var body: some View {
        MainContent()
            .saiAccessibilityAudit()
    }
}
#endif
This will log warnings for:
  • Missing accessibility labels
  • Touch targets smaller than 44x44 points
  • Insufficient color contrast
  • Non-scaling text

Testing Accessibility

Unit Tests

func testAccessibilityLabels() {
    // Verify labels are not empty
    XCTAssertFalse(A11y.Chat.sendButton.label.isEmpty)
    XCTAssertFalse(A11y.Chat.sendButton.hint.isEmpty)
}

UI Tests

func testVoiceOverNavigation() throws {
    let app = XCUIApplication()
    app.launch()
    
    // Navigate using accessibility identifiers
    let sendButton = app.buttons["Send message"]
    XCTAssertTrue(sendButton.exists)
    
    // Verify accessibility hint
    XCTAssertEqual(
        sendButton.label,
        "Send message"
    )
}

Manual Testing

1

Enable VoiceOver

Settings → Accessibility → VoiceOver → On
2

Navigate your app

Swipe right to move through elements
3

Verify announcements

Listen for clear, helpful descriptions
4

Test Dynamic Type

Settings → Display → Text Size → Largest

Best Practices

Label all interactive elements

Buttons, links, and controls need labels

Hide decorative images

Use .saiAccessibilityHidden() for backgrounds

Provide meaningful hints

Describe what happens when activated

Test with real users

Accessibility users find issues you miss