Spacing & Elevation¶
How we use space (between elements) and depth (between layers). Both compose into the "tonal layering" approach that defines Tomoda's UI.
Spacing scale¶
A 4-pixel base with named steps. Stick to the scale — half-steps and arbitrary pixels make the system feel sloppy.
When to use which¶
| Step | Use |
|---|---|
xs (4) |
Icon-to-text gap, inline label-value pairing |
sm (8) |
Tight grouping — within a card, between list metadata |
md (12) |
List item vertical separation |
lg (16) |
Section padding inside a card |
xl (24) |
Card internal padding, top-of-section margin |
2xl (32) |
Between major content blocks |
3xl (48) |
Hero / display surfaces, generous landing spacing |
Avoid lg for card internal padding when content is rich — xl or 2xl lets the content breathe and reinforces the "curator's frame" feel.
Border radius¶
Three corner sizes. Use one consistently per surface tier.
| Token | Value | Use |
|---|---|---|
sm |
6px | Chips, pills, tight controls |
md |
10px | Buttons, inputs, small cards |
lg |
16px | Cards, modals, sheets |
xl |
24px | Large hero surfaces, full-bleed cards |
Don't mix md and lg for elements at the same hierarchy level — the inconsistency reads as accidental.
Surface ladder (depth without shadow)¶
The core insight: depth comes from stepping the surface ladder, not from shadows. Place a higher-tier surface on top of a lower-tier one and the eye sees real depth.
The four tiers¶
| Tier | Dark hex | Light hex | Use |
|---|---|---|---|
background |
#0e0e0f |
#FFFFFF |
App root, scroll background |
surface |
#18181a |
#FFFFFF |
Cards on top of background |
surfaceContainerLow |
#1c1c1f |
#F4F4F5 |
Grouping containers, list backgrounds |
surfaceContainerHigh |
#252528 |
#E4E4E7 |
Interactive layers, inputs |
surfaceContainerHighest |
#2e2e32 |
#D4D4D8 |
Popovers, focused inputs, hover states |
Composition rules¶
- One step up at a time. Don't jump from
backgroundstraight tosurfaceContainerHighest— the contrast looks broken. Step gradually. - Nest to imply depth. A
surfacecard containing asurfaceContainerLowsection reads as "carved" — depth without a shadow. - Reverse the ladder in light mode. In light mode the same semantic tokens still mean "deeper" — they just step down in luminance rather than up. The component code never branches on mode.
Shadows (used sparingly)¶
Reserved for elements that genuinely float. The surface ladder handles all in-context depth.
| Token | Value | Use |
|---|---|---|
shadow-modal |
0px 24px 48px rgba(0, 0, 0, 0.35) |
Modals, fullscreen sheets |
shadow-floating |
0px 12px 24px rgba(0, 0, 0, 0.25) |
Dropdowns, popovers |
shadow-elevated |
0px 4px 12px rgba(0, 0, 0, 0.20) |
Sticky bottom bars, FABs |
Shadow rules¶
- Never pure black. Shadow opacity tops out at 0.4 to keep the warm-charcoal feel.
- Diffused, not crisp. Large blur radius, no spread. Shadows should feel like ambient occlusion, not hard cutouts.
- No top shadow. Light always falls from above; shadow always falls below. Top-shadows look like floating UFOs.
- No shadow on a
backgroundsurface. If the underlying surface is the app root, you don't need a shadow to imply depth — use the surface ladder.
Glassmorphism¶
A specific exception to the no-shadow rule: floating UI that should feel frosted rather than opaque.
- Surface:
surfaceContainerLowat 70% opacity - Backdrop filter:
blur(24px) - Wrapped by the
GlassViewcomponent which handles native (expo-blur) and web (backdrop-filterCSS) paths.
Use for floating navigation pills, contextual overlays on top of imagery, and the chat composer when it sits over content. Don't use as a default — every glass surface is a moment.
Ghost borders¶
When a border is absolutely required (focus state, error state, accessibility outline), use the border token, never solid:
- Dark:
rgba(255, 255, 255, 0.15) - Light:
rgba(0, 0, 0, 0.10)
This is the only sanctioned use of a border in product UI. Solid #000 / #fff borders are forbidden — see the no-line rule.
Layout density¶
Two density modes appear in production:
- Comfortable (default) —
xlpadding on cards,lg/xlbetween list items. Used everywhere user-content lives (chat, event detail, profile). - Dense —
lgpadding,mdbetween items. Used in admin tables, settings lists, anywhere the screen is configuration-heavy.
Don't introduce a third density. Comfortable defaults serve almost everything; dense covers the rest.