mcpField
D
dialog-patterns

dialog-patterns

github.com/majesticlabs-dev设计与 UI5063 次安装7 周前更新
代码仓库

关于 dialog-patterns

dialog-patterns 面向 Rails 开发者,提供构建无障碍对话框和覆盖界面的模式。内容涵盖原生 HTML dialog、Turbo Frames 与 Stimulus,也包括无需 JavaScript 的确认方案。

主要功能

  1. 01原生对话框无障碍支持
  2. 02无需 JavaScript 的确认框
  3. 03Turbo Frame 异步模态框
  4. 04Toast 与侧滑面板模式
  5. 05表单验证处理

适用场景

  1. 01在 Rails 中构建模态表单
  2. 02确认破坏性操作
  3. 03显示 Toast 通知
  4. 04创建筛选器侧边面板

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/majesticlabs-dev/majestic-marketplace --skill dialog-patterns

安装后在 Claude Code 中输入/dialog-patterns确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
dialog-examples.md
toast-slideover-patterns.md
zero-js-patterns.md

详细介绍

Rails 的原生对话框模式

使用原生 HTML <dialog> 元素、Turbo Frames 和 Stimulus,构建现代、易于访问的对话框 UI。无需 JavaScript 框架或重量级库。

何时使用此技能

  • 为表单、确认操作或内容构建模态对话框
  • 创建 Toast/提醒通知
  • 实现确认对话框(删除、破坏性操作)
  • 构建任何需要焦点管理和无障碍支持的叠加层 UI

为什么使用原生 <dialog>?

特性原生 <dialog>自定义模态框
焦点限制内置手动实现
按 ESC 关闭内置手动实现
背景遮罩内置(::backdrop)手动叠加
无障碍支持原生 role="dialog"手动配置 ARIA
顶层自动(位于所有内容之上)与 z-index 较劲
滚动锁定自动手动设置 overflow: hidden

零 JavaScript 确认对话框(推荐)

现代浏览器支持声明式控制对话框的 Invoker Commands API,无需 JavaScript。完整示例请参见 references/zero-js-patterns.md。

快速参考
<%= button_tag "Delete", commandfor: "delete-#{post.id}", command: "show-modal" %>

<dialog id="delete-<%= post.id %>" closedby="any" role="alertdialog">
  <h3>Delete "<%= post.title %>"?</h3>
  <button commandfor="delete-<%= post.id %>" command="close">Cancel</button>
  <%= button_to "Delete", post, method: :delete %>
</dialog>
关键属性
属性用途
commandfor="id"引用要控制的对话框
command="show-modal"以模态方式打开(背景遮罩、焦点限制)
command="close"关闭对话框
closedby="any"启用点击背景遮罩和按 ESC 关闭
何时使用零 JavaScript,何时使用 Stimulus
场景方案
简单确认操作零 JavaScript(Invoker Commands)
含异步内容的模态框Stimulus + Turbo Frames
复杂的多步骤对话框Stimulus 控制器
动画CSS @starting-style
其他模式(参见 references/)
  • 使用 @starting-style 实现 CSS 进入/退出过渡动画
  • 使用 Turbo.config.forms.confirm 替换难看的浏览器对话框
  • 使用渐进增强确保跨浏览器兼容性

核心模式:使用 Turbo Frames 的异步模态框

Rails 模态框的推荐模式结合了三种技术:

  1. Turbo Frame - 无需重新加载页面即可异步加载内容
  2. 原生 <dialog> - 无障碍的模态呈现
  3. Stimulus 控制器 - 生命周期管理
步骤 1:布局容器

在布局中添加一个 modal turbo-frame:

<%# app/views/layouts/application.html.erb %>
<body>
  <%= yield %>

  <%# Modal injection point %>
  <%= turbo_frame_tag :modal %>
</body>
步骤 2:触发链接

从任意链接指向 modal frame:

<%# Any view %>
<%= link_to "New Post", new_post_path, data: { turbo_frame: :modal } %>
<%= link_to "Edit", edit_post_path(@post), data: { turbo_frame: :modal } %>
<%= link_to "Confirm Delete", confirm_delete_post_path(@post), data: { turbo_frame: :modal } %>
步骤 3:模态框内容视图

将模态框内容包裹在匹配的 turbo-frame 中,并嵌套内层 frame:

<%# app/views/posts/new.html.erb %>
<%= turbo_frame_tag :modal do %>
  <%# Inner frame prevents flash during form validation %>
  <%= turbo_frame_tag :modal_content do %>
    <dialog data-controller="dialog" data-action="click->dialog#clickOutside" open>
      <article>
        <header>
          <h2>New Post</h2>
          <button data-action="dialog#close" aria-label="Close">&times;</button>
        </header>

        <%= render "form", post: @post %>
      </article>
    </dialog>
  <% end %>
<% end %>
步骤 4:Stimulus 控制器

关键行为:连接时调用 showModal(),断开连接时调用 replaceChildren()(防止内容过时),使用 clickOutside 关闭背景遮罩。

完整的 Stimulus 控制器、CSS 样式和 Tailwind 变体,请参见 references/dialog-examples.md。

为什么要嵌套 Turbo Frames?

嵌套 frame 模式(modal > modal_content)可避免内容闪烁:

<%= turbo_frame_tag :modal do %>
  <%= turbo_frame_tag :modal_content do %>
    <dialog>...</dialog>
  <% end %>
<% end %>

未使用嵌套 frame 时的问题: 模态框中的表单出现验证错误并重新渲染时,外层 frame 会先短暂显示旧内容,然后才替换内容。

使用嵌套 frame 的解决方案: 内层 frame 独立处理表单重新渲染,使模态框结构保持稳定。

在模态框中处理表单

提交成功

使用 Turbo 重定向来关闭模态框并更新页面:

# app/controllers/posts_controller.rb
def create
  @post = Post.new(post_params)

  if @post.save
    redirect_to posts_path, notice: "Post created!"
  else
    render :new, status: :unprocessable_entity
  end
end

重定向会导航到 _top(整个页面),从而关闭模态框。

验证错误

使用 422 状态重新渲染表单,以保持模态框打开:

render :new, status: :unprocessable_entity
Turbo Stream 响应(留在模态框中)

使用 turbo_stream.update("modal", "") 清空模态框,无需完整重定向。完整示例请参见 references/dialog-examples.md。

确认对话框模式

对于破坏性操作:添加 confirm_delete 成员路由,在 turbo frame 中渲染对话框,并通过带有 data: { turbo_frame: :modal } 的 link_to 触发。

完整的确认对话框视图、路由和触发方式,请参阅 references/dialog-examples.md。

提示消息/Toast 模式

用于 flash 消息和通知。非模态呈现时使用 show(),而不是 showModal()。完整实现请参阅 references/toast-slideover-patterns.md。

<dialog class="toast" data-controller="toast" data-toast-duration-value="5000">
  <p><%= message %></p>
</dialog>

主要区别:show() 打开时没有背景遮罩或焦点陷阱(用于 Toast),而 showModal() 会在屏幕中央打开并显示背景遮罩(用于模态框)。

滑出面板模式

用于侧边面板(设置、筛选条件、详情)。有关样式和动画,请参阅 references/toast-slideover-patterns.md。

<dialog class="slideover" data-controller="dialog" data-action="click->dialog#clickOutside">
  <aside>
    <header><h2>Filters</h2></header>
    <%= render "filters" %>
  </aside>
</dialog>

无障碍功能

原生 <dialog> 会自动提供焦点陷阱、按 ESC 关闭、使背景内容不可交互,并置于顶层。此外,还需确保:

  • 显示关闭按钮(不能只依靠 ESC)
  • 使用 aria-labelledby / aria-describedby 提供描述性上下文
  • 关闭时将焦点返回触发元素(在 connect() 中保存 document.activeElement)

有关增强无障碍功能和焦点返回示例,请参阅 references/dialog-examples.md。

常见模式摘要

模式容器Stimulusshow 方法
模态表单turbo_frame_tag :modaldialogshowModal()
确认对话框turbo_frame_tag :modaldialogshowModal()
Toast/提示消息固定位置toastshow()
滑出面板turbo_frame_tag :modaldialogshowModal()

应避免的反模式

反模式问题解决方案
不使用 <dialog> 的自定义模态框缺少原生无障碍支持使用原生 <dialog>
缺少嵌套 turbo-frame验证时内容闪烁添加内层 frame
关闭时不清空 frame重新打开时显示过时内容在 disconnect() 中使用 replaceChildren() 清空
使用 z-index 控制层叠与其他元素发生冲突<dialog> 使用顶层
手动设置焦点陷阱复杂且容易出错由 showModal() 处理
内联背景遮罩 div增加额外标记使用 ::backdrop 伪元素

测试对话框

# System test - use `within "dialog"` to scope assertions
within "dialog" do
  fill_in "Title", with: "My Post"
  click_button "Create"
end
expect(page).not_to have_selector("dialog[open]")  # Modal closed

浏览器支持

模式ChromeFirefoxSafari
原生 <dialog>37+98+15.4+
Invoker Commands135+144+26.2+
@starting-style117+129+17.5+

对于较旧的浏览器:dialog polyfill、invokers polyfill。有关渐进增强策略,请参阅 references/zero-js-patterns.md。

评论

支持 Markdown