SwiftUI - Multiple Buttons in a List row
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
When you place multiple Button views inside a SwiftUI List row, tapping anywhere on the row triggers every button action at once. This happens because List wraps the entire row in an implicit tap gesture. Fixing this behavior requires understanding how SwiftUI resolves gesture conflicts within list rows and applying the correct button style or gesture modifiers.
The Default Problem
By default, SwiftUI treats the entire List row as a single tappable area. If you place two buttons in a row, both actions fire regardless of which button the user tapped.
Tapping anywhere on this row prints both "Edit tapped" and "Delete tapped". This is not the expected behavior for independent buttons.
The .buttonStyle(.borderless) Fix
The most common and recommended solution is to apply .buttonStyle(.borderless) to each button. This tells SwiftUI to confine the tap target to the button content rather than the full row.
Now each button responds independently. The .borderless style removes the default list row tap behavior and scopes the hit area to the button label. You can also apply the modifier to the HStack or the row itself, and it propagates to all buttons within.
Using .buttonStyle(.plain)
An alternative is .buttonStyle(.plain), which also separates button tap targets. The difference is visual: .plain removes all button styling including the default accent color, rendering the label in the primary text color.
Use .plain when you want full control over the button appearance and do not want SwiftUI to apply any default highlight or tint behavior.
Using onTapGesture Instead of Button
Another approach replaces Button with Text or any view combined with onTapGesture. Since onTapGesture is not a button, SwiftUI does not apply the row-wide tap behavior.
This works but has a trade-off: you lose the built-in button accessibility traits. Screen readers will not announce these views as buttons unless you manually add .accessibilityAddTraits(.isButton).
Combining Buttons with NavigationLink
A common scenario is having a NavigationLink row that also contains action buttons, such as a favorite or delete button. The NavigationLink consumes the entire row tap, making buttons inside it non-functional.
The solution is to hide the NavigationLink and use a separate tap gesture for navigation:
The hidden NavigationLink still provides navigation when the row is tapped outside the button area, while the star button toggles independently.
Handling Multiple Buttons with Custom Styles
For rows with several buttons that need distinct visual treatments, create a reusable button style:
Custom ButtonStyle implementations automatically scope the tap target to each button, so they work correctly inside list rows without additional modifiers.
Common Pitfalls
- Forgetting .buttonStyle on all buttons: If you apply
.borderlessto one button but not the other, the unstyled button still participates in the row-wide tap. - Losing accessibility with onTapGesture: Replacing
ButtonwithonTapGestureremoves the button accessibility trait. Always add.accessibilityAddTraits(.isButton)when using this workaround. - Applying .buttonStyle to the List instead of the row: Applying the style at the
Listlevel can have unexpected results on different OS versions. Apply it at the row or button level for consistent behavior. - Using .automatic button style: The default
.automaticstyle in aListcontext delegates to the list row style, which makes the entire row tappable. Always specify an explicit style. - Ignoring platform differences: The
.borderlessfix works on iOS but may behave differently on macOS or watchOS. Test on each target platform.
Summary
- SwiftUI
Listrows treat the entire row as a single tap target by default, causing all buttons to fire simultaneously. - Apply
.buttonStyle(.borderless)or.buttonStyle(.plain)to each button to make tap targets independent. - Use
onTapGestureas an alternative, but add accessibility traits manually to maintain screen reader support. - When combining
NavigationLinkwith action buttons, hide the link with.opacity(0)and use.buttonStyle(.borderless)on the buttons. - Custom
ButtonStyleimplementations automatically scope tap targets, making them a clean solution for rows with multiple styled actions.
Related reading
- SwiftUI - Multiple Buttons in a List row
- SwiftUI app life cycle iOS14 where to put AppDelegate code?
- SwiftUI app life cycle iOS14 where to put AppDelegate code?
- SwiftUI Binding Initialize
- SwiftUI can't tap in Spacer of HStack
- SwiftUI can't tap in Spacer of HStack
- SwiftUI. How to change the placeholder color of the TextField?
- SwiftUI How to implement a custom init with Binding variables
.png&w=3840&q=75)
Tackling System Design Interview Problems
A short course that equips you with the skills to approach system design interviews methodically.
Start the free courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.