Radius Reform — Gap Analysis & Proposal
Radius hiện tại là static (trỏ thẳng tới primitive), trong khi spacing là dynamic (
calc(var(--spacing) * N)). Khi chuyển density, spacing co giãn nhưng radius đứng yên → mất đồng bộ.
1. Gap Analysis
Gap 1: Radius static, Spacing dynamic
Spacing: calc(var(--spacing) * 4) → 12/16/20px khi thay --spacing
Radius: var(--radius-sm) → LUÔN 12px, bất kể density
Khi demo switch compact(3px) / comfortable(4px) / spacious(5px), padding/gap thay đổi mượt mà nhưng mọi border-radius đứng yên.
Gap 2: --comp-radius = --group-radius-default trên Mobile
Cả hai cùng trỏ var(--radius-sm) = 12px → mất concentric hierarchy.
Group: border-radius: var(--group-radius-default) = 12px
└ Comp: border-radius: var(--comp-radius) = 12px ← CÙNG GIÁ TRỊ!
Concentric rule yêu cầu: R_inner = R_outer − padding = 12 − 16 = −4 → phải giảm 1 step, nhưng hiện tại không giảm.
Gap 3: lint-css.js mapping sai
// HIỆN TẠI — SAI:
'4px': 'var(--radius-sm)', // radius-sm = 12px, KHÔNG PHẢI 4px
'6px': 'var(--radius-md)', // radius-md = 16px, KHÔNG PHẢI 6px
'8px': 'var(--radius-md)', // radius-md = 16px, KHÔNG PHẢI 8px
'12px': 'var(--radius-lg)', // radius-lg = 20px, KHÔNG PHẢI 12px
'16px': 'var(--radius-xl)', // radius-xl = 24px, KHÔNG PHẢI 16px
Mapping này theo Tailwind v3 mặc định, không match primitive scale thực tế của Prisma DS.
Gap 4: Thiếu primitive cho 4px / 6px
Scale bắt đầu từ --radius-xs = 8px. Không có token cho giá trị nhỏ hơn.
Gap 5: Hardcode 4px trong demo
demo-app.html line 354, 359: border-radius: var(--comp-radius) trên spending bar → không có token nào match.
2. Đề xuất: Radius Formula-Based (giống Spacing)
Ý tưởng
Chuyển radius từ static primitive reference sang formula-based dùng calc(var(--spacing) * N), giống spacing.
/* TRƯỚC (static): */
--comp-radius: var(--radius-sm); /* LUÔN 12px */
/* SAU (formula): */
--comp-radius: calc(var(--spacing) * 3); /* 9/12/15px theo density */
Radius Multiplier Table
| Tier Token | Multiplier | Compact (3px) | Comfortable (4px) | Spacious (5px) |
|---|---|---|---|---|
--page-radius-default |
×6 | 18px | 24px | 30px |
--section-radius-default |
×4 | 12px | 16px | 20px |
--section-radius-small |
×3 | 9px | 12px | 15px |
--group-radius-default |
×3 | 9px | 12px | 15px |
--group-radius-small |
×2 | 6px | 8px | 10px |
--comp-radius |
×2 | 6px | 8px | 10px |
--comp-radius-capsule |
9999px | — | — | — |
Concentric Check (Comfortable, 4px)
| Parent → Child | R_outer | Padding | R_concentric | R_actual | ✓? |
|---|---|---|---|---|---|
| Page → Section | 24px | 24px | 0 | 16px | ✅ |
| Section → Group | 16px | 20px | −4 → 0 | 12px | ✅ |
| Group → Comp | 12px | 24px | −12 → 0 | 8px | ✅ FIX: giờ comp < group |
Concentric Check (Compact, 3px)
| Parent → Child | R_outer | Padding | R_concentric | R_actual | ✓? |
|---|---|---|---|---|---|
| Page → Section | 18px | 18px | 0 | 12px | ✅ |
| Section → Group | 12px | 15px | −3 → 0 | 9px | ✅ |
| Group → Comp | 9px | 18px | −9 → 0 | 6px | ✅ |
Web density — giữ giá trị lớn hơn
| Tier Token | Multiplier | Web (4px) | Web sẽ ra |
|---|---|---|---|
--page-radius-default |
×6 | 24px | = hiện tại (radius-xl) |
--section-radius-default |
×5 | 20px | ≈ hiện tại (radius-xl=24, chênh 4px) |
--group-radius-default |
×4 | 16px | = hiện tại (radius-md) |
--comp-radius |
×3 | 12px | = hiện tại (radius-sm) |
Web default giữ nguyên hoặc gần bằng giá trị hiện tại → không breaking change.
3. Tác động & Files cần sửa
Token JSON
| File | Thay đổi |
|---|---|
mobile.tokens.json |
Radius → calc(var(--spacing) * N) |
web.tokens.json |
Radius → calc(var(--spacing) * N) |
CSS
| File | Thay đổi |
|---|---|
variables.css |
Auto-generated bởi sync-tokens.js |
primitives.css |
Giữ nguyên — primitive scale vẫn tồn tại cho reference |
Scripts
| File | Thay đổi |
|---|---|
lint-css.js |
Fix BORDER_RADIUS_MAP cho đúng giá trị thực |
sync-tokens.js |
Không cần đổi — nó đã emit $value as-is |
Demos
| File | Thay đổi |
|---|---|
demo-app.html |
Xóa hardcoded 4px, dùng var(--comp-radius) hoặc var(--radius-xs) |
Style overrides (ios17, liquid)
| File | Ghi chú |
|---|---|
ios17.json |
Giữ own border-radius object — là style-specific, không dùng density formula |
liquid.json |
Tương tự |
4. lint-css.js Fix — Correct Mapping
// SAU (đúng với Prisma primitive scale):
const BORDER_RADIUS_MAP = {
'0': 'var(--radius-none) or 0',
'4px': 'var(--radius-xs) or var(--comp-radius)', // radius-xs = 8px (gần nhất)
'6px': 'var(--radius-xs)',
'8px': 'var(--radius-xs)', // radius-xs = 8px ✓
'12px': 'var(--radius-sm)', // radius-sm = 12px ✓
'16px': 'var(--radius-md)', // radius-md = 16px ✓
'20px': 'var(--radius-lg)', // radius-lg = 20px ✓
'24px': 'var(--radius-xl)', // radius-xl = 24px ✓
'28px': 'var(--radius-2xl)', // radius-2xl = 28px ✓
'32px': 'var(--radius-3xl)', // radius-3xl = 32px ✓
'50%': 'var(--radius-full)',
'9999px':'var(--radius-full)',
};
5. Quyết định cần bạn
[!IMPORTANT] Chọn 1 trong 2 hướng:
Option A: Formula-Based (recommended)
- Radius =
calc(var(--spacing) * N)→ auto-scale với density - Con: giá trị có thể lẻ (9px, 15px) — nhưng trình duyệt render sub-pixel radius rất tốt
- Pro: đồng bộ hoàn toàn với spacing, chỉ thay
--spacinglà xong
Option B: Step-Down Mapping (conservative)
- Mỗi density profile chọn step khác trên primitive scale
- Mobile: comp→
xs(8), group→sm(12), section→md(16) - Web giữ nguyên: comp→
sm(12), group→md(16), section→xl(24) - Con: không auto-scale, phải update thủ công cho mỗi density preset mới
- Pro: giá trị luôn "đẹp" (8, 12, 16, 24...)