As a UIKit-first iOS developer, I’ve been gradually adopting SwiftUI. Some friction is expected when moving from an imperative framework to a declarative one, and not every surprise is evidence of bad design. But “the SwiftUI way” is sometimes just a polite way of saying “the framework made a design choice for you.” HStack and VStack are a good example.

A Stack Should Stack

Consider this code:

HStack {    Text("Hello")    Text("World")}
Quick Quiz

If you omit spacing, which of these is closest to what SwiftUI does?

The obvious reading is simple: place these views next to each other horizontally. However, that is not quite what happens. SwiftUI slips a gap in there even though the call site never asked for one.

That gap often looks like 8 points, but the number is not really the issue. The issue is that the stack chooses one at all. Apple’s own initializer makes the delegation explicit: spacing is optional, so leaving it out hands the distance back to SwiftUI. The code stops fully describing the layout and starts handing part of it back to the framework.

Too Helpful Is Still Wrong

A primitive should not smuggle presentation choices into an API that looks neutral at the call site.

For a layout primitive, the neutral default should be 0. A view without padding has no padding. A view without offset stays where it is. Those defaults make sense because they do not invent presentation. Stack spacing should work the same way. Any nonzero spacing is a design choice, and design choices should be explicit.

The obvious defense is that SwiftUI is trying to encode platform conventions. That is understandable. Apple wants common interfaces to breathe a little by default, and higher-level components can reasonably reflect those conventions. But HStack and VStack are not high-level components. They are primitives. A stack should stack, not quietly pick a visual rhythm for you.

That is why this matters beyond one initializer. SwiftUI often looks concise because behavior has been moved out of the call site and into framework interpretation. That can feel elegant, but it is often less honest. The code looks more concise while saying less.

The Practical Rule

My practical rule is simple: always set spacing, especially when you intend for it to be 0. Use a linter if you have to. If the framework wants breathing room, your code should say so.

HStack(spacing: .zero) {    Text("Hello")    Text("World")}