| name | spacing-and-layout |
| description | Spacing scales, 4/8pt base grids, layout tokens, and density. Use this when defining spacing, sizing, or layout tokens for a mobile design system. |
Spacing and Layout
Instructions
Spacing tokens are the skeleton of every screen. A finite, mathematically regular scale prevents "pixel drift" and makes responsive and density changes trivial.
1. Pick a Base Unit
Use 4pt as the atomic unit (Material, HIG, and modern RN / Flutter all align). 8pt is a legacy choice; 4pt gives finer control without inviting arbitrary values.
2. Define a Bounded Scale
Finite, named, and non-linear toward the top:
| Token | Value | Typical Use |
|---|
space.0 | 0 | Reset |
space.1 | 2 | Hairline gap, tight icon-to-text |
space.2 | 4 | Compact gap |
space.3 | 8 | Default inline gap |
space.4 | 12 | Tight section padding |
space.5 | 16 | Default container padding |
space.6 | 20 | |
space.7 | 24 | Section spacing |
space.8 | 32 | Large section spacing |
space.9 | 40 | |
space.10 | 48 | Hero spacing |
{
"space": {
"$type": "dimension",
"1": { "$value": "2px" },
"2": { "$value": "4px" },
"3": { "$value": "8px" },
"5": { "$value": "16px" }
}
}
3. Semantic Spacing Roles
Layer intent on top of the raw scale:
{
"space": {
"inset": {
"sm": { "$value": "{space.3}" },
"md": { "$value": "{space.5}" },
"lg": { "$value": "{space.7}" }
},
"stack": {
"sm": { "$value": "{space.3}" },
"md": { "$value": "{space.5}" },
"lg": { "$value": "{space.7}" }
},
"inline": {
"sm": { "$value": "{space.2}" },
"md": { "$value": "{space.3}" },
"lg": { "$value": "{space.5}" }
}
}
}
- Inset = padding inside a container.
- Stack = vertical gap between siblings.
- Inline = horizontal gap between siblings.
Components consume these roles, not the raw scale.
4. Layout Tokens
Ship these alongside spacing:
| Token | Example | Purpose |
|---|
radius.* | 0, 4, 8, 12, pill | Corner radii |
border.width.* | 1, 2 | Stroke weights |
size.icon.* | 16, 20, 24, 32 | Icon sizes |
size.touch.* | 44, 48 | Minimum touch target (iOS 44, Android 48) |
breakpoint.* | 360, 600, 840, 1200 | Responsive thresholds |
5. Density
Expose a density modifier (compact, default, comfortable) that scales space.inset.* and size.touch.* by a factor. Implement as alternative token files, not runtime math in components.
val LocalAppSpacing = staticCompositionLocalOf { Spacing.Default }
object Spacing {
data class Tokens(val insetSm: Dp, val insetMd: Dp, val insetLg: Dp, )
val Default = Tokens(insetSm = 8.dp, insetMd = 16.dp, insetLg = 24.dp)
val Compact = Tokens(insetSm = 6.dp, insetMd = 12.dp, insetLg = 20.dp)
}
6. Platform Rendering
SwiftUI:
extension View {
func dsPadding(_ inset: DSInset = .md) -> some View {
padding(inset.value)
}
}
Flutter:
class AppSpacing extends ThemeExtension<AppSpacing> {
final double insetSm, insetMd, insetLg;
const AppSpacing({required this.insetSm, required this.insetMd, required this.insetLg});
@override AppSpacing copyWith(...) => ...;
@override AppSpacing lerp(ThemeExtension<AppSpacing>? other, double t) => ...;
}
React Native:
export const space = { inset: { sm: 8, md: 16, lg: 24 } } as const;
7. Anti-Patterns
- Magic numbers (
padding: 13) anywhere in component source.
- Using
space.5 * 2 instead of space.7.
- Different scales per platform.
- Baking density into each component with booleans instead of a token tier.
- Forgetting
size.touch.* on icon-only buttons — accessibility failure.
Checklist