Prisma

Plan Tối Ưu Spacing System — Prisma Design System

Date: 2026-03-12 Status: 📋 PLAN — Chờ duyệt Sources:


0. Executive Summary

Sau khi tổng hợp 5 nguồn nghiên cứu, plan này đề xuất 3 phase tối ưu spacing system:

Phase Tên Effort Breaking?
Phase 1 Spatial Rhythm Alignment Small ⚠️ Minor
Phase 2 iOS 26 Future-Proofing Medium ❌ Non-breaking
Phase 3 Density Architecture Simplification Medium ⚠️ Breaking (controlled)

[!IMPORTANT] Core Insight từ research: UI premium không đến từ color/typography, mà từ spacing rhythm chặt. Prisma cần enforce 2× rule3-layer spacing model để đạt cảm giác "clean" như Airbnb/Apple/Stripe.


1. Hiện Trạng — Audit Nhanh

1.1 Spacing Scale hiện tại

text
Prisma primitives: --spacing = 4px
Scale dùng: ×0.5(2) → ×1(4) → ×2(8) → ×3(12) → ×4(16) → ×5(20) → ×6(24) → ×8(32)

1.2 So với Industry Standard

Industry Pattern Values Prisma hỗ trợ?
4-8-16 rule 4, 8, 16 ✅ Có
2× rule (8→16→32) 8, 16, 32 ✅ Có (×2→×4→×8)
Big app convergence 4, 8, 16, 24, 32, 48 ⚠️ Thiếu 48
3-layer model micro(4/8), comp(12/16), layout(24/32/48) ⚠️ Mờ boundary

1.3 Vấn đề phát hiện

# Vấn đề Source Severity
P1 2× rule bị phá ở nhiều tier — comp.stack-gap = 4px nhưng group.stack-gap = 12px (×3, không phải ×2) Notion Spatial Rhythm 🟡
P2 Thiếu layer 48px — big app pattern cần spacing 48 cho page sections ChatGPT + Notion 🟡
P3 comp.padding-default mobile (12px) vs iOS 26 (16pt) — gap iOS 26 Research 🟡
P4 comp.radius mobile (8px) quá nhỏ cho iOS 26 capsule trend iOS 26 Research 🔴
P5 Thiếu capsule radius token (9999px) iOS 26 Research 🔴
P6 Thiếu concentric radius concept iOS 26 Research 🟡
P7 Thiếu comp.height tokens Apple/MD3 Comparison 🟡
P8 3 density files → 2 files đã simplify nhưng architecture còn verbose Density Simplification 🟡
P9 Vertical rhythm chưa enforce — spacing không link với line-height Notion Spatial Rhythm 🟢

2. Phase 1: Spatial Rhythm Alignment ⭐ Quan trọng nhất

Goal: Enforce 2× rule + 3-layer spacing model → UI nhìn "premium" hơn. Effort: Small | Risk: Minor breaking

2.1 Định nghĩa 3 Spacing Layers

text
┌────────────────────────────────────────────────┐
│  Layer 3: LAYOUT                                │
│  Spacing: 24 / 32 / 48                         │
│  Dùng cho: page margin, section gap, page gap   │
│                                                  │
│  ┌──────────────────────────────────────────┐   │
│  │  Layer 2: COMPONENT                      │   │
│  │  Spacing: 12 / 16                        │   │
│  │  Dùng cho: card padding, form gap,       │   │
│  │  component internal padding              │   │
│  │                                          │   │
│  │  ┌──────────────────────────────────┐    │   │
│  │  │  Layer 1: MICRO                  │    │   │
│  │  │  Spacing: 4 / 8                  │    │   │
│  │  │  Dùng cho: icon-text gap,        │    │   │
│  │  │  inline spacing, chip padding    │    │   │
│  │  └──────────────────────────────────┘    │   │
│  └──────────────────────────────────────────┘   │
└────────────────────────────────────────────────┘

2.2 2× Rule Application

Pattern nhịp chính: 8 → 16 → 32

Current Token Hiện tại (Mobile) Đề xuất Lý do
comp.stack-gap-default ×1 = 4px ×2 = 8px 4→8: micro layer floor. Apple/MD3 đều ≥ 8px cho form gap
comp.stack-gap-small ×1 = 4px ×1 = 4px Giữ: micro layer min
group.stack-gap-default ×3 = 12px ×4 = 16px 8→16: đúng 2× rule
group.stack-gap-small ×1 = 4px ×2 = 8px Nâng lên micro base
section.stack-gap-default ×4 = 16px ×4 = 16px ✅ Giữ: đã đúng
page.stack-gap-default ×4 = 16px ×6 = 24px 16→24: layout layer, tạo nhịp rõ hơn
page.stack-gap-large ×6 = 24px ×8 = 32px 24→32: layout layer max

Kết quả 2× rhythm:

text
BEFORE: 4 → 12 → 16 → 24    (nhảy không đều: ×3, ×1.33, ×1.5)
AFTER:  4 → 8 → 16 → 24 → 32 (nhảy đều: ×2, ×2, ×1.5, ×1.33)
         micro  comp  layout

2.3 Thêm Spacing Token 48px

json
// Thêm vào page tier — cả mobile.tokens.json VÀ web.tokens.json
{
    "page": {
        "stack-gap-xlarge": {
            "$type": "number",
            "$value": "calc(var(--spacing) * 12)",
            "$description": "48px — major section divider, hero spacing"
        }
    }
}

Lý do: Tất cả big app (Airbnb, Apple, Stripe, Uber) đều có spacing 48px cho section-level separation. Pattern chuẩn: 4, 8, 16, 24, 32, 48.

2.4 Comp Padding Adjustment (from Apple/MD3 research)

Token Mobile hiện tại Mobile mới Web hiện tại Web mới
comp.padding-default ×3 = 12px ×4 = 16px ×3 = 12px ×4 = 16px
comp.padding-small ×2 = 8px ×2 = 8px ×2 = 8px ×3 = 12px
comp.padding-xsmall ×1 = 4px ×1 = 4px ×1 = 4px ×2 = 8px

[!WARNING] comp.padding-default 12→16px sẽ tăng size tất cả button/input. Đây là breaking change nhỏ nhưng needed — cả Apple (16pt) lẫn MD3 (12-16dp) đều chuộng giá trị này.

2.5 Summary Phase 1: Token Changes

diff
## mobile.tokens.json changes

## comp tier
- "comp.stack-gap-default": "var(--spacing)"          # 4px
+ "comp.stack-gap-default": "calc(var(--spacing) * 2)" # 8px

- "comp.padding-default": "calc(var(--spacing) * 3)"   # 12px  
+ "comp.padding-default": "calc(var(--spacing) * 4)"   # 16px

## group tier
- "group.stack-gap-default": "calc(var(--spacing) * 3)" # 12px
+ "group.stack-gap-default": "calc(var(--spacing) * 4)" # 16px

- "group.stack-gap-small": "var(--spacing)"             # 4px
+ "group.stack-gap-small": "calc(var(--spacing) * 2)"   # 8px

## page tier
- "page.stack-gap-default": "calc(var(--spacing) * 4)"  # 16px
+ "page.stack-gap-default": "calc(var(--spacing) * 6)"  # 24px

- "page.stack-gap-large": "calc(var(--spacing) * 6)"    # 24px
+ "page.stack-gap-large": "calc(var(--spacing) * 8)"    # 32px

+ "page.stack-gap-xlarge": "calc(var(--spacing) * 12)"  # 48px (NEW)

3. Phase 2: iOS 26 Future-Proofing

Goal: Thêm tokens cho iOS 26 Liquid Glass mà không break gì. Effort: Medium | Risk: Non-breaking (toàn bộ additive)

3.1 Capsule Radius Token

json
// Thêm vào CẢ mobile.tokens.json VÀ web.tokens.json
{
    "comp": {
        "radius-capsule": {
            "$type": "number",
            "$value": "9999px",
            "$description": "Capsule/pill shape — iOS 26 primary button shape. Use for buttons, tags, nav items."
        }
    }
}

3.2 Nâng Comp Radius Mobile

Token Hiện tại Đề xuất Lý do
comp.radius (mobile) --radius-xs (8px) --radius-sm (12px) iOS 26 fixed shape = 10-12pt. Apple HIG convention ~10pt.
diff
## mobile.tokens.json
- "comp.radius": "var(--radius-xs)"   # 8px
+ "comp.radius": "var(--radius-sm)"   # 12px

3.3 Concentric Radius CSS Helper

css
/* Thêm vào variables.css — utility cho nested containers */
:root {
    --radius-concentric: max(0px, calc(var(--group-radius-default) - var(--group-padding-default)));
}

/* Usage example:
   .card { border-radius: var(--group-radius-default); padding: var(--group-padding-default); }
   .card > .inner { border-radius: var(--radius-concentric); }
*/

3.4 Component Height Tokens

json
// Thêm vào CẢ 2 density files
{
    "comp": {
        "height-default": {
            "$type": "number",
            "$value": "44px",
            "$description": "Standard interactive height (Apple: 44pt)"
        },
        "height-large": {
            "$type": "number",
            "$value": "48px",
            "$description": "Large button/input (MD3 comfortable target)"
        },
        "height-small": {
            "$type": "number",
            "$value": "36px",
            "$description": "Compact interactive element"
        }
    }
}

3.5 Layout Tokens (iOS 26 specific)

json
// Thêm vào mobile.tokens.json
{
    "layout": {
        "tab-bar-height": {
            "$type": "number",
            "$value": "68px",
            "$description": "iOS 26 floating tab bar height"
        },
        "nav-bar-height": {
            "$type": "number",
            "$value": "44px",
            "$description": "iOS 26 floating nav bar height"
        },
        "safe-area-bottom": {
            "$type": "number",
            "$value": "34px",
            "$description": "iPhone home indicator safe area"
        }
    }
}

4. Phase 3: Density Architecture Simplification

Goal: Simplify density switching (from density-simplification-research). Effort: Medium | Risk: Breaking (controlled migration)

4.1 Core Change

text
BEFORE:
  tokens/density/compact.tokens.json  ← DELETE
  tokens/density/default.tokens.json  ← DELETE  
  tokens/density/comfortable.tokens.json ← DELETE
  tokens/density/mobile.tokens.json   ← KEEP
  tokens/density/web.tokens.json      ← KEEP

CSS:
  3 @layer blocks + responsive media query = ~290 lines

AFTER:  
  tokens/density/mobile.tokens.json   ← app mode
  tokens/density/web.tokens.json      ← web mode (default)

CSS:
  :root = web (default)
  [data-mode="app"] = mobile override
  = ~174 lines (-116 lines)

4.2 Data Attribute Migration

html
<!-- BEFORE -->
<body data-style-mode="compact">

<!-- AFTER -->
<body data-mode="app">

<!-- Web default — no attribute needed -->
<body data-brand="prisma" data-theme="light">

4.3 File Cleanup

Action Files
DELETE compact.tokens.json, default.tokens.json, comfortable.tokens.json
KEEP mobile.tokens.json (= app mode), web.tokens.json (= web/default)
UPDATE variables.css — rewrite style-modes section
UPDATE All docs referencing data-style-mode

5. Final Spacing Scale — After All Phases

5.1 The "Prisma Rhythm" Scale

text
Token Scale (mobile mode):

MICRO LAYER
  2px   ×0.5   comp.padding-xsmall, min gap
  4px   ×1     comp.stack-gap-small, comp.inline-gap-small
  8px   ×2     comp.stack-gap-default, comp.padding-small, group.stack-gap-small

COMPONENT LAYER  
  12px  ×3     group.stack-gap-default → 16px ✦, comp.padding-default → 16px ✦
  16px  ×4     section.padding-default, group.padding-default, comp.padding-default ✦

LAYOUT LAYER
  20px  ×5     section.padding-large
  24px  ×6     page.padding-large, page.stack-gap-default ✦
  32px  ×8     page.stack-gap-large ✦
  48px  ×12    page.stack-gap-xlarge (NEW ✦)

✦ = changed/new values

5.2 2× Rule Verification

text
Micro:   4 → 8    (×2) ✅
Comp:    8 → 16   (×2) ✅
Layout: 16 → 32   (×2) ✅

Full chain: 4 → 8 → 16 → 32  (perfect 2× rhythm)
Extras:    12, 24, 48 (×1.5 and ×1.5 fills for fine-tuning)

5.3 Radius Scale — After Phase 2

text
MOBILE:
  comp.radius:         12px (was 8px)     ← nâng cho iOS 26
  comp.radius-capsule: 9999px             ← NEW
  group.radius-default: 12px              ← giữ
  section.radius-default: 16px            ← giữ  
  page.radius-default: 16px               ← giữ

WEB:
  comp.radius:         12px               ← giữ
  comp.radius-capsule: 9999px             ← NEW
  group.radius-default: 16px              ← giữ
  section.radius-default: 24px            ← giữ
  page.radius-default: 24px               ← giữ

6. Visual Before/After

6.1 Spacing Rhythm — Mobile Screen

text
BEFORE (inconsistent rhythm):
┌──────────────────────┐
│ margin: 16           │
│ ┌──────────────────┐ │
│ │ section gap: 16  │ │  ← section = page gap (flat)
│ │ ┌──────────────┐ │ │
│ │ │card gap: 12  │ │ │  ← ×0.75 (odd ratio)
│ │ │ icon→text: 4 │ │ │  ← ×0.33 (too tight)
│ │ │ form gap: 4  │ │ │  ← ×0.33 (too tight)
│ │ └──────────────┘ │ │
│ └──────────────────┘ │
└──────────────────────┘

AFTER (2× rhythm):
┌──────────────────────┐
│ margin: 16           │
│ ┌──────────────────┐ │ 
│ │ section gap: 24  │ │  ← ×1.5 (clear hierarchy)
│ │ ┌──────────────┐ │ │
│ │ │card gap: 16  │ │ │  ← ×2 from micro ✅
│ │ │ icon→text: 4 │ │ │  ← micro layer ✅
│ │ │ form gap: 8  │ │ │  ← ×2 from 4 ✅
│ │ └──────────────┘ │ │
│ └──────────────────┘ │
└──────────────────────┘

Rhythm: 4 → 8 → 16 → 24 (feels intentional, premium)

7. Implementation Priority

# Task Phase Effort Impact Priority
1 Enforce 2× rule — adjust stack-gap values P1 Small 🔴 High — rhythm quality P0
2 Nâng comp.padding-default 12→16px P1 Small 🔴 High — Apple/MD3 aligned P0
3 Thêm comp.radius-capsule (9999px) P2 Tiny 🔴 High — iOS 26 ready P0
4 Nâng comp.radius mobile 8→12px P2 Small 🟡 Medium — iOS 26 aligned P1
5 Thêm comp.height tokens (44/48/36) P2 Small 🟡 Medium — standardize P1
6 Thêm page.stack-gap-xlarge (48px) P1 Tiny 🟡 Medium — big section gap P1
7 Thêm concentric radius helper P2 Small 🟡 Medium — iOS 26 nesting P2
8 Thêm layout. tokens* (tab/nav bar) P2 Small 🟢 Low-Med — structured P2
9 Density arch simplification P3 Medium 🟡 Medium — DX improvement P2
10 Tạo ios26.json style Future Large 🟢 Future — full glass P3

8. Quyết Định Cần Xác Nhận

[!IMPORTANT] Cần confirm trước khi implement:

  1. 2× rule enforcement: Đồng ý nâng comp.stack-gap 4→8px? Sẽ tăng khoảng cách giữa form elements.
  2. comp.padding 12→16px: Chấp nhận buttons/inputs lớn hơn trên mobile?
  3. comp.radius 8→12px: Mobile components bo tròn hơn — OK?
  4. Capsule token: Thêm comp.radius-capsule: 9999px — đồng ý?
  5. 48px spacing: Thêm page.stack-gap-xlarge cho major sections?
  6. Phase order: Bắt Phase 1 trước (rhythm) hay Phase 2 trước (iOS 26)?
  7. Density simplification (Phase 3): Xác nhận data-mode="app" thay data-style-mode="compact"?

9. Appendix: Research Cross-Reference

Insight Source Applied in
2× rule: group gap = 2× component gap Notion Spatial Rhythm Phase 1 §2.2
3-layer spacing: micro/component/layout ChatGPT + Notion Phase 1 §2.1
Pattern 4,8,16,24,32,48 ChatGPT (big app convergence) Phase 1 §2.3
Spacing rhythm > color/typography for premium feel Notion insight Phase 1 core thesis
Vertical rhythm = spacing as multiple of line-height Notion Phase 1 §2.1 (concept)
Optical spacing — sometimes "wrong math, right eye" Notion Acknowledged, not token-ized
iOS 26 capsule shape iOS 26 Research Phase 2 §3.1
iOS 26 concentric radius iOS 26 Research Phase 2 §3.3
iOS 26 spacious padding (+20%) iOS 26 Research Phase 1 §2.4
comp.padding quá nhỏ vs Apple 16pt Apple/MD3 Comparison Phase 1 §2.4
Touch target 44px already added Apple/MD3 Comparison ✅ Already in tokens
Density simplification web/app Density Simplification Phase 3
Squint test spacing (Airbnb) ChatGPT Validation method
Density ladder (Grab/Gojek) ChatGPT Already doing via web/mobile split