```html SwiftUI: How to Add Accessibility Labels (iOS 17+, 2026)

How to Add Accessibility Labels in SwiftUI

iOS 17+ Xcode 16+ Beginner APIs: accessibilityLabel Updated: May 11, 2026
TL;DR

Attach .accessibilityLabel("…") to any SwiftUI view to give VoiceOver a clear spoken name. Pair it with .accessibilityHint("…") to describe the action, and use .accessibilityHidden(true) to silence purely decorative elements.

Button(action: likePost) {
    Image(systemName: "heart.fill")
        .foregroundStyle(.red)
}
.accessibilityLabel("Like post")
.accessibilityHint("Double-tap to like this post")

// Decorative divider — VoiceOver skips it
Divider()
    .accessibilityHidden(true)

Full implementation

The example below builds a social-style post card. Icon-only buttons get explicit .accessibilityLabel strings, a like-count value is surfaced with .accessibilityValue, and the avatar image is hidden from VoiceOver because the author's name is already announced by the adjacent Text view. The action row groups its children so VoiceOver treats each button as one tap target rather than traversing label and icon separately.

import SwiftUI

struct PostCard: View {
    let author: String
    let body: String
    var likeCount: Int

    @State private var liked = false

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {

            // MARK: Header
            HStack(spacing: 10) {
                Image(systemName: "person.circle.fill")
                    .font(.title2)
                    .foregroundStyle(.secondary)
                    // Decorative: author name Text already provides context
                    .accessibilityHidden(true)

                Text(author)
                    .font(.headline)
                    // Explicit label clarifies the element's role
                    .accessibilityLabel("Author: \(author)")
            }

            // MARK: Post body
            Text(body)
                .font(.body)
                .foregroundStyle(.primary)
                // Body text reads naturally; no extra label needed

            Divider()
                .accessibilityHidden(true)

            // MARK: Action row
            HStack(spacing: 24) {

                // Like button with dynamic value
                Button {
                    liked.toggle()
                } label: {
                    Label(
                        liked ? "Liked" : "Like",
                        systemImage: liked ? "heart.fill" : "heart"
                    )
                    .foregroundStyle(liked ? .red : .secondary)
                }
                .accessibilityLabel(liked ? "Unlike post" : "Like post")
                .accessibilityHint("Double-tap to \(liked ? "remove your like" : "like this post")")
                .accessibilityValue("\(likeCount + (liked ? 1 : 0)) likes")

                // Share button — icon only, needs a label
                Button {
                    // share action
                } label: {
                    Image(systemName: "square.and.arrow.up")
                        .foregroundStyle(.secondary)
                }
                .accessibilityLabel("Share post")
                .accessibilityHint("Double-tap to open the share sheet")

                // Bookmark button with toggle trait
                Button {
                    // bookmark action
                } label: {
                    Image(systemName: "bookmark")
                        .foregroundStyle(.secondary)
                }
                .accessibilityLabel("Bookmark post")
                .accessibilityAddTraits(.isButton)
            }
            .accessibilityElement(children: .contain)
        }
        .padding()
        .background(.background)
        .clipShape(RoundedRectangle(cornerRadius: 16))
        .shadow(color: .black.opacity(0.06), radius: 8, y: 4)
    }
}

#Preview {
    PostCard(
        author: "Ada Lovelace",
        body: "The Analytical Engine weaves algebraic patterns just as the Jacquard loom weaves flowers and leaves.",
        likeCount: 42
    )
    .padding()
    .background(Color(.systemGroupedBackground))
}

How it works

  1. .accessibilityLabel("Author: \(author)") — overrides the default accessible name that VoiceOver would derive from the view's content. Prefixing with a role word ("Author:") gives users immediate context without them needing to navigate back up the hierarchy.
  2. .accessibilityValue("\(likeCount + (liked ? 1 : 0)) likes") — separates the element's identity (label) from its current state (value). VoiceOver announces both: "Like post, 43 likes, button." This pattern mirrors how Stepper and Slider report their current value.
  3. .accessibilityHint("Double-tap to…") — describes the outcome of the primary gesture in the imperative form recommended by Apple's Human Interface Guidelines. Hints are announced after a brief pause, so keep them concise (under one sentence).
  4. .accessibilityHidden(true) on the avatar Image and Divider — removes purely decorative or redundant elements from the accessibility tree, keeping the VoiceOver traversal order clean and fast.
  5. .accessibilityElement(children: .contain) on the action HStack — keeps each child button as its own focus target while logically grouping them in the tree. Use .combine instead if you want the whole row to be a single tap target (useful for list rows).

Variants

Combine a card row into one focusable unit

For a settings-style list row where the label and chevron should read as one element, use .accessibilityElement(children: .combine) on the container. VoiceOver merges all child text into a single announcement.

HStack {
    Image(systemName: "bell.fill")
        .foregroundStyle(.orange)
    VStack(alignment: .leading) {
        Text("Notifications")
            .font(.body)
        Text("Enabled")
            .font(.caption)
            .foregroundStyle(.secondary)
    }
    Spacer()
    Image(systemName: "chevron.right")
        .foregroundStyle(.tertiary)
}
// VoiceOver says: "Notifications, Enabled"
.accessibilityElement(children: .combine)
.accessibilityHint("Double-tap to open notification settings")

Dynamic labels for loading states

When a view toggles between a loading spinner and real content, update the label dynamically so VoiceOver announces the change. Use a computed property or a ternary inside .accessibilityLabel:

@State private var isLoading = true

ProgressView()
    .opacity(isLoading ? 1 : 0)
    .accessibilityLabel(isLoading ? "Loading posts" : "Posts loaded")
    .accessibilityAddTraits(isLoading ? .updatesFrequently : [])

Common pitfalls

Prompt this with Claude Code

When using Soarias or Claude Code directly to implement this:

Implement accessibility labels in SwiftUI for iOS 17+.
Use accessibilityLabel, accessibilityHint, accessibilityValue,
accessibilityHidden, and accessibilityElement(children:).
Make it accessible (VoiceOver labels on every interactive control).
Add a #Preview with realistic sample data.

In Soarias's Build phase, paste this prompt into the active session after scaffolding your view — Claude Code will audit every Button, Image, and custom control in the file and add the appropriate accessibility modifiers in one pass.

Related

FAQ

Does this work on iOS 16?

Yes — .accessibilityLabel, .accessibilityHint, .accessibilityValue, and .accessibilityHidden are all available back to iOS 13. The code in this guide compiles and runs on iOS 16 without changes. The iOS 17+ requirement on this page reflects the minimum deployment target recommended by Soarias for new projects in 2026; none of the APIs shown are iOS-17-exclusive.

How do I test VoiceOver labels without a physical device?

The Xcode Accessibility Inspector (Xcode → Open Developer Tool → Accessibility Inspector) lets you point at any running Simulator view and see the exact label, hint, value, and traits that VoiceOver would announce. You can also run the Audit tab to auto-detect missing labels. For on-device testing, triple-click the side button to toggle VoiceOver, then swipe right to step through focusable elements in order.

What's the UIKit equivalent?

In UIKit you set the same concepts as properties directly on UIView: view.accessibilityLabel = "Like post", view.accessibilityHint = "Double-tap to like", view.accessibilityValue = "42 likes", and view.isAccessibilityElement = false for hidden elements. SwiftUI's modifier-based API maps 1-to-1 to these properties under the hood, so bridging between the two in UIViewRepresentable wrappers requires no extra work.

Last reviewed: 2026-05-11 by the Soarias team.

```