# Bottom Sheet — Component Research & Phương Án
Nghiên cứu: 2026-03-12
Sources: M3 Bottom Sheets, Apple HIG Sheets, shadcn/ui Sheet, UX best practices
Status: PROPOSED
Cross-ref: knowledge/research/industry-benchmarking-report.md, design-system/components md/navigation-bar.md
1. Tổng Quan
Bottom Sheet là container nội dung phụ gắn ở đáy màn hình, trượt lên để hiển thị thêm thông tin hoặc actions mà không rời khỏi context hiện tại. Đây là pattern thiết yếu cho mobile-first apps (Maps, Payment, E-commerce, xPOS).
Tại sao Prisma cần Bottom Sheet?
| Lý do |
Ví dụ trong xPOS |
| Detail view overlay |
Xem chi tiết sản phẩm trên danh sách |
| Action sheet |
Share, Export, Print options |
| Filter/Sort |
Lọc danh sách đơn hàng |
| Quick form |
Nhập số lượng, ghi chú nhanh |
| Contextual info |
Thông tin vận chuyển, trạng thái đơn |
2. Cross-Platform Research
2.1. Material Design 3 — Bottom Sheets
| Aspect |
Standard |
Modal |
| Tương tác nền |
✅ Có thể tương tác |
❌ Blocked (scrim) |
| Scrim |
Không |
Có (semi-transparent) |
| Dismiss |
Close button / navigate away |
Tap scrim / Swipe down / Menu item / Close |
| Use case |
Map + place detail, Music player |
Share menu, Filters, Action sheet |
| Coexistence |
Cùng tồn tại với main UI |
Overlay, disable main UI |
Specs:
| Property |
Value |
Token tương ứng Prisma |
| Corner radius |
28dp (top only) |
--radius-3xl (28px) |
| Top margin (min) |
72dp |
Tính toán: screen - 72dp |
| Max width |
640dp |
CSS max-width: 640px |
| Container color |
Surface Container Low |
--surface |
| Drag handle size |
32dp × 4dp |
Fixed |
| Drag handle hit target |
48dp |
Min touch target |
| Drag handle color |
On Surface Variant (opacity 0.4) |
--muted-foreground |
| Scrim color |
Scrim |
--overlay / rgba(0,0,0,0.32) |
| Elevation |
Level 1 (modal) |
--shadow-md |
| Typography (list items) |
Label Large |
--font-size-sm, --font-weight-medium |
| Typography (supporting) |
Body Medium |
--font-size-sm |
Behavior:
- Header cố định, content scroll bên trong
- Drag handle (tùy chọn) cho phép kéo resize giữa các detent heights
- Modal sheets ban đầu max 50% screen height → kéo lên full
- Standard sheets có thể snap vào nhiều vị trí (detents)
2.2. Apple HIG — Sheets (iOS)
| Aspect |
Spec |
| Tên gọi |
"Sheet" (card-style modal presentation) |
| Default style |
Card-style từ iOS 13+ — peek background content |
| Dismiss |
Swipe down anywhere / Close button |
| Detents |
.medium (~50%), .large (~100%), custom |
| Non-modal |
isModalInPresentation = false — interact with background |
| Scroll + dismiss |
Scroll content to top first → then dismiss on next swipe |
| Safe area |
Respects home indicator |
| Corner radius |
System default ~10pt (follows UIKit) |
| Grabber |
Optional chip indicator ở top center |
Key patterns:
- Progressive disclosure via medium detent: ban đầu hiện 50%, kéo lên để xem thêm
- Maps pattern: Persistent non-modal sheet với multiple detents (collapsed → half → full)
- Card-style: Rounded corners, slight shadow, background visible phía trên
2.3. shadcn/ui — Sheet Component
| Aspect |
Implementation |
| Base |
Radix UI Dialog primitive |
| Side prop |
"top" / "bottom" / "left" / "right" |
| Bottom sheet |
<SheetContent side="bottom"> |
| Overlay |
Built-in backdrop overlay |
| Close |
× button + Escape key + overlay click |
| Animation |
Slide-in/out CSS transitions |
| Composition |
SheetHeader, SheetTitle, SheetDescription, SheetFooter |
2.4. M3 Modal Bottom Sheet — Use Cases
M3 khuyến nghị dùng modal bottom sheet cho các tình huống sau:
| Use Case |
Mô tả |
Content Type |
Detent |
| Share / Action Sheet |
Grid share targets (Messages, WhatsApp, Email...) + action list (Copy, Save, Print) |
Icon grid + List items |
Medium |
| Filter & Sort |
Chips, sliders, date range. User chọn → Apply |
Chips + Range + Buttons |
Medium → Large |
| Menu with 3+ options |
Vertical list of actions khi có ≥ 3 items |
List items with icons |
Medium (content-fit) |
| Confirmation |
Xác nhận hành động nguy hiểm (xóa, hủy đơn, logout) |
Text + 2 buttons |
Small (content-fit) |
| Media Picker |
Chọn nguồn ảnh/file (Camera, Gallery, Files) |
Icon grid / List |
Medium |
| Payment Selection |
Chọn phương thức thanh toán (cards, wallets) |
List items + Radio |
Medium |
| Detail Preview |
Quick view sản phẩm/place — hero image + metadata + CTA |
Mixed content |
Large |
| Quick Form |
2-3 fields input nhanh (ghi chú, số lượng, feedback) |
Form fields |
Large |
Khi KHÔNG dùng modal bottom sheet:
- Content cần toàn bộ sự chú ý → dùng fullscreen hoặc new page
- Chỉ có 1-2 actions → dùng dialog hoặc menu
- Navigation between views → dùng navigation component
- Primary content → bottom sheet chỉ cho secondary content
2.5. Fullscreen Bottom Sheet
Fullscreen là trạng thái đặc biệt khi bottom sheet mở rộng chiếm toàn bộ màn hình:
| Aspect |
Spec |
| Khi nào |
Content cần full screen: search + keyboard, long form, media view |
| Transition |
Sheet expand từ detent large → fullscreen, hoặc mở trực tiếp fullscreen |
| Corner radius |
0 (không rounded — chiếm full screen) |
| Header |
Chuyển thành app bar style — close button (X hoặc ←) bên trái |
| Drag handle |
Ẩn — không cần drag khi fullscreen |
| Dismiss |
Close button, Back gesture, Escape key |
| Top margin |
0 (full height) |
| Status bar |
Vẫn hiển thị, header clear notch/status bar |
Fullscreen Use Cases:
| Case |
Lý do cần fullscreen |
| Search + Select |
Keyboard + scrollable result list cần full viewport |
| Long Form / Multi-step |
Nhiều fields, cần không gian nhập liệu thoải mái |
| Map / Media View |
Nội dung visual cần toàn màn hình |
| Terms / Policy |
Văn bản dài cần scroll nhiều |
| Photo Gallery |
Browse/select nhiều ảnh |
| Calendar Picker |
Full month view + time selection |
Transition pattern (M3 + iOS):
[Trigger tap] → Sheet slides up to detent:medium (50%)
→ User drags up → detent:large (90%)
→ User continues drag → detent:full (100%, radius→0)
→ OR: Direct open as fullscreen
2.6. UX Best Practices (Industry Consensus)
| Rule |
Guideline |
| Content |
Supplementary, secondary — KHÔNG phải primary content |
| Initial height |
Modal: ≤ 50% screen. Standard: context-dependent |
| Max width |
640px (prevent stretching on tablet/desktop) |
| Dismiss |
Multiple ways: swipe, tap scrim, close button, escape |
| Actions |
≥ 3 actions → bottom sheet. 1-2 → menu/dialog |
| Stacking |
KHÔNG stack multiple sheets lên nhau |
| Animation |
Smooth slide-up, 200-300ms |
| Touch target |
Min 44×44px (WCAG) / 48×48 (MD3) |
| Cognitive load |
Keep content concise, prioritize important info |
| Context |
Scrim/dimmed overlay preserves sense of place |
3. So Sánh Cross-Platform
| Feature |
M3 |
Apple HIG |
shadcn |
Prisma Decision |
| Standard (non-modal) |
✅ |
✅ (isModal=false) |
❌ (modal only) |
✅ Cần cả hai |
| Modal |
✅ |
✅ |
✅ |
✅ |
| Detents (multi-height) |
✅ (drag handle) |
✅ (medium/large/custom) |
❌ |
✅ Priority feature |
| Scrim |
Modal only |
Modal only |
Always |
Modal only |
| Drag handle |
Optional |
Optional (grabber) |
❌ |
Optional |
| Corner radius |
28dp |
~10pt (system) |
theme-based |
28px (--radius-3xl) |
| Max width |
640dp |
Full device width |
Responsive |
640px on tablet+ |
| Swipe dismiss |
✅ |
✅ |
❌ (click only) |
✅ |
| Scroll inside |
✅ |
✅ |
✅ |
✅ |
| Keyboard dismiss (Esc) |
✅ (modal) |
N/A (native) |
✅ |
✅ (modal) |
4. Phương Án Prisma — Bottom Sheet Component
4.1. Variants
| Variant |
Mô tả |
Use case |
modal |
Overlay + scrim, blocks background |
Action sheets, share menus, confirmations |
standard |
Coexists with background, no scrim |
Map details, persistent info panels |
persistent |
Always visible, snaps to detents |
Music player, delivery tracker |
fullscreen |
Full screen, no radius, app bar header |
Search, long form, media view |
4.2. Anatomy (Sub-components / Slots)
┌─────────────────────────────────┐
│ Drag Handle │ ← Optional, 32×4px chip
│─────────────────────────────────│
│ [Icon] Header Title [Close] │ ← Header slot
│ Subtitle │
│─────────────────────────────────│ ← Optional divider
│ │
│ Scrollable Content │ ← Content slot
│ (list / form / custom) │
│ │
│─────────────────────────────────│ ← Optional divider
│ [Secondary] [Primary] │ ← Footer/Actions slot
│─────────────────────────────────│
│ safe area padding │
└─────────────────────────────────┘
| Slot |
Element |
Tokens |
| Container |
.surface-sheet |
--surface bg, --radius-3xl top corners, --shadow-md |
| Drag Handle |
.surface-sheet__handle |
--muted-foreground (40% opacity), 32×4px, --radius-full |
| Header |
.surface-sheet__header |
--foreground title, --muted-foreground subtitle |
| Content |
.surface-sheet__content |
Scrollable, overflow-y: auto |
| Footer |
.surface-sheet__footer |
--border top border, sticky bottom |
| Scrim |
.surface-sheet__scrim |
--overlay / rgba(0,0,0,0.32) |
| Close |
.surface-sheet__close |
Icon button, top-right |
4.3. Height Standards (Min/Max)
Cross-platform height specs:
| Metric |
M3 |
Apple HIG |
Industry |
Prisma |
| Min height (standard) |
56dp |
— |
— |
7% |
| Min height (modal) |
Content-fit |
Content-fit |
≥128dp (~2 items) |
15% |
| Initial open (modal) |
50% |
50% (.medium) |
50% |
50% |
| Expanded |
screen − 72dp (~92%) |
~100% (.large) |
90% |
92% |
| Fullscreen |
100% |
100% |
100% |
100% |
| Max width |
640dp |
device width |
640px |
640px |
4.4. Detents (Height Stops)
| Detent |
% Screen |
CSS Value |
Use case |
peek |
25% |
max-height: 25% |
Peek — summary, 1-2 items |
medium |
50% |
max-height: 50% |
Default open — M3 & iOS standard |
large |
92% |
max-height: calc(100% - 8%) |
Expanded — M3 top margin 72dp |
full |
100% |
max-height: 100% |
Fullscreen — radius:0, no handle |
Logic: Drag handle interaction cycles through detents. Swipe velocity determines snap target.
Không dùng fixed px cho height — luôn dùng % of container/viewport để responsive trên mọi device size.
4.5. CSS Naming Convention (Elevation-based)
Theo css-naming-convention.md:
- Bottom Sheet = Surface layer (overlays content)
- Modal variant adds Scrim (overlay layer)
.surface-sheet ← Container (Surface layer)
.surface-sheet--modal ← Modal variant
.surface-sheet--standard ← Standard variant
.surface-sheet--persistent ← Persistent variant
.surface-sheet--fullscreen ← Fullscreen variant (radius:0, 100%)
.surface-sheet__handle ← Drag handle
.surface-sheet__header ← Header area
.surface-sheet__content ← Scrollable body (SLOT injection point)
.surface-sheet__footer ← Actions area
.surface-sheet__scrim ← Background overlay (modal only)
.surface-sheet__close ← Close button
Slot Architecture: .surface-sheet__content là injection point. JS switches content bằng slot name → inject HTML tương ứng. Mỗi slot define: title, subtitle, content (HTML), showFooter, footerButtons.
4.6. Token Mapping (Draft)
| Property |
Token |
Value Reference |
| Container bg |
--surface |
Surface Container Low |
| Container fg |
--foreground |
On Surface |
| Corner radius (top) |
--radius-3xl |
28px (M3 spec) |
| Elevation |
--shadow-md |
Level 1 elevation |
| Handle bg |
--muted-foreground |
On Surface Variant @ 40% |
| Handle width |
32px |
M3 spec |
| Handle height |
4px |
M3 spec |
| Handle radius |
--radius-full |
chip shape |
| Handle hit target |
48px |
Min touch target |
| Scrim bg |
--overlay |
Scrim token |
| Header padding |
--spacing-4 --spacing-5 |
16px 20px |
| Content padding |
--spacing-4 --spacing-5 |
16px 20px |
| Footer padding |
--spacing-3 --spacing-4 |
12px 16px |
| Footer border |
--border |
Divider |
| Close icon size |
--icon-md |
20px |
| Title font |
--font-size-lg / --font-weight-semibold |
Title Large |
| Subtitle font |
--font-size-sm / --font-weight-regular |
Body Medium |
| Max width |
640px |
M3 responsive |
| Top margin (min) |
8% (~72px) |
M3 spec — hiển thị status bar |
| Safe area bottom |
env(safe-area-inset-bottom) |
iOS home indicator |
| Transition |
--duration-medium-1 |
~300ms |
| Transition easing |
--easing-standard-decelerate |
Open animation |
| z-index |
--z-index-modal |
Above content |
4.7. Props & API (Draft)
interface BottomSheetProps {
/** Sheet type */
variant?: 'modal' | 'standard' | 'persistent';
/** Controlled open state */
open: boolean;
/** Close callback */
onClose: () => void;
/** Available detent heights */
detents?: ('collapsed' | 'medium' | 'large' | 'full')[];
/** Initial detent */
initialDetent?: 'collapsed' | 'medium' | 'large';
/** Show drag handle */
showHandle?: boolean;
/** Close on scrim tap (modal only) */
closeOnOverlayClick?: boolean;
/** Close on Escape key */
closeOnEscape?: boolean;
/** Swipe to dismiss */
swipeToDismiss?: boolean;
/** Lock backdrop scroll (modal only) */
lockBackgroundScroll?: boolean;
/** Custom max width */
maxWidth?: string;
/** Children content */
children: ReactNode;
}
4.8. Accessibility
| Requirement |
Implementation |
| Role |
role="dialog" (modal) / role="complementary" (standard) |
| ARIA |
aria-modal="true" (modal), aria-label="Sheet title" |
| Focus trap |
Modal: focus trapped inside. Standard: no trap |
| Initial focus |
First focusable element or container |
| Escape |
Close modal sheet |
| Drag handle |
role="slider", aria-label="Resize sheet", focusable in tab order |
| Screen reader |
Announce "Sheet opened" / "Sheet closed" |
| Touch target |
Drag handle: 48px hit area. Close: 44×44px min |
| Reduced motion |
@media (prefers-reduced-motion) → instant show/hide |
4.9. Interaction Patterns
| Action |
Behavior |
| Open |
Slide up from bottom with decelerate easing |
| Close (modal) |
Tap scrim, swipe down, close button, Escape, menu item tap |
| Close (standard) |
Close button, programmatic |
| Resize |
Drag handle or content edge → snap to nearest detent |
| Scroll vs Swipe |
Content at top → swipe dismisses. Content scrolled → scroll first, then dismiss |
| Velocity swipe |
Fast downward swipe → dismiss regardless of position |
| Background scroll |
Modal: locked. Standard: allowed |
5. Responsive Layout
| Breakpoint |
Layout |
| Compact (< 600px) |
Full width, bottom-anchored |
| Medium (600-839px) |
Centered, max-width 640px, 56dp side margins |
| Expanded (840px+) |
Consider replacing with Side Sheet or Dialog |
Decision: Trên desktop (≥840px), bottom sheet nên tự động chuyển thành Dialog hoặc Side Panel (responsive pattern). Tương tự Google Maps desktop vs mobile.
6. Phương Án Thực Thi (Implementation Strategy)
Phase 1: Core Modal Bottom Sheet
Phase 2: Detents & Drag
Phase 3: Standard & Persistent Variants
Phase 4: Responsive
7. Priority & Effort
| Priority |
Item |
Effort |
| 🔴 P1 |
Modal bottom sheet (core) |
M |
| 🔴 P1 |
Token mapping + CSS |
S |
| 🟡 P2 |
Detents + drag handle |
L |
| 🟡 P2 |
Swipe to dismiss |
M |
| 🟢 P3 |
Standard variant |
M |
| 🟢 P3 |
Persistent variant |
M |
| 🟢 P3 |
Responsive auto-switch |
L |
8. References