Skip to content

Use .labelStyle(.iconOnly) Instead of Nesting Image in Button

The proper SwiftUI pattern for icon-only buttons that preserves accessibility without sacrificing readability.

Stop Fighting the Framework

A pattern I see constantly in SwiftUI code is manually creating icon-only buttons by putting an Image directly inside a Button‘s label closure. It works visually, but it throws away accessibility information and fights against SwiftUI’s design.

The Wrong Way

Button {
   toggleSidebar()
} label: {
   Image(systemName: "sidebar.left")
}

This renders a tappable icon, but VoiceOver has no meaningful label to announce. The user hears something like “button” or the raw SF Symbol name, which is not helpful.

The Right Way

Button("Toggle Sidebar", systemImage: "sidebar.left") {
   toggleSidebar()
}
.labelStyle(.iconOnly)

Or using Label explicitly:

Button(action: toggleSidebar) {
   Label("Toggle Sidebar", systemImage: "sidebar.left")
}
.labelStyle(.iconOnly)

Code comparison showing the wrong way versus the right way

Why This Matters

The Label view carries both a title and an icon. When you apply .labelStyle(.iconOnly), SwiftUI hides the title visually but preserves it in the accessibility tree. VoiceOver will announce “Toggle Sidebar, button” – exactly what the user needs to hear.

This pattern also makes your code more adaptable. If you later decide to show text alongside the icon (for example, in a toolbar on iPad), you just change the label style to .titleAndIcon. No restructuring needed.

Beyond Buttons

The same principle applies to any view that accepts a Label: NavigationLink, Toggle, Picker, menu items. Whenever you are tempted to use a bare Image, ask yourself whether a Label with a style modifier would be more appropriate. In almost every case, it is.

Found this helpful? Follow me on Bluesky and Mastodon for more Swift tips and indie dev updates.