Interface geometry · 6 min read
Every gap needs an owner.
Clean spacing is not smaller spacing. It is a system where each relationship is intentional, measured, and paid for once.
The screenshot is evidence, not a scale
A centered form can look loose for several reasons. Every field might carry both a parent gap and its own margin. A fixed-height wrapper may be distributing unused space. An illustration may be participating in layout when it should be an independent layer.
The screenshot reveals relationships, not their CSS. Its pixel values belong to that content, viewport, font, and component system. Copying them into a settings page or data table would reproduce the picture without understanding the rule.
The goal is not less space. The goal is less unexplained space.
Name the relationship first
Start with roles: label to control, peer to peer, group to group, or content to container. A consistent interface does not give every role the same number. It gives equal relationships equal rhythm, while larger boundaries keep the hierarchy legible.
The order is often more useful than a universal ratio: local space should feel no larger than peer space, and peer space should remain smaller than a major group boundary.
Notifications
Choose what reaches your inbox.
Product updates
Security alerts
- Local
- 8 px
- Peer
- 16 px
- Group
- 32 px
Hierarchy stays visible because each role has its own cadence.
Change the mode, then inspect a relationship. The example is a settings screen; the method is the same for forms, cards, toolbars, and sections.
Count the gap once
CSS declarations describe inputs. The rendered interface is the result. For two vertical boxes, measure next.top − previous.bottom, then trace what produced that distance.
A parent should usually own separation among siblings. A container owns its inset. A component primitive owns repeated internal geometry. The suspicious case is one visible boundary paid by a parent gap, a child margin, and perhaps distributed free space on top.
Toggle the two sources. In this controlled flex stack, both values add to the same rendered boundary; real layouts still need border-box measurement first.
Learn the system before you snap
An existing token system is stronger evidence than a fashionable base grid. If the product already has spacing variables and shared primitives, reuse them. If it does not, infer a small local scale from repeated relationships of the same kind.
Do not average label-to-control space together with section space. The result may be numerically tidy while the hierarchy disappears. Partition by role, component variant, density, and responsive state before choosing a candidate.
Atlas scale
Existing project tokens
candidate(role) = nearestProjectToken(median(observationsForRole))
Roles stay separate: local resolves to 8 px and group resolves to 24 px.
The same observations resolve differently when the project changes. Mixing every gap into one sample creates one neat answer and destroys two useful roles.
Empty is not redundant
Whitespace can separate ideas, create focus, protect a focus ring, preserve a usable target, or hold an intentional composition together. Empty-looking space is not evidence enough to remove it.
The illustration in the original form is a good example. It should not determine the field stack's geometry, but the space around it may still belong to the composition. Keep decoration in its own layer so helper text, validation, or translation can grow without turning the artwork into a spacer.
Unusual values are not automatically mistakes either. Font metrics, borders, safe areas, intrinsic media, and asymmetric artwork can justify an optical correction. Make those corrections after the structural geometry is coherent, and leave the reason visible.
A four-step normalization loop
- ClassifyName the semantic relationship before comparing its number.
- MeasureRead the rendered border boxes, then trace every CSS source that contributes.
- NormalizeReuse the project's token and give the boundary one primary owner.
- Pressure-testVerify wrapping, errors, focus, zoom, narrow containers, and real responsive transitions.
// Rendered factgap = next.getBoundingClientRect().top - previous.getBoundingClientRect().bottom// Then explain itowner = parentGap | childMargin | padding | trackSizing | distributedSpaceMath can identify drift, duplication, and shared edges. It cannot decide that every blank area is waste.
The result
A normalized interface may contain just as much whitespace as before. The difference is that the space now has structure.
Labels belong to controls. Peers follow one rhythm. Sections remain distinct. Shared edges align. Decoration supports the composition without controlling the content. When something moves, there is one place to change it and a reason for the value that replaces it.
Good spacing is rarely noticed. Unowned spacing is.
Use it as a skill
Install the layout audit when an interface feels padded, inconsistent, or patched together and you want to correct its geometry without redesigning its identity.
npx skills add daniasyrofi --skill interface-layout-normalizer