import { Dialog, Button } from "@cloudflare/kumo";
import { X } from "@phosphor-icons/react";

export function DialogWithActionsDemo() {
  return (
    <Dialog.Root>
      <Dialog.Trigger render={(p) => <Button {...p}>Delete</Button>} />
      <Dialog className="p-8">
        <div className="mb-4 flex items-start justify-between gap-4">
          <Dialog.Title className="text-2xl font-semibold">
            Modal Title
          </Dialog.Title>
          <Dialog.Close
            aria-label="Close"
            render={(props) => (
              <Button
                {...props}
                variant="secondary"
                shape="square"
                icon={<X />}
                aria-label="Close"
              />
            )}
          />
        </div>
        <Dialog.Description className="text-kumo-subtle">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
          eiusmod tempor incididunt ut labore et dolore magna aliqua.
        </Dialog.Description>
        <div className="mt-8 flex justify-end gap-2">
          <Dialog.Close
            render={(props) => (
              <Button variant="secondary" {...props}>
                Cancel
              </Button>
            )}
          />
          <Dialog.Close
            render={(props) => (
              <Button variant="destructive" {...props}>
                Delete
              </Button>
            )}
          />
        </div>
      </Dialog>
    </Dialog.Root>
  );
}

安装

批量导入

import { Dialog } from "@cloudflare/kumo";

按需导入

import { Dialog } from "@cloudflare/kumo/components/dialog";

用法

import { Dialog, Button } from "@cloudflare/kumo";

export default function Example() {
  return (
    <Dialog.Root>
      <Dialog.Trigger render={(p) => <Button {...p}>Open</Button>} />
      <Dialog>
        <Dialog.Title>Dialog Title</Dialog.Title>
        <Dialog.Description>Dialog content goes here.</Dialog.Description>
        <div className="flex justify-end gap-2 mt-4">
          <Dialog.Close
            render={(p) => (
              <Button variant="secondary" {...p}>
                Cancel
              </Button>
            )}
          />
        </div>
      </Dialog>
    </Dialog.Root>
  );
}

Dialog 与 Alert Dialog

Dialog 组件支持两种 ARIA role,以向辅助技术正确传达语义含义:

角色用例行为

role="dialog" (默认)

通用的模态、表单和内容展示

默认可关闭
role="alertdialog"

危险操作、确认、关键警告

需要用户明确确认

示例

基本对话框

import { Dialog, Button } from "@cloudflare/kumo";
import { X } from "@phosphor-icons/react";

export function DialogBasicDemo() {
  return (
    <Dialog.Root>
      <Dialog.Trigger render={(p) => <Button {...p}>Click me</Button>} />
      <Dialog className="p-8">
        <div className="mb-4 flex items-start justify-between gap-4">
          <Dialog.Title className="text-2xl font-semibold">
            Modal Title
          </Dialog.Title>
          <Dialog.Close
            aria-label="Close"
            render={(props) => (
              <Button
                {...props}
                variant="secondary"
                shape="square"
                icon={<X />}
                aria-label="Close"
              />
            )}
          />
        </div>
        <Dialog.Description className="text-kumo-subtle">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
          eiusmod tempor incididunt ut labore et dolore magna aliqua.
        </Dialog.Description>
      </Dialog>
    </Dialog.Root>
  );
}

尺寸

size 属性控制对话框在桌面上的固定宽度。超出对话框宽度的内容 将在对话框内部横向滚动,而不会撑宽对话框。

import { Dialog, Button } from "@cloudflare/kumo";
import { DialogProps } from "@cloudflare/kumo";
import { X } from "@phosphor-icons/react";

/**
 * Demonstrates that each dialog size holds its fixed width regardless of
 * content. Each dialog contains a wide table that would previously cause the
 * dialog to stretch beyond its intended size.
 */
export function DialogSizesDemo() {
  const sizes: {
    size: NonNullable<DialogProps["size"]>;
    label: string;
    width: string;
  }[] = [
    { size: "sm", label: "Small", width: "288px" },
    { size: "base", label: "Base", width: "384px" },
    { size: "lg", label: "Large", width: "512px" },
    { size: "xl", label: "Extra Large", width: "768px" },
  ];

  return (
    <div className="flex flex-wrap gap-2">
      {sizes.map(({ size, label, width }) => (
        <Dialog.Root key={size}>
          <Dialog.Trigger
            render={(p) => (
              <Button variant="secondary" {...p}>
                {label} ({width})
              </Button>
            )}
          />
          <Dialog size={size} className="p-8">
            <div className="mb-4 flex items-start justify-between gap-4">
              <Dialog.Title className="text-2xl font-semibold">
                {label} Dialog
              </Dialog.Title>
              <Dialog.Close
                aria-label="Close"
                render={(props) => (
                  <Button
                    {...props}
                    variant="secondary"
                    shape="square"
                    icon={<X />}
                    aria-label="Close"
                  />
                )}
              />
            </div>
            <Dialog.Description className="text-kumo-subtle">
              This <code>size="{size}"</code> dialog should stay at {width} wide
              regardless of the content below.
            </Dialog.Description>
            <div className="mt-4 overflow-auto rounded-md border border-kumo-line">
              <table className="w-max text-sm">
                <thead className="bg-kumo-elevated text-left">
                  <tr>
                    <th className="px-3 py-2">Resource</th>
                    <th className="px-3 py-2">Region</th>
                    <th className="px-3 py-2">Status</th>
                    <th className="px-3 py-2">Latency</th>
                    <th className="px-3 py-2">Requests</th>
                    <th className="px-3 py-2">Last Deployed</th>
                  </tr>
                </thead>
                <tbody className="divide-y divide-kumo-hairline">
                  <tr>
                    <td className="px-3 py-2">api-gateway-prod</td>
                    <td className="px-3 py-2">us-east-1</td>
                    <td className="px-3 py-2 text-kumo-success">Healthy</td>
                    <td className="px-3 py-2">12ms</td>
                    <td className="px-3 py-2">1,234,567</td>
                    <td className="px-3 py-2">2026-06-23</td>
                  </tr>
                  <tr>
                    <td className="px-3 py-2">worker-analytics</td>
                    <td className="px-3 py-2">eu-west-1</td>
                    <td className="px-3 py-2 text-kumo-warning">Degraded</td>
                    <td className="px-3 py-2">89ms</td>
                    <td className="px-3 py-2">456,789</td>
                    <td className="px-3 py-2">2026-06-22</td>
                  </tr>
                </tbody>
              </table>
            </div>
            <div className="mt-6 flex justify-end gap-2">
              <Dialog.Close
                render={(props) => (
                  <Button variant="secondary" {...props}>
                    Close
                  </Button>
                )}
              />
            </div>
          </Dialog>
        </Dialog.Root>
      ))}
    </div>
  );
}

Alert Dialog(role="alertdialog"

对于危险或确认类对话框,请在 Dialog.Root 上使用 role="alertdialog"。这会以 role="alertdialog" 而非 role="dialog" 渲染对话框,提供正确的无障碍语义。

何时使用 role="alertdialog"

  • 危险操作(删除、丢弃、移除)
  • 需要用户明确确认的确认流程
  • 无法撤销的操作
  • 关键警告或错误
import { Dialog, Button } from "@cloudflare/kumo";
import { Warning } from "@phosphor-icons/react";

/**
 * Alert dialog for destructive actions that uses role="alertdialog".
 * This provides proper accessibility semantics for confirmation flows.
 */
export function DialogAlertDemo() {
  return (
    <Dialog.Root role="alertdialog">
      <Dialog.Trigger
        render={(p) => (
          <Button {...p} variant="destructive">
            Delete Account
          </Button>
        )}
      />
      <Dialog className="p-8">
        <div className="mb-4 flex items-center gap-3">
          <div className="flex h-10 w-10 items-center justify-center rounded-full bg-kumo-danger/20">
            <Warning size={20} className="text-kumo-danger" weight="fill" />
          </div>
          <Dialog.Title className="text-xl font-semibold">
            Delete Account?
          </Dialog.Title>
        </div>
        <Dialog.Description className="text-kumo-subtle">
          This action cannot be undone. All your data will be permanently
          removed from our servers. Are you sure you want to proceed?
        </Dialog.Description>
        <div className="mt-8 flex justify-end gap-2">
          <Dialog.Close
            render={(props) => (
              <Button variant="secondary" {...props}>
                Cancel
              </Button>
            )}
          />
          <Dialog.Close
            render={(props) => (
              <Button variant="destructive" {...props}>
                Delete Account
              </Button>
            )}
          />
        </div>
      </Dialog>
    </Dialog.Root>
  );
}

确认对话框(使用 disablePointerDismissal

对于不应通过点击外部区域关闭的确认对话框,请在 Dialog.Root 上使用 disablePointerDismissal。可将其与 role="alertdialog" 组合使用以获得正确的无障碍支持。

import { Dialog, Button } from "@cloudflare/kumo";
import { Warning } from "@phosphor-icons/react";

export function DialogConfirmationDemo() {
  return (
    <Dialog.Root disablePointerDismissal>
      <Dialog.Trigger
        render={(p) => (
          <Button {...p} variant="destructive">
            Delete Project
          </Button>
        )}
      />
      <Dialog className="p-8">
        <div className="mb-4 flex items-center gap-3">
          <div className="flex h-10 w-10 items-center justify-center rounded-full bg-kumo-danger/20">
            <Warning size={20} className="text-kumo-danger" />
          </div>
          <Dialog.Title className="text-xl font-semibold">
            Delete Project?
          </Dialog.Title>
        </div>
        <Dialog.Description className="text-kumo-subtle">
          This action cannot be undone. This will permanently delete the project
          and all associated data.
        </Dialog.Description>
        <div className="mt-8 flex justify-end gap-2">
          <Dialog.Close
            render={(props) => (
              <Button variant="secondary" {...props}>
                Cancel
              </Button>
            )}
          />
          <Dialog.Close
            render={(props) => (
              <Button variant="destructive" {...props}>
                Delete
              </Button>
            )}
          />
        </div>
      </Dialog>
    </Dialog.Root>
  );
}

带操作按钮

import { Dialog, Button } from "@cloudflare/kumo";
import { X } from "@phosphor-icons/react";

export function DialogWithActionsDemo() {
  return (
    <Dialog.Root>
      <Dialog.Trigger render={(p) => <Button {...p}>Delete</Button>} />
      <Dialog className="p-8">
        <div className="mb-4 flex items-start justify-between gap-4">
          <Dialog.Title className="text-2xl font-semibold">
            Modal Title
          </Dialog.Title>
          <Dialog.Close
            aria-label="Close"
            render={(props) => (
              <Button
                {...props}
                variant="secondary"
                shape="square"
                icon={<X />}
                aria-label="Close"
              />
            )}
          />
        </div>
        <Dialog.Description className="text-kumo-subtle">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
          eiusmod tempor incididunt ut labore et dolore magna aliqua.
        </Dialog.Description>
        <div className="mt-8 flex justify-end gap-2">
          <Dialog.Close
            render={(props) => (
              <Button variant="secondary" {...props}>
                Cancel
              </Button>
            )}
          />
          <Dialog.Close
            render={(props) => (
              <Button variant="destructive" {...props}>
                Delete
              </Button>
            )}
          />
        </div>
      </Dialog>
    </Dialog.Root>
  );
}

自定义最大宽度

max-w-lg 等消费方最大宽度工具应在桌面上限制对话框的宽度, 即使对话框内部包含较宽的固有内容。

import { Dialog, Button } from "@cloudflare/kumo";
import { X } from "@phosphor-icons/react";

/**
 * Dialog with a consumer-provided max width and wide intrinsic content.
 * The panel should stay capped at max-w-lg on desktop.
 */
export function DialogMaxWidthDemo() {
  return (
    <Dialog.Root>
      <Dialog.Trigger
        render={(p) => <Button {...p}>Open capped dialog</Button>}
      />
      <Dialog className="max-w-lg p-8">
        <div className="mb-4 flex items-start justify-between gap-4">
          <Dialog.Title className="text-2xl font-semibold">
            Max width override
          </Dialog.Title>
          <Dialog.Close
            aria-label="Close"
            render={(props) => (
              <Button
                {...props}
                variant="secondary"
                shape="square"
                icon={<X />}
                aria-label="Close"
              />
            )}
          />
        </div>
        <Dialog.Description className="text-kumo-subtle">
          This dialog uses <code>className="max-w-lg"</code> and should stay
          capped around 512px on desktop.
        </Dialog.Description>
        <div className="mt-4 truncate rounded-md border border-kumo-line bg-kumo-recessed p-3 font-mono text-sm">
          abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
        </div>
      </Dialog>
    </Dialog.Root>
  );
}

搭配 Select

包含 Select 下拉框的对话框。

import { Dialog, Button, Select } from "@cloudflare/kumo";
import { X } from "@phosphor-icons/react";

export function DialogWithSelectDemo() {
  return (
    <Dialog.Root>
      <Dialog.Trigger render={(p) => <Button {...p}>Open Form</Button>} />
      <Dialog className="p-8">
        <div className="mb-4 flex items-start justify-between gap-4">
          <Dialog.Title className="text-2xl font-semibold">
            Create Resource
          </Dialog.Title>
          <Dialog.Close
            aria-label="Close"
            render={(props) => (
              <Button
                {...props}
                variant="secondary"
                shape="square"
                icon={<X />}
                aria-label="Close"
              />
            )}
          />
        </div>
        <Dialog.Description className="mb-4 text-kumo-subtle">
          Select a region for your new resource.
        </Dialog.Description>
        <Select
          className="w-full"
          placeholder="Select region..."
          renderValue={(v) => regions.find((r) => r.value === v)?.label}
        >
          {regions.map((region) => (
            <Select.Option key={region.value} value={region.value}>
              {region.label}
            </Select.Option>
          ))}
        </Select>
        <div className="mt-8 flex justify-end gap-2">
          <Dialog.Close
            render={(props) => (
              <Button variant="secondary" {...props}>
                Cancel
              </Button>
            )}
          />
          <Button variant="primary">Create</Button>
        </div>
      </Dialog>
    </Dialog.Root>
  );
}

搭配 Combobox

包含 Combobox 的对话框,用于可搜索选择。

import { useState } from "react";
import { Dialog, Button, Combobox } from "@cloudflare/kumo";
import { X } from "@phosphor-icons/react";

export function DialogWithComboboxDemo() {
  const [value, setValue] = useState<{ value: string; label: string } | null>(
    null,
  );

  return (
    <Dialog.Root>
      <Dialog.Trigger render={(p) => <Button {...p}>Open Form</Button>} />
      <Dialog className="p-8">
        <div className="mb-4 flex items-start justify-between gap-4">
          <Dialog.Title className="text-2xl font-semibold">
            Create Resource
          </Dialog.Title>
          <Dialog.Close
            aria-label="Close"
            render={(props) => (
              <Button
                {...props}
                variant="secondary"
                shape="square"
                icon={<X />}
                aria-label="Close"
              />
            )}
          />
        </div>
        <Dialog.Description className="mb-4 text-kumo-subtle">
          Search and select a region for your new resource.
        </Dialog.Description>
        <Combobox value={value} onValueChange={setValue} items={regions}>
          <Combobox.TriggerInput
            className="w-full"
            placeholder="Search regions..."
          />
          <Combobox.Content>
            <Combobox.Empty>No regions found</Combobox.Empty>
            <Combobox.List>
              {(item: { value: string; label: string }) => (
                <Combobox.Item key={item.value} value={item}>
                  {item.label}
                </Combobox.Item>
              )}
            </Combobox.List>
          </Combobox.Content>
        </Combobox>
        <div className="mt-8 flex justify-end gap-2">
          <Dialog.Close
            render={(props) => (
              <Button variant="secondary" {...props}>
                Cancel
              </Button>
            )}
          />
          <Button variant="primary">Create</Button>
        </div>
      </Dialog>
    </Dialog.Root>
  );
}

搭配 Dropdown

包含 Dropdown 菜单的对话框。

import { Dialog, Button, DropdownMenu } from "@cloudflare/kumo";
import { X } from "@phosphor-icons/react";

export function DialogWithDropdownDemo() {
  return (
    <Dialog.Root>
      <Dialog.Trigger render={(p) => <Button {...p}>Open Form</Button>} />
      <Dialog className="p-8">
        <div className="mb-4 flex items-start justify-between gap-4">
          <Dialog.Title className="text-2xl font-semibold">
            Resource Actions
          </Dialog.Title>
          <Dialog.Close
            aria-label="Close"
            render={(props) => (
              <Button
                {...props}
                variant="secondary"
                shape="square"
                icon={<X />}
                aria-label="Close"
              />
            )}
          />
        </div>
        <Dialog.Description className="mb-4 text-kumo-subtle">
          Choose an action for the selected resource.
        </Dialog.Description>
        <DropdownMenu>
          <DropdownMenu.Trigger render={<Button>Actions</Button>} />
          <DropdownMenu.Content>
            <DropdownMenu.Item>Edit</DropdownMenu.Item>
            <DropdownMenu.Item>Duplicate</DropdownMenu.Item>
            <DropdownMenu.Separator />
            <DropdownMenu.Item variant="danger">Delete</DropdownMenu.Item>
          </DropdownMenu.Content>
        </DropdownMenu>
        <div className="mt-8 flex justify-end">
          <Dialog.Close
            render={(props) => (
              <Button variant="secondary" {...props}>
                Close
              </Button>
            )}
          />
        </div>
      </Dialog>
    </Dialog.Root>
  );
}

API 参考

Dialog

渲染模态覆盖层和弹出层的主对话框容器。

PropTypeDefaultDescription
classNamestring-Additional CSS classes merged via `cn()`.
childrenReactNode-Dialog content (typically Title, Description, Close, and action buttons).
containerPortalContainer-Container element for the portal. Use this to render the dialog inside a Shadow DOM or custom container. Overrides `KumoPortalProvider` context.
size"base" | "sm" | "lg" | "xl""base"Dialog width. - `"sm"` — Small (288px) for simple confirmations - `"base"` — Default (384px) - `"lg"` — Large (512px) for complex content - `"xl"` — Extra large (768px) for detailed views

Dialog.Root

控制对话框的打开状态。不渲染自身的 HTML 元素。

属性类型默认值说明
role”dialog” | “alertdialog""dialog”

对话框的 ARIA role。危险或确认流程请使用 "alertdialog"

disablePointerDismissalbooleanfalse

为 true 时,可防止对话框通过点击外部区域被关闭。

PropTypeDefault

No component-specific props. Accepts standard HTML attributes.

Dialog.Trigger

点击即可打开对话框的按钮。

PropTypeDefault

No component-specific props. Accepts standard HTML attributes.

Dialog.Title

为对话框提供无障碍标签的标题。

PropTypeDefault

No component-specific props. Accepts standard HTML attributes.

Dialog.Description

提供对话框附加上下文信息的段落。

PropTypeDefault

No component-specific props. Accepts standard HTML attributes.

Dialog.Close

点击即可关闭对话框的按钮。

PropTypeDefault

No component-specific props. Accepts standard HTML attributes.