backdrop
组件

Dialog

一种覆盖在主窗口或另一对话框窗口之上的窗口,使下层内容变为不可操作。

功能特性

  • 支持模态和非模态模式。
  • 模态模式下自动实现焦点捕获。
  • 可受控或非受控使用。
  • 通过 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 参考

Root

包含对话框的所有部件

PropDefaultType
defaultOpen
false
boolean

The open state of the dialog when it is initially rendered. Use when you do not need to control its open state.

modal
true
boolean

The modality of the dialog When set to true,
interaction with outside elements will be disabled and only dialog content will be visible to screen readers.

open
boolean

The controlled open state of the dialog. Can be binded as v-model:open.

EmitPayload
update:open
[value: boolean]

Event handler called when the open state of the dialog changes.

Slots (default)Payload
open
boolean

Current open state

close
(): void

Close the dialog

Trigger

用于打开对话框的按钮

PropDefaultType
as
'button'
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

Data AttributeValue
[data-state]"open" | "closed"

Portal

使用时,会将遮罩层和内容部件传送至 body 中。

PropDefaultType
defer
boolean

Defer the resolving of a Teleport target until other parts of the application have mounted (requires Vue 3.5.0+)

reference

disabled
boolean

Disable teleport and render the component inline

reference

forceMount
boolean

Used to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries.

to
string | HTMLElement

Vue native teleport component prop :to

reference

Overlay

对话框打开时覆盖在非活动视图区域上的一层。

tip
Built with Presence component - supports any animation techniques while maintaining access to presence emitted events.
PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

forceMount
boolean

Used to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries.

Data AttributeValue
[data-state]"open" | "closed"

Content

包含要在打开的对话框中呈现的内容

tip
Built with Presence component - supports any animation techniques while maintaining access to presence emitted events.
PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

disableOutsidePointerEvents
boolean

When true, hover/focus/click interactions will be disabled on elements outside the DismissableLayer. Users will need to click twice on outside elements to interact with them: once to close the DismissableLayer, and again to trigger the element.

forceMount
boolean

Used to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries.

EmitPayload
closeAutoFocus
[event: Event]

Event handler called when auto-focusing on close. Can be prevented.

escapeKeyDown
[event: KeyboardEvent]

Event handler called when the escape key is down. Can be prevented.

focusOutside
[event: FocusOutsideEvent]

Event handler called when the focus moves outside of the DismissableLayer. Can be prevented.

interactOutside
[event: PointerDownOutsideEvent | FocusOutsideEvent]

Event handler called when an interaction happens outside the DismissableLayer. Specifically, when a pointerdown event happens outside or focus moves outside of it. Can be prevented.

openAutoFocus
[event: Event]

Event handler called when auto-focusing on open. Can be prevented.

pointerDownOutside
[event: PointerDownOutsideEvent]

Event handler called when a pointerdown event happens outside of the DismissableLayer. Can be prevented.

Data AttributeValue
[data-state]"open" | "closed"

Close

用于关闭对话框的按钮

PropDefaultType
as
'button'
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

Title

对话框打开时向屏幕阅读器播报的可访问标题。

如需隐藏标题,可使用我们的 Visually Hidden 工具将其包裹,如 <VisuallyHidden asChild>

PropDefaultType
as
'h2'
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

Description

对话框打开时向屏幕阅读器播报的可选可访问描述。

如需隐藏描述,可使用我们的 Visually Hidden 工具将其包裹,如 <VisuallyHidden asChild>。若要完全移除描述,请移除此部件并向 DialogContent 传递 :aria-describedby="undefined"

PropDefaultType
as
'p'
AsTag | Component

The element or component this component should render as. Can be overwritten by asChild.

asChild
boolean

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read our Composition guide for more details.

示例

嵌套对话框

可嵌套多层对话框。

异步表单提交后关闭

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

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));
            }
          "
        >
          <!-- 一些输入项 -->
          <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();
    }
  }"
>

自定义传送容器

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

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>

禁用外部交互关闭

例如,若您有某些全局 Toast 组件,点击它时不应关闭对话框。

可访问性

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

关闭图标按钮

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

vue
<template>
  <DialogRoot>
    <DialogTrigger />
    <DialogPortal>
      <DialogOverlay />
      <DialogContent>
        <DialogTitle />
        <DialogDescription />
        <DialogClose aria-label="关闭">
          <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>
          <!-- 一些输入项 -->
          <button type="submit" @click="close">
            Submit
          </button>
        </form>
      </DialogContent>
      <DialogFooter>
        <button type="submit" @click="close">
          Submit
        </button>
      </DialogFooter>
    </DialogPortal>
  </DialogRoot>
</template>

键盘交互

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

自定义 API

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

抽象遮罩层和关闭按钮

此示例抽象了 DialogOverlayDialogClose 部件。

用法

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

<template>
  <Dialog>
    <DialogTrigger>对话框触发器</DialogTrigger>
    <DialogContent>对话框内容</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">关闭</span>
      </DialogClose>
    </DialogContent>
  </DialogPortal>
</template>