Prisma

# 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):

text
[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)

text
┌─────────────────────────────────┐
│         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)
text
.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)

typescript
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

  • Token JSON cho bottom-sheet component
  • CSS skeleton (.surface-sheet, .surface-sheet--modal)
  • Scrim overlay
  • Open/close animation (slide up/down)
  • Close on scrim tap + Escape
  • Accessibility (role, aria, focus trap)

Phase 2: Detents & Drag

  • Drag handle component
  • Multi-detent system (collapsed, medium, large)
  • Swipe to dismiss
  • Scroll-aware dismiss (content at top → swipe dismiss)
  • Snap physics (velocity-based)

Phase 3: Standard & Persistent Variants

  • Standard variant (no scrim, coexists)
  • Persistent variant (always visible, detent-based)
  • Map-style pattern example

Phase 4: Responsive

  • Max-width 640px on medium/expanded
  • Desktop: auto-switch to dialog/side-panel
  • Tablet: centered positioning

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