SwiftUI Stacks Insist Upon Themselves
SwiftUI sneaks a little breathing room between views and calls it a default.
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")}
If you omit spacing, which of these is closest to what SwiftUI does?
That would be the neutral default.
It would also be the one I'd argue for, but it is not what SwiftUI does when spacing is omitted.
Reasonable guess, still wrong.
A smaller built-in spacing would at least feel more conservative, but SwiftUI is not promising a fixed small number here either.
Close, but not really.
It often looks like 8, which is why the default can feel deceptively concrete. But SwiftUI is really choosing a default distance rather than promising a fixed value.
That is the real behavior.
SwiftUI is not really promising a fixed number here. It is choosing a default distance for you, which is the whole complaint.
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")}