Prisma
Live Preview

Modal Component Generation Skill

Skill này hướng dẫn bạn (AI Agent) tạo component Modal — overlay popup hiển thị nội dung phức tạp (forms, detail views, editors).

1. Mục tiêu (Objective)

Tạo component Modal hoàn chỉnh với focus trap, backdrop, animation, keyboard navigation, và nhiều variants. Tích hợp 100% Design Tokens.

2. AI Context & Intent (Ngữ cảnh cho AI)

Khi nào dùng Modal?

  • Form phức tạp: Tạo nội dung mới, chỉnh sửa nhiều fields
  • Chi tiết item: Xem chi tiết mà không rời trang
  • Content editor: Rich text editor, image cropper
  • Onboarding step: Welcome flow, tutorial
  • Preview: Xem trước file, ảnh, document

Đặc điểm cốt lõi

  • Bản chất: Content display — hiển thị rich information
  • Content: Forms, tables, detail views, editors
  • Scroll: ✅ Body scrollable
  • Size: 384-1024px (web center overlay), 100% (app bottom sheet)
  • Backdrop: ✅ Click dismiss

Decision Tree cho AI

text
Cần hiển thị rich content overlay?
├─ Form (2+ fields) → Modal (default)
├─ Nội dung chi tiết / preview → Modal (default)
├─ Content editor / complex UI → Modal (lg/xl)
├─ Fullscreen experience (mobile) → Modal (fullscreen)
└─ Navigation / filters / long form → Drawer (sheet from edge)

3. Ngữ nghĩa & Phân loại (Semantics)

3.1. Variants

Variant Mô tả Khi nào dùng
default Center overlay, content tự do Form, detail view, editor
fullscreen Toàn màn hình (mobile) Complex form, preview
drawer Slide từ cạnh (right/bottom) Navigation, filters

3.2. Sizes (variant = default)

Size Max width Use case
sm 400px Simple confirm, notification
md 500px Standard form
lg 640px Detail view
xl 800px Complex content
fullscreen 100vw × 100vh Mobile, preview

3.3. Sub-components

Part Mô tả
Modal.Header Title + close button (optional description)
Modal.Body Scrollable content area
Modal.Footer Action buttons (right-aligned)

3.4. Slot Map (Figma ↔ Code)

📎 Source: slot-manifest.jsonmodal · Layer: surface

Figma Slot data-slot CSS Class Required Accepts
Root modal .surface-modal
Header modal-header .slot-header title-group
Title modal-title text
Description modal-description text
Close modal-close close-button
Body modal-body .slot-body · .slot-body-flush input-group, list, table, form, rich-editor, *
Footer modal-footer .slot-footer button-group
html
<!-- HTML skeleton with data-slot -->
<div class="surface-modal" data-slot="modal" role="dialog" aria-modal="true">
  <div class="slot-header" data-slot="modal-header">
    <h2 data-slot="modal-title">Title</h2>
    <button data-slot="modal-close" aria-label="Close">✕</button>
  </div>
  <div class="slot-body" data-slot="modal-body">
    <!-- scrollable content -->
  </div>
  <div class="slot-footer" data-slot="modal-footer">
    <button class="btn btn--outline">Cancel</button>
    <button class="btn btn--primary">Save</button>
  </div>
</div>

4. Token Mapping

📦 Token values: Xem ATOMIC-MAPPING.md — single source of truth cho tất cả actual token values.

Token Type Value Mô tả
max-width.sm number {max-width.sm} Small modal — confirmations, simple alerts (384px)
max-width.md number {max-width.lg} Medium modal — forms, detail views (512px)
max-width.lg number {max-width.3xl} Large modal — complex content, tables (768px)
max-width.xl number {max-width.5xl} Extra large modal — full editors, dashboards (1024px)
max-width.full string 100% Full-width modal — mobile fullscreen, immersive flows
padding number {module.padding-large} Internal padding of modal content area
padding-header number {module.padding-default} Padding for modal header section
padding-footer number {module.padding-default} Padding for modal footer/action bar
radius number {module.radius-large} Corner radius of modal container
backdrop-blur number {blur.lg} Backdrop blur intensity behind modal
color.backdrop color {base.background-overlay} Semi-transparent backdrop
color.surface color {base.surface} Modal background
color.surface-foreground color {base.surface-foreground} Modal text color
color.border color {base.card-border} Modal border (optional)
color.footer-border color {base.border} Footer top separator
color.close-button color {base.muted-foreground} Close X icon
color.close-hover color {state-layer.hover} Close button hover
color.title color {base.foreground} Title text
color.description color {base.muted-foreground} Description text
color.focus-ring color {focus.ring-color} Focus ring color — ref shared/focus
color.disabled-bg color {base.muted} Disabled state background
color.disabled-fg color {base.muted-foreground} Disabled state foreground
elevation shadow {elevation.level-4} Shadow elevation for modal — level 4 (high)
z-index number {z-index.modal} Stacking order for modal layer
header.font-size number {font.size.lg} Modal title font size — heading-4 (18px)
header.font-weight number {font.weight.semibold} Modal title font weight
close-button-size number {min-width.8} Size of the close button in modal header
gap number {spacing.4} Vertical gap between modal content sections
transition-enter string {duration.slow-1} {easing.emphasized-decelerate} Modal entrance animation timing
transition-exit string {duration.normal-1} {easing.emphasized-accelerate} Modal exit animation timing

5. Props & API

typescript
interface ModalProps {
  /** Mở/đóng */
  open: boolean;
  /** Callback khi close (Escape, backdrop click, X button) */
  onClose: () => void;
  /** Variant */
  variant?: 'default' | 'fullscreen' | 'drawer';
  /** Drawer position */
  drawerPosition?: 'right' | 'bottom' | 'left';
  /** Size (variant=default) */
  size?: 'sm' | 'md' | 'lg' | 'xl';
  /** Có thể close khi click backdrop */
  closeOnBackdrop?: boolean; // default: true (false cho alert)
  /** Có thể close khi nhấn Escape */
  closeOnEscape?: boolean; // default: true
  /** Hiện close button (X) trong header */
  showCloseButton?: boolean; // default: true
  children: ReactNode;
}

interface ModalHeaderProps {
  /** Title */
  title: string;
  /** Description */
  description?: string;
}

interface ModalFooterProps {
  children: ReactNode; // Buttons
}

6. Accessibility (a11y)

  • Role: role="dialog" + aria-modal="true".
  • Label: aria-labelledby trỏ đến title, aria-describedby trỏ đến description.
  • Focus trap: Focus PHẢI bị trap bên trong modal — Tab cycle qua focusable elements.
  • Initial focus: Focus vào element đầu tiên (hoặc primary action button).
  • Return focus: Khi close, trả focus về trigger element ban đầu.
  • Escape: Nhấn Escape close modal.
  • Scroll lock: Body scroll bị khoá khi modal mở.
  • Tab order: Focusable elements trong modal giữ đúng tab order.

7. Best Practices & Rules

  • Không lạm dụng: Modal interrupts workflow — chỉ dùng khi thật sự cần user attention.
  • Portal: Render modal qua Portal vào <body> — tránh z-index/overflow issues.
  • Animation: Fade in backdrop + scale up modal (200ms ease). Reverse khi close.
  • Nested modals: TRÁNH nest modal trong modal — redesign UX nếu cần.
  • Mobile: Trên mobile, dùng fullscreen hoặc drawer (bottom sheet) thay vì center modal nhỏ.
  • Không Hardcode: Mọi giá trị từ Token, đặc biệt backdrop overlay + surface colors.
  • Icon: Close button (x) CHỈ dùng Lucide icon từ assets/icons/. CẤM dùng text emoji. Xem icon.md mục 10.
  • Button size (BS-1, BS-3): Modal.Footer buttons PHẢI cùng size (BS-3). Standard form → md. Mobile fullscreen → btn-lg.btn--block (BS-4). KHÔNG dùng btn-lg cho mọi modal. Xem button.md §4.7.

8. Example Usage

jsx
{/* Form modal */}
<Modal open={isOpen} onClose={close} size="md">
  <Modal.Header title="Thêm thẻ mới" />
  <Modal.Body>
    <Input label="Số thẻ" placeholder="0000 0000 0000 0000" />
    <Input label="Tên chủ thẻ" />
  </Modal.Body>
  <Modal.Footer>
    <Button variant="outline" onClick={close}>Huỷ</Button>
    <Button variant="primary" onClick={handleSave}>Lưu</Button>
  </Modal.Footer>
</Modal>

{/* Bottom sheet (mobile) */}
<Modal open={isOpen} onClose={close} variant="drawer" drawerPosition="bottom">
  <Modal.Header title="Chọn phương thức" />
  <Modal.Body>{/* options */}</Modal.Body>
</Modal>