Reka UI 标志Reka
backdrop
组件

对话框

一个覆盖在主窗口或另一个对话框窗口之上的窗口,使下方内容处于非活动状态。

特性

  • 支持模态和非模态模式。
  • 在模态模式下,焦点会自动捕获。
  • 可控或不可控。
  • 通过 TitleDescription 组件管理屏幕阅读器公告。
  • 按 Esc 键自动关闭组件。

安装

从命令行安装此组件。

sh
$ npm add reka-ui

结构

导入所有部分并将其组合。

vue
<script setup>
import {
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogOverlay,
  DialogPortal,
  DialogRoot,
  DialogTitle,
  DialogTrigger,
} from 'reka-ui'
</script>

<template>
  <DialogRoot>
    <DialogTrigger />
    <DialogPortal>
      <DialogOverlay />
      <DialogContent>
        <DialogTitle />
        <DialogDescription />
        <DialogClose />
      </DialogContent>
    </DialogPortal>
  </DialogRoot>
</template>

API 参考

包含对话框的所有部分

属性默认类型
defaultOpen
false
布尔值

对话框初始渲染时的打开状态。当您不需要控制其打开状态时使用。

modal
true
布尔值

对话框的模态性。当设置为 true 时,
与外部元素的交互将被禁用,并且只有对话框内容对屏幕阅读器可见。

open
布尔值

对话框的受控打开状态。可以绑定为 v-model:open

事件触发载荷
update:open
[value: boolean]

当对话框的打开状态改变时调用的事件处理程序。

插槽(默认)载荷
open
布尔值

当前打开状态

close
(): void

关闭对话框

触发器

打开对话框的按钮

属性默认类型
as
'button'
AsTag | Component

此组件应渲染为的元素或组件。可通过 asChild 覆盖。

asChild
布尔值

更改默认渲染元素为作为子项传递的元素,合并它们的属性和行为。

阅读我们的 组合指南了解更多详情。

数据属性
[data-state]"打开" | "关闭"

Portal

使用时,将您的覆盖层和内容部分传送(portals)到 body 中。

属性默认类型
defer
布尔值

延迟解析 Teleport 目标,直到应用程序的其他部分挂载(需要 Vue 3.5.0+)

reference

disabled
布尔值

禁用传送并内联渲染组件

reference

forceMount
布尔值

当需要更多控制时,用于强制挂载。在与 Vue 动画库控制动画时很有用。

to
字符串 | HTMLElement

Vue 原生传送组件属性 :to

reference

覆盖层

对话框打开时,覆盖视图非活动部分的图层。

提示
使用 Presence 组件构建 - 支持任何 动画技术,同时保持对 Presence 发出事件的访问。
属性默认类型
as
'div'
AsTag | Component

此组件应渲染为的元素或组件。可通过 asChild 覆盖。

asChild
布尔值

更改默认渲染元素为作为子项传递的元素,合并它们的属性和行为。

阅读我们的 组合指南了解更多详情。

forceMount
布尔值

当需要更多控制时,用于强制挂载。在与 Vue 动画库控制动画时很有用。

数据属性
[data-state]"打开" | "关闭"

内容

包含要在打开的对话框中渲染的内容

提示
使用 Presence 组件构建 - 支持任何 动画技术,同时保持对 Presence 发出事件的访问。
属性默认类型
as
'div'
AsTag | Component

此组件应渲染为的元素或组件。可通过 asChild 覆盖。

asChild
布尔值

更改默认渲染元素为作为子项传递的元素,合并它们的属性和行为。

阅读我们的 组合指南了解更多详情。

disableOutsidePointerEvents
布尔值

当设置为 true 时,DismissableLayer 外部元素的悬停/焦点/点击交互将被禁用。用户需要点击两次外部元素才能与其交互:第一次关闭 DismissableLayer,第二次触发元素。

forceMount
布尔值

当需要更多控制时,用于强制挂载。在与 Vue 动画库控制动画时很有用。

事件触发载荷
closeAutoFocus
[事件: Event]

关闭时自动聚焦调用的事件处理程序。可阻止其默认行为。

escapeKeyDown
[事件: KeyboardEvent]

按下 Esc 键时调用的事件处理程序。可阻止其默认行为。

focusOutside
[事件: FocusOutsideEvent]

当焦点移出 DismissableLayer 时调用的事件处理程序。可阻止其默认行为。

interactOutside
[事件: PointerDownOutsideEvent | FocusOutsideEvent]

DismissableLayer 外部发生交互时调用的事件处理程序。具体来说,当 pointerdown 事件在外部发生或焦点移出时。可阻止其默认行为。

openAutoFocus
[事件: Event]

打开时自动聚焦调用的事件处理程序。可阻止其默认行为。

pointerDownOutside
[事件: PointerDownOutsideEvent]

pointerdown 事件在 DismissableLayer 外部发生时调用的事件处理程序。可阻止其默认行为。

数据属性
[data-state]"打开" | "关闭"

关闭

关闭对话框的按钮

属性默认类型
as
'button'
AsTag | Component

此组件应渲染为的元素或组件。可通过 asChild 覆盖。

asChild
布尔值

更改默认渲染元素为作为子项传递的元素,合并它们的属性和行为。

阅读我们的 组合指南了解更多详情。

标题

对话框打开时宣布的可访问标题。

如果您想隐藏标题,请将其包裹在我们的 Visually Hidden 实用工具中,例如 <VisuallyHidden asChild>

属性默认类型
as
'h2'
AsTag | Component

此组件应渲染为的元素或组件。可通过 asChild 覆盖。

asChild
布尔值

更改默认渲染元素为作为子项传递的元素,合并它们的属性和行为。

阅读我们的 组合指南了解更多详情。

描述

对话框打开时宣布的可选可访问描述。

如果您想隐藏描述,请将其包裹在我们的 Visually Hidden 实用工具中,例如 <VisuallyHidden asChild>。如果您想完全删除描述,请移除此部分并向 DialogContent 传入 :aria-describedby="undefined"

属性默认类型
as
'p'
AsTag | Component

此组件应渲染为的元素或组件。可通过 asChild 覆盖。

asChild
布尔值

更改默认渲染元素为作为子项传递的元素,合并它们的属性和行为。

阅读我们的 组合指南了解更多详情。

示例

嵌套对话框

您可以嵌套多层对话框。

异步表单提交后关闭

使用受控属性在异步操作完成后以编程方式关闭对话框。

vue
<script setup>
import { DialogContent, DialogOverlay, DialogPortal, DialogRoot, DialogTrigger } from 'reka-ui'

const wait = () => new Promise(resolve => setTimeout(resolve, 1000))
const open = ref(false)
</script>

<template>
  <DialogRoot v-model:open="open">
    <DialogTrigger>Open</DialogTrigger>
    <DialogPortal>
      <DialogOverlay />
      <DialogContent>
        <form
          @submit.prevent="
            (event) => {
              wait().then(() => (open = false));
            }
          "
        >
          <!-- some inputs -->
          <button type="submit">
            Submit
          </button>
        </form>
      </DialogContent>
    </DialogPortal>
  </DialogRoot>
</template>

可滚动覆盖层

将内容移到覆盖层内部,以渲染带溢出内容的对话框。

vue
// index.vue
<script setup>
import { DialogContent, DialogOverlay, DialogPortal, DialogRoot, DialogTrigger } from 'reka-ui'
import './styles.css'
</script>

<template>
  <DialogRoot>
    <DialogTrigger />
    <DialogPortal>
      <DialogOverlay class="DialogOverlay">
        <DialogContent class="DialogContent">
          ...
        </DialogContent>
      </DialogOverlay>
    </DialogPortal>
  </DialogRoot>
</template>
css
/* styles.css */
.DialogOverlay {
  background: rgba(0 0 0 / 0.5);
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: grid;
  place-items: center;
  overflow-y: auto;
}

.DialogContent {
  min-width: 300px;
  background: white;
  padding: 30px;
  border-radius: 4px;
}

然而,这种方法有一个注意事项,即用户可能会点击滚动条而意外关闭对话框。目前没有通用的解决方案可以解决这个问题,但您可以向 DialogContent 添加以下代码片段,以防止在点击滚动条时关闭模态框。

vue
<DialogContent
  @pointer-down-outside="(event) => {
    const originalEvent = event.detail.originalEvent;
    const target = originalEvent.target as HTMLElement;
    if (originalEvent.offsetX > target.clientWidth || originalEvent.offsetY > target.clientHeight) {
      event.preventDefault();
    }
  }"
>

自定义 Portal 容器

自定义对话框传送到的元素。

vue
<script setup>
import { DialogContent, DialogOverlay, DialogPortal, DialogRoot, DialogTrigger } from 'reka-ui'

const container = ref(null)
</script>

<template>
  <div>
    <DialogRoot>
      <DialogTrigger />
      <DialogPortal to="container">
        <DialogOverlay />
        <DialogContent>...</DialogContent>
      </DialogPortal>
    </DialogRoot>

    <div ref="container" />
  </div>
</template>

禁用外部交互关闭

例如,如果您有一些全局的 Toaster 组件,点击它时不应关闭对话框。

可访问性

遵循 对话框 WAI-ARIA 设计模式

关闭图标按钮

提供图标(或字体图标)时,请记住为屏幕阅读器用户正确标注它。

vue
<template>
  <DialogRoot>
    <DialogTrigger />
    <DialogPortal>
      <DialogOverlay />
      <DialogContent>
        <DialogTitle />
        <DialogDescription />
        <DialogClose aria-label="Close">
          <span aria-hidden="true">×</span>
        </DialogClose>
      </DialogContent>
    </DialogPortal>
  </DialogRoot>
</template>

使用插槽属性关闭

或者,您可以使用 DialogRoot 插槽属性提供的 close 方法以编程方式关闭对话框。

vue
<script setup>
import { DialogContent, DialogOverlay, DialogPortal, DialogRoot, DialogTrigger } from 'reka-ui'
</script>

<template>
  <DialogRoot v-slot="{ close }">
    <DialogTrigger>Open</DialogTrigger>
    <DialogPortal>
      <DialogOverlay />
      <DialogContent>
        <form>
          <!-- some inputs -->
          <button type="submit" @click="close">
            Submit
          </button>
        </form>
      </DialogContent>
      <DialogFooter>
        <button type="submit" @click="close">
          Submit
        </button>
      </DialogFooter>
    </DialogPortal>
  </DialogRoot>
</template>

键盘交互

描述
空格键
打开/关闭对话框
回车键
打开/关闭对话框
Tab
将焦点移动到下一个可聚焦元素。
Shift + Tab
将焦点移至上一个可聚焦元素。
Esc键
关闭对话框并将焦点移至 DialogTrigger

自定义 API

通过将原始部件抽象到您自己的组件中来创建您自己的 API。

抽象化覆盖层和关闭按钮

此示例抽象化了 DialogOverlayDialogClose 部分。

用法

vue
<script setup>
import { Dialog, DialogContent, DialogTrigger } from './your-dialog'
</script>

<template>
  <Dialog>
    <DialogTrigger>Dialog trigger</DialogTrigger>
    <DialogContent>Dialog Content</DialogContent>
  </Dialog>
</template>

实现

ts
// your-dialog.ts
export { default as DialogContent } from 'DialogContent.vue'
export { DialogRoot as Dialog, DialogTrigger } from 'reka-ui'
vue
<!-- DialogContent.vue -->
<script setup lang="ts">
import type { DialogContentEmits, DialogContentProps } from 'reka-ui'
import { Cross2Icon } from '@radix-icons/vue'
import { DialogClose, DialogContent, DialogOverlay, DialogPortal, useForwardPropsEmits } from 'reka-ui'

const props = defineProps<DialogContentProps>()
const emits = defineEmits<DialogContentEmits>()

const forwarded = useForwardPropsEmits(props, emits)
</script>

<template>
  <DialogPortal>
    <DialogOverlay />
    <DialogContent v-bind="forwarded">
      <slot />

      <DialogClose>
        <Cross2Icon />
        <span class="sr-only">Close</span>
      </DialogClose>
    </DialogContent>
  </DialogPortal>
</template>