modal-and-overlay-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Modal and Overlay Patterns

Modal与覆盖层设计模式

Overlays appear above the main content layer. They range from lightweight popovers (non-blocking, anchored to a trigger) to full blocking modals (require a user response before the app continues). Choosing the right overlay type for the task prevents unnecessary interruption and keeps the user oriented.

覆盖层出现在主内容层上方,从轻量级的popover(非阻塞、锚定到触发元素)到完全阻塞的modal(需用户响应后应用才能继续)。为任务选择合适的覆盖层类型可避免不必要的干扰,让用户保持方向感。

The Overlay Hierarchy

覆盖层层级

Choose the lightest type that satisfies the task. Heavier overlays carry higher cognitive cost.
TypeBlocks backgroundAnchored to triggerTypical contentDismiss with
TooltipNoYes1–2 lines of explanatory textCursor leave / focus out
PopoverNoYesShort interactive content: a form field, a picker, a small listClick outside, Escape, explicit close
Dropdown / MenuNoYesList of actions or optionsClick outside, Escape, selection
Bottom sheet (mobile)Partial (dimmed)NoActions or content on small screensSwipe down, tap scrim, Escape
Drawer / Side panelPartial (dimmed)NoSecondary editing, detail views, long formsEscape, explicit close; optionally click scrim
Dialog / ModalYes (full scrim)NoBlocking task: confirm action, fill required formEscape (non-destructive only), explicit button
Full-screen overlayYes (complete)NoImmersive task: media viewer, complex configurationExplicit close only
Decision rule: If the user can continue using the rest of the app while the overlay is open, use a non-blocking type (drawer, popover). If the app must wait for the user's response, use a modal.

选择能满足任务需求的最轻量类型。更重量级的覆盖层会带来更高的认知成本。
类型是否阻塞背景是否锚定到触发元素典型内容关闭方式
Tooltip1-2行说明文本光标离开 / 失去焦点
Popover简短交互内容:表单字段、选择器、小型列表点击外部、Escape键、显式关闭按钮
Dropdown / Menu操作或选项列表点击外部、Escape键、选择选项
Bottom sheet(移动端)部分阻塞(变暗)小屏幕上的操作或内容向下滑动、点击遮罩、Escape键
Drawer / Side panel部分阻塞(变暗)次要编辑任务、详情视图、长表单Escape键、显式关闭按钮;可选点击遮罩
Dialog / Modal是(完全遮罩)阻塞性任务:确认操作、填写必填表单Escape键(仅非破坏性操作)、显式按钮
Full-screen overlay是(完全阻塞)沉浸式任务:媒体查看器、复杂配置仅支持显式关闭
决策规则: 如果用户在覆盖层打开时仍可继续使用应用的其他部分,使用非阻塞类型(drawer、popover)。如果应用必须等待用户响应,使用modal。

Tooltip

Tooltip

A tooltip appears on hover or keyboard focus and disappears when the trigger loses focus. It is purely informational — no interactive elements inside.
  • Content: one short sentence, label, or keyboard shortcut. Never put a link or button inside a tooltip.
  • Delay: 300–400ms on hover; no delay on keyboard focus.
  • Position: prefer above the trigger; auto-flip when viewport edge is near.
  • ARIA:
    role="tooltip"
    on the element;
    aria-describedby
    on the trigger pointing to the tooltip id.

Tooltip在悬停或键盘聚焦时显示,触发元素失去焦点时消失。它仅用于展示信息——内部无交互元素。
  • 内容:一句短句、标签或键盘快捷键。切勿在Tooltip内放置链接或按钮。
  • 延迟:悬停时300-400ms;键盘聚焦时无延迟。
  • 位置:优先显示在触发元素上方;当靠近视口边缘时自动翻转位置。
  • ARIA:元素设置
    role="tooltip"
    ;触发元素设置
    aria-describedby
    指向Tooltip的ID。

Popover

Popover

A popover is anchored to a trigger but contains interactive content — a colour picker, a date range selector, a small form, a list of filters. Unlike a tooltip, it stays open while the user interacts.
  • Max width: 280–360px. For larger content, use a drawer.
  • Position: anchored to the trigger; auto-flip to stay in viewport.
  • Dismiss: click outside, Escape key, or explicit close button when the content is long.
  • Focus: move focus into the popover when it opens; return focus to the trigger on close.
  • ARIA:
    role="dialog"
    (if interactive) or
    role="listbox"
    (if a list);
    aria-haspopup
    on the trigger.

Popover锚定到触发元素,但包含交互内容——颜色选择器、日期范围选择器、小型表单、筛选器列表。与Tooltip不同,它在用户交互时保持打开状态。
  • 最大宽度:280-360px。内容较大时,使用drawer。
  • 位置:锚定到触发元素;自动翻转位置以保持在视口内。
  • 关闭:点击外部、Escape键;内容较长时可添加显式关闭按钮。
  • 焦点:打开时将焦点移入Popover;关闭时将焦点返回触发元素。
  • ARIA:(若为交互型)设置
    role="dialog"
    ,(若为列表)设置
    role="listbox"
    ;触发元素设置
    aria-haspopup

Dropdown and Menu

Dropdown and Menu

A dropdown lists selectable options or actions anchored to a trigger button. It is the lightest interactive overlay.
  • Separate select dropdowns (the user picks one value that persists) from action menus (the user triggers an action that doesn't persist as a value).
  • Width: at least as wide as the trigger; cap at 280px.
  • Long lists: add a search input at the top when there are more than 8–10 items.
  • Keyboard:
    /
    to move between items,
    Enter
    to select,
    Escape
    to close.

Dropdown列出可选择的选项或操作,锚定到触发按钮。它是最轻量的交互型覆盖层。
  • 区分选择型dropdown(用户选择一个持久值)与操作菜单(用户触发不持久的操作)。
  • 宽度:至少与触发元素等宽;最大280px。
  • 长列表:当选项超过8-10个时,在顶部添加搜索输入框。
  • 键盘操作:
    /
    切换选项,
    Enter
    选择,
    Escape
    关闭。

Bottom Sheet (Mobile)

Bottom Sheet(移动端)

On small screens, a bottom sheet replaces modals and popovers. It slides up from the bottom edge and feels native to touch devices.
  • Peek height: Show a small portion of the sheet first (a handle + title), let the user drag to expand.
  • Full-height: For longer content or forms that need the full viewport.
  • Dismiss: swipe down, tap the scrim, or press Escape.
  • Do not centre dialogs on mobile — use a bottom sheet instead (centred modals are too small and hard to reach).
  • ARIA: treat as
    role="dialog"
    with the same focus management as a modal.

在小屏幕上,bottom sheet替代modal和popover。它从底部边缘滑入,在触控设备上更具原生感。
  • 预览高度: 先显示sheet的一小部分(手柄+标题),让用户拖动展开。
  • 全屏高度: 用于较长内容或需要整个视口的表单。
  • 关闭:向下滑动、点击遮罩、按Escape键。
  • 移动端请勿使用居中对话框——改用bottom sheet(居中modal太小且难以触碰)。
  • ARIA:视为
    role="dialog"
    ,采用与modal相同的焦点管理方式。

Drawer / Side Panel

Drawer / Side Panel

A drawer slides in from the left or right and partially covers the main content. Use it for secondary editing tasks, detail views, or settings that the user might refer back to while using the main content.
  • Right drawer: Detail view, editing form, filter/sort panel. Most common.
  • Left drawer: Navigation on mobile (hamburger menu pattern).
  • Width: 320–480px on desktop. Full-width on mobile (effectively a bottom sheet or full-screen overlay instead).
  • Scrim: a semi-transparent backdrop (
    rgba(0,0,0,0.4)
    ) behind the drawer dims the main content.
  • Dismiss: Escape key, explicit close button. Clicking the scrim is optional — avoid it when the drawer contains an unsaved form.
  • Do not use a drawer when the task is blocking (e.g. a required decision). Use a modal instead.
  • ARIA:
    role="dialog"
    ,
    aria-modal="true"
    ,
    aria-labelledby
    pointing to the drawer title.

Drawer从左侧或右侧滑入,部分覆盖主内容。适用于次要编辑任务、详情视图或用户在使用主内容时可能需要参考的设置。
  • 右侧drawer: 详情视图、编辑表单、筛选/排序面板。最常用。
  • 左侧drawer: 移动端导航(汉堡菜单模式)。
  • 宽度:桌面端320-480px;移动端全屏(实际为bottom sheet或全屏覆盖层)。
  • 遮罩:drawer后方的半透明背景(
    rgba(0,0,0,0.4)
    )使主内容变暗。
  • 关闭:Escape键、显式关闭按钮。点击遮罩为可选操作——当drawer包含未保存表单时避免使用此方式。
  • 任务为阻塞型(如必填决策)时,请勿使用drawer,改用modal。
  • ARIA:设置
    role="dialog"
    aria-modal="true"
    aria-labelledby
    指向drawer标题。

Modal / Dialog

Modal / Dialog

A modal blocks the entire UI with a full scrim. Use it only when the app genuinely cannot continue without the user's response.
Modal通过完全遮罩阻塞整个UI。仅当应用确实需要用户响应才能继续时使用。

When to use a modal

使用场景

  • Confirming a destructive or irreversible action
  • A required form that must be submitted before continuing
  • An error or warning that requires the user's acknowledgement
  • 确认破坏性或不可逆操作
  • 必须提交才能继续的必填表单
  • 需要用户确认的错误或警告

When not to use a modal

非使用场景

  • Displaying information the user can read at their leisure → use an inline notice or notification
  • A task the user might want to do alongside the main content → use a drawer
  • A large form with many fields → use a dedicated page or a drawer
  • 展示用户可随时查看的信息 → 使用内联通知或公告
  • 用户可能希望与主内容同时进行的任务 → 使用drawer
  • 包含多个字段的长表单 → 使用专属页面或drawer

Anatomy

结构

┌──────────────────────────────────┐
│  Title                      [✕]  │  ← Header: title + close button
├──────────────────────────────────┤
│                                  │
│  Body content                    │  ← Content: scrolls if needed
│  (description, form, media)      │
│                                  │
├──────────────────────────────────┤
│               [Cancel]  [Confirm]│  ← Footer: actions, right-aligned
└──────────────────────────────────┘
┌──────────────────────────────────┐
│  Title                      [✕]  │  ← 头部:标题 + 关闭按钮
├──────────────────────────────────┤
│                                  │
│  Body content                    │  ← 内容:需要时可滚动
│  (description, form, media)      │
│                                  │
├──────────────────────────────────┤
│               [Cancel]  [Confirm]│  ← 底部:操作按钮,靠右对齐
└──────────────────────────────────┘

Sizing

尺寸

SizeWidthUse for
Small360pxShort confirmations, single-field prompts
Medium480pxStandard dialogs, short forms
Large640pxMulti-field forms, richer content
Full-screen100% viewportImmersive tasks; use sparingly
Content that overflows the modal height should scroll within the body area only — the header and footer must remain visible.
尺寸宽度适用场景
小型360px简短确认、单字段提示
中型480px标准对话框、短表单
大型640px多字段表单、丰富内容
全屏100%视口沉浸式任务;谨慎使用
超出modal高度的内容应仅在主体区域内滚动——头部和底部必须保持可见。

Dismiss behaviour

关闭行为

TriggerAllowed for non-destructive?Allowed for destructive?
Escape
key
YesNo — require explicit Cancel
Click outside scrimYes (optional)No — too easy to dismiss accidentally
Close button (✕)YesYes
Cancel buttonYesYes
For destructive or irreversible actions: disable Escape and click-outside dismissal. The user must explicitly press Cancel or Confirm.
触发方式非破坏性操作是否允许破坏性操作是否允许
Escape
否 — 需显式点击Cancel
点击遮罩外部是(可选)否 — 易误触关闭
关闭按钮(✕)
Cancel按钮
对于破坏性或不可逆操作:禁用Escape键和点击外部关闭功能。用户必须显式点击Cancel或Confirm。

Stacking modals

嵌套modal

Avoid opening a modal from inside a modal. It signals an information architecture problem — the task is likely too complex for a single dialog.
If a secondary overlay is unavoidable, use a popover anchored inside the modal rather than another full modal. Never stack two blocking scrim overlays.

避免从modal内打开另一个modal。这表明信息架构存在问题——任务可能过于复杂,不适合单个对话框。
若必须使用二级覆盖层,使用锚定在modal内的popover而非另一个全屏modal。切勿堆叠两个阻塞型遮罩覆盖层。

Focus Management

焦点管理

Every overlay must manage focus correctly. Broken focus management is one of the most common accessibility failures.
每个覆盖层都必须正确管理焦点。焦点管理失效是最常见的无障碍问题之一。

On open

打开时

  1. Move focus to the first interactive element inside the overlay (usually the first field, or the confirm button for confirmations).
  2. Trap focus:
    Tab
    and
    Shift+Tab
    cycle within the overlay only; focus cannot escape to the content behind the scrim.
  1. 将焦点移至覆盖层内的第一个交互元素(通常是第一个字段,或确认对话框中的确认按钮)。
  2. 锁定焦点:
    Tab
    Shift+Tab
    仅在覆盖层内循环;焦点无法移至遮罩后的内容。

On close

关闭时

Return focus to the element that triggered the overlay. If the trigger no longer exists (the element was deleted), move focus to a logical nearby element.
将焦点返回触发覆盖层的元素。若触发元素已不存在(如被删除),将焦点移至附近的合理元素。

Implementation note

实现注意事项

Use a focus trap library or the native
<dialog>
element, which handles trapping natively in modern browsers. Rolling a manual focus trap is error-prone.

使用焦点锁定库或原生
<dialog>
元素,现代浏览器原生支持焦点锁定。手动实现焦点锁定容易出错。

ARIA for Modals and Drawers

Modal与Drawer的ARIA规范

html
<div
  role="dialog"
  aria-modal="true"
  aria-labelledby="modal-title"
  aria-describedby="modal-description"
>
  <h2 id="modal-title">Delete project?</h2>
  <p id="modal-description">
    This will permanently delete "Apollo" and all its data. This cannot be undone.
  </p>
  <button>Cancel</button>
  <button>Delete</button>
</div>
  • role="dialog"
    on the container
  • aria-modal="true"
    tells screen readers to ignore content behind the overlay
  • aria-labelledby
    points to the dialog title's id
  • aria-describedby
    points to the description's id (optional but helpful for confirmations)
  • The scrim backdrop should have
    aria-hidden="true"
    — screen readers must not read it

html
<div
  role="dialog"
  aria-modal="true"
  aria-labelledby="modal-title"
  aria-describedby="modal-description"
>
  <h2 id="modal-title">Delete project?</h2>
  <p id="modal-description">
    This will permanently delete "Apollo" and all its data. This cannot be undone.
  </p>
  <button>Cancel</button>
  <button>Delete</button>
</div>
  • 容器设置
    role="dialog"
  • aria-modal="true"
    告知屏幕阅读器忽略覆盖层后方的内容
  • aria-labelledby
    指向对话框标题的ID
  • aria-describedby
    指向描述文本的ID(可选,但对确认对话框有帮助)
  • 遮罩背景应设置
    aria-hidden="true"
    ——屏幕阅读器不得读取它

Destructive Confirmation Pattern

破坏性操作确认模式

Confirmation dialogs for destructive actions must name the item and consequence. Generic "Are you sure?" dialogs don't give enough context.
Do:
Delete "Apollo Project"? This will permanently remove all tasks, files, and history. This cannot be undone. [Cancel] [Delete project]
Don't:
Are you sure you want to do this? [No] [Yes]
  • The primary destructive action button uses
    --color-error
    /
    --color-danger
    , not
    --color-primary
    .
  • Label the destructive button explicitly: "Delete project", "Remove member", "Cancel order" — not just "OK" or "Confirm".
  • Cancel is always on the left (or secondary position); destructive action on the right.

破坏性操作的确认对话框必须明确操作对象和后果。通用的“确定要执行此操作吗?”对话框提供的上下文不足。
正确示例:
删除「Apollo项目」? 此操作将永久删除所有任务、文件和历史记录,且无法撤销。 [取消] [删除项目]
错误示例:
确定要执行此操作吗? [否] [是]
  • 主要破坏性操作按钮使用
    --color-error
    /
    --color-danger
    ,而非
    --color-primary
  • 破坏性按钮需明确标注:“删除项目”、“移除成员”、“取消订单”——而非仅“确定”或“确认”。
  • Cancel始终在左侧(次要位置);破坏性操作在右侧。

Review Checklist

审核清单

  • Is the correct overlay type chosen for the task (tooltip / popover / menu / bottom sheet / drawer / modal)?
  • Is a modal avoided when a drawer or inline pattern would suffice?
  • Does the modal/drawer have a visible title, body, and clear action buttons?
  • Does overflow content scroll within the body — with the header and footer fixed?
  • Is Escape disabled for destructive actions (click-outside too)?
  • Does focus move into the overlay on open, and return to the trigger on close?
  • Is focus trapped within the overlay while it's open?
  • Is
    role="dialog"
    ,
    aria-modal="true"
    ,
    aria-labelledby
    set correctly?
  • Does the destructive confirm dialog name the item and describe the consequence?
  • Is the destructive button labelled explicitly (not "OK" or "Confirm")?
  • Are stacked modals avoided?
  • On mobile, are modals replaced with bottom sheets?
  • 是否为任务选择了正确的覆盖层类型(tooltip / popover / menu / bottom sheet / drawer / modal)?
  • 是否在drawer或内联模式可行时避免使用modal?
  • modal/drawer是否有可见标题、主体内容和清晰的操作按钮?
  • 溢出内容是否仅在主体区域滚动——头部和底部固定?
  • 破坏性操作是否禁用了Escape键(及点击外部关闭)?
  • 打开时焦点是否移入覆盖层,关闭时是否返回触发元素?
  • 覆盖层打开时焦点是否被锁定在内部?
  • role="dialog"
    aria-modal="true"
    aria-labelledby
    是否设置正确?
  • 破坏性确认对话框是否明确了操作对象和后果?
  • 破坏性按钮是否有明确标注(非“确定”或“确认”)?
  • 是否避免了嵌套modal?
  • 移动端是否用bottom sheet替代了modal?