---
title: 确认窗 Dialog
url: https://ui.zhaozimin.cn/components/dialog/
markdown: https://ui.zhaozimin.cn/components/dialog.md
group: 组件
origin: 提炼层组件（src/extracted），本仓库是唯一源头
source:
  - src/extracted/ConfirmDialog.jsx  # https://ui.zhaozimin.cn/src/extracted/ConfirmDialog.jsx
  - src/system/styles/v4/controls.css  # https://ui.zhaozimin.cn/src/system/styles/v4/controls.css
  - src/extracted/extracted.css  # https://ui.zhaozimin.cn/src/extracted/extracted.css
version: v0.2.0  # 设计系统版本，发布于 2026-09-23
scope: .zzm-v4   # 根节点必须带这个类，否则样式不生效
css: https://ui.zhaozimin.cn/v/0.2.0/zzm.css   # 锁版本地址，本仓库再改也不影响你
---

# 确认窗 Dialog

> 纸面卡片、宽 440、圆角 16，遮罩只降亮不模糊——背景仍能认出正在操作什么。朱键只用于破坏性确认。

### 样张 · 真实弹出

Esc 或点遮罩＝取消；打开时焦点落在确认键，关闭后归还

React：

```jsx
import { useState } from 'react'
import { ConfirmProvider, useConfirm } from './extracted/ConfirmDialog.jsx'
import { Btn } from './components/v4/Button.jsx'

function Actions() {
  const confirm = useConfirm()
  const [result, setResult] = useState('还没有操作')
  const remove = async () => {
    const ok = await confirm({
      title: '确认删除这条笔记？',
      description: '此操作不可撤销。',
      tone: 'destructive',
      confirmText: '删除',
    })
    setResult(ok ? '已删除' : '已取消')
  }
  const publish = async () => {
    const ok = await confirm({ title: '发布这节课？', description: '发布后学员立即可见。', confirmText: '发布' })
    setResult(ok ? '已发布' : '已取消')
  }
  return (
    <div style={{ display: 'flex', gap: 12, alignItems: 'center', flexWrap: 'wrap' }}>
      <Btn variant="accent" onClick={remove}>删除笔记</Btn>
      <Btn onClick={publish}>发布课节</Btn>
      <span style={{ fontSize: 12, color: '#8B8B85' }}>{result}</span>
    </div>
  )
}

export default function DialogDemo() {
  return (
    <ConfirmProvider>
      <Actions />
    </ConfirmProvider>
  )
}
```

HTML（构建时由上面的 React 渲染得到，可直接粘贴）：

```html
<div style="display: flex; gap: 12px; align-items: center; flex-wrap: wrap">
  <button type="button" class="zzm-btn zzm-btn--accent">删除笔记</button>
  <button type="button" class="zzm-btn">发布课节</button>
  <span style="font-size: 12px; color: #8b8b85">还没有操作</span>
</div>
```

### 样张 · 破坏性确认

面板本身（inline 陈列）

React：

```jsx
import { ConfirmPanel } from './extracted/ConfirmDialog.jsx'

export function Destructive() {
  return (
    <ConfirmPanel
      inline
      title="确认删除这条笔记？"
      description="此操作不可撤销。确认窗保留操作上下文，不让浏览器原生灰框打断视觉语言。"
      tone="destructive"
      confirmText="删除"
    />
  )
}
```

HTML（构建时由上面的 React 渲染得到，可直接粘贴）：

```html
<div
  role="alertdialog"
  aria-labelledby="zzm-dialog-title"
  aria-describedby="zzm-dialog-desc"
  class="zzm-dialog-panel zzm-dialog-panel--inline"
>
  <div class="zzm-dialog-header">
    <h2 id="zzm-dialog-title" class="zzm-dialog-title">确认删除这条笔记？</h2>
    <p id="zzm-dialog-desc" class="zzm-dialog-description">
      此操作不可撤销。确认窗保留操作上下文，不让浏览器原生灰框打断视觉语言。
    </p>
  </div>
  <div class="zzm-dialog-actions">
    <button type="button" class="zzm-btn zzm-btn--sm">取消</button>
    <button type="button" class="zzm-btn zzm-btn--sm zzm-btn--accent">删除</button>
  </div>
</div>
```

### 样张 · 普通确认

React：

```jsx
import { ConfirmPanel } from './extracted/ConfirmDialog.jsx'

export function Normal() {
  return <ConfirmPanel inline title="发布这节课？" description="发布后学员立即可见。" confirmText="发布" />
}
```

HTML（构建时由上面的 React 渲染得到，可直接粘贴）：

```html
<div
  role="alertdialog"
  aria-labelledby="zzm-dialog-title"
  aria-describedby="zzm-dialog-desc"
  class="zzm-dialog-panel zzm-dialog-panel--inline"
>
  <div class="zzm-dialog-header">
    <h2 id="zzm-dialog-title" class="zzm-dialog-title">发布这节课？</h2>
    <p id="zzm-dialog-desc" class="zzm-dialog-description">发布后学员立即可见。</p>
  </div>
  <div class="zzm-dialog-actions">
    <button type="button" class="zzm-btn zzm-btn--sm">取消</button>
    <button type="button" class="zzm-btn zzm-btn--sm zzm-btn--primary">发布</button>
  </div>
</div>
```

## useConfirm()

与 edu 的 `useDialog().confirm` 同一个形状：在根部挂一次 `ConfirmProvider`，任何地方 `await confirm({...})` 拿到 `true / false`。

| 属性 | 取值 | 默认 | 说明 |
|---|---|---|---|
| `title` | string | — | 标题（17px / 700） |
| `description` | string | — | 说明（13px / 20，灰） |
| `tone` | 'default' · 'primary' · 'destructive' | 'default' | 确认键：墨键，破坏性用朱键 |
| `confirmText` | string | '确认' | 确认键文字，写动作本身（「删除」而不是「确定」） |
| `cancelText` | string | '取消' | 取消键文字 |

## 从 edu 到这里改了什么

edu 的确认窗建在 shadcn `dialog.jsx` 与 Tailwind 工具类上。这里去掉了这两层依赖：皮肤仍是 `controls.css` 的 `.zzm-dialog-*`，定位与入场由 `extracted.css` 用纯 CSS 补齐，所以不装 Tailwind 也能 1:1 成立。

## 铁律

- ✓ 要：朱键只给「删除、作废、注销」这类破坏性确认
- ✗ 不要：用 `window.confirm()` 原生灰框
- ✓ 要：遮罩 `rgba(22,22,22,.46)`，只降亮
- ✗ 不要：给遮罩加模糊

## 本页用到的 CSS

按样张里出现的类名，从源文件原样裁出（完整版见 zzm.css）：

```css
/* controls.css */

.zzm-v4 .zzm-btn {
  -webkit-appearance: none;
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 36px;
  padding: 0 16px;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .2px;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  border-radius: 8px;                                   /* 圆角矩形——按钮禁胶囊 */
  border: 1px solid rgba(0, 0, 0, .14);
  color: #3F3F3B;
  background: linear-gradient(180deg, #FFFFFF 0%, #F2F2EF 100%);
  box-shadow:
    0 0 0 3px rgba(255, 255, 255, .55),                 /* ④ 反光光晕环 */
    0 1px 2px rgba(0, 0, 0, .09),                       /* ③ 近影 */
    0 2px 6px rgba(0, 0, 0, .06),                       /* ③ 远影 */
    inset 0 1px 0 rgba(255, 255, 255, .92),             /* ② 顶部反光 */
    inset 0 -1px 0 rgba(0, 0, 0, .05);                  /* ① 底缘收沉 */
  transition: box-shadow .16s ease, transform .16s ease, background .16s ease,
    border-color .16s ease, filter .16s ease, opacity .16s ease;
}

.zzm-v4 .zzm-btn:hover {
  transform: translateY(-1px);
  border-color: rgba(0, 0, 0, .18);
  box-shadow:
    0 0 0 4px rgba(255, 255, 255, .8),
    0 2px 4px rgba(0, 0, 0, .10),
    0 4px 12px rgba(0, 0, 0, .09),
    inset 0 1px 0 rgba(255, 255, 255, .95),
    inset 0 -1px 0 rgba(0, 0, 0, .05);
}

.zzm-v4 .zzm-btn:active {
  transform: translateY(0);
  background: linear-gradient(180deg, #ECECE9 0%, #F4F4F1 100%);   /* ⑤ 凹面翻转 */
  box-shadow:
    0 0 0 3px rgba(255, 255, 255, .45),
    0 1px 0 rgba(255, 255, 255, .65),
    inset 0 2px 4px rgba(0, 0, 0, .13),
    inset 0 1px 2px rgba(0, 0, 0, .09);
}

.zzm-v4 .zzm-btn:focus-visible {
  outline: none;
  box-shadow:
    0 0 0 3px rgba(166, 64, 47, .20),                   /* 键盘焦点＝朱色光晕 */
    0 1px 2px rgba(0, 0, 0, .09),
    0 2px 6px rgba(0, 0, 0, .06),
    inset 0 1px 0 rgba(255, 255, 255, .92);
}

.zzm-v4 .zzm-btn:disabled {
  cursor: not-allowed;
  transform: none;
}

.zzm-v4 .zzm-btn:disabled:not(.zzm-btn--busy) {
  color: #A2A29C;
  background: #F2F2EF;
  border-color: rgba(0, 0, 0, .08);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .7), 0 1px 2px rgba(0, 0, 0, .04);
}

.zzm-v4 .zzm-btn--primary {
  color: #FFFFFF;
  border-color: rgba(0, 0, 0, .85);
  background: linear-gradient(180deg, #3A3A36 0%, #1D1D1B 58%, #141412 100%);
  box-shadow:
    0 0 0 3px rgba(22, 22, 22, .06),
    0 1px 2px rgba(0, 0, 0, .18),
    0 3px 10px rgba(22, 22, 22, .24),
    inset 0 1px 0 rgba(255, 255, 255, .20),
    inset 0 -1px 0 rgba(0, 0, 0, .38);
}

.zzm-v4 .zzm-btn--primary:hover {
  filter: brightness(1.12);
  border-color: rgba(0, 0, 0, .85);
  box-shadow:
    0 0 0 4px rgba(22, 22, 22, .09),
    0 2px 4px rgba(0, 0, 0, .20),
    0 5px 14px rgba(22, 22, 22, .28),
    inset 0 1px 0 rgba(255, 255, 255, .24),
    inset 0 -1px 0 rgba(0, 0, 0, .38);
}

.zzm-v4 .zzm-btn--primary:active {
  filter: none;
  background: linear-gradient(180deg, #101010 0%, #22221F 100%);
  box-shadow:
    0 0 0 3px rgba(22, 22, 22, .06),
    inset 0 2px 5px rgba(0, 0, 0, .55),
    inset 0 1px 2px rgba(0, 0, 0, .4);
}

.zzm-v4 .zzm-btn--accent {
  color: #FFFFFF;
  border-color: rgba(122, 42, 29, .9);
  background: linear-gradient(180deg, #C25743 0%, #A6402F 56%, #99392A 100%);
  box-shadow:
    0 0 0 3px rgba(166, 64, 47, .10),                   /* 朱色反光光晕 */
    0 1px 2px rgba(0, 0, 0, .14),
    0 3px 10px rgba(166, 64, 47, .28),
    inset 0 1px 0 rgba(255, 255, 255, .32),
    inset 0 -1px 0 rgba(0, 0, 0, .20);
}

.zzm-v4 .zzm-btn--accent:hover {
  filter: brightness(1.06);
  border-color: rgba(122, 42, 29, .9);
  box-shadow:
    0 0 0 4px rgba(166, 64, 47, .16),
    0 2px 4px rgba(0, 0, 0, .16),
    0 5px 14px rgba(166, 64, 47, .34),
    inset 0 1px 0 rgba(255, 255, 255, .36),
    inset 0 -1px 0 rgba(0, 0, 0, .20);
}

.zzm-v4 .zzm-btn--accent:active {
  filter: none;
  background: linear-gradient(180deg, #8E3425 0%, #A03D2C 100%);
  box-shadow:
    0 0 0 3px rgba(166, 64, 47, .10),
    inset 0 2px 5px rgba(0, 0, 0, .34),
    inset 0 1px 2px rgba(0, 0, 0, .24);
}

.zzm-v4 .zzm-btn--sm { height: 28px; padding: 0 12px; font-size: 12px; gap: 4px; }

.zzm-v4 .zzm-btn--icon.zzm-btn--sm { width: 28px; }

.zzm-v4 .zzm-dialog-panel {
  width: min(calc(100% - 32px), 440px);
  max-width: 440px;
  gap: 16px;
  padding: 24px;
  border: 1px solid rgba(0, 0, 0, .14);
  border-radius: 16px;
  background: #FFFFFF;
  box-shadow:
    0 1px 2px rgba(0, 0, 0, .10),
    0 18px 48px rgba(0, 0, 0, .22),
    inset 0 1px 0 rgba(255, 255, 255, .92);
}

.zzm-v4 .zzm-dialog-title {
  color: #1D1D1B;
  font-size: 17px;
  font-weight: 700;
  line-height: 24px;
}

.zzm-v4 .zzm-dialog-description {
  color: #6E6E68;
  font-size: 13px;
  line-height: 20px;
}

.zzm-v4 .zzm-dialog-actions {
  margin-top: 4px;
  gap: 8px;
}

/* extracted.css */

.zzm-v4 .zzm-dialog-panel {
  position: fixed; top: 50%; left: 50%; z-index: 51; display: grid;
  transform: translate(-50%, -50%); animation: zzmDialogIn .2s cubic-bezier(.2, .7, .2, 1) both;
}

.zzm-v4 .zzm-dialog-panel--inline { position: relative; top: auto; left: auto; z-index: auto; transform: none; animation: none; }

.zzm-v4 .zzm-dialog-header { display: grid; gap: 8px; }

.zzm-v4 .zzm-dialog-title,
.zzm-v4 .zzm-dialog-description { margin: 0; }

.zzm-v4 .zzm-dialog-actions { display: flex; justify-content: flex-end; }

/* keyframes */
@keyframes zzmDialogIn {
  from { opacity: 0; transform: translate(-50%, -50%) scale(.96) }
  to   { opacity: 1; transform: translate(-50%, -50%) }
}
```

---

上一页：[分页](https://ui.zhaozimin.cn/components/pagination.md) · 下一页：[轻提示](https://ui.zhaozimin.cn/components/toast.md) · 全站目录：https://ui.zhaozimin.cn/llms.txt
