---
title: Drawer 抽屉
description: 从屏幕边缘滑出的模态面板
category: Feedback
status: beta
since: 0.3.4
tags: [drawer, panel, overlay]
syncStatus: reviewed
verified: true
---

## 用法

### 基础
打开上层确认框后，Tab 留在确认框内，Escape 只关闭确认框。
:::TuffDemoWrapper{demo="DrawerBasicDrawerDemo" code-lang="vue"}
---
code: |
  <template>
    <TxButton @click="visible = true">打开抽屉</TxButton>
    <TxDrawer v-model:visible="visible" title="设置">
      <p>这是抽屉的内容区域。</p>
      <TxButton @click="confirmVisible = true">打开确认框</TxButton>
    </TxDrawer>
    <TxModal v-model="confirmVisible" title="确认操作">
      <template #footer>
        <TxButton @click="confirmVisible = false">取消</TxButton>
      </template>
    </TxModal>
  </template>
---
:::

### 方向
`direction` 支持四个方向；左右时 `size` 是宽度，上下时是高度。
:::TuffDemoWrapper{demo="DrawerDirectionDemo" code-lang="vue"}
---
code: |
  <template>
    <TxDrawer v-model:visible="left" title="左侧抽屉" direction="left" size="360px" :mobile-adapt="false" />
    <TxDrawer v-model:visible="right" title="右侧抽屉" direction="right" size="360px" :mobile-adapt="false" />
    <TxDrawer v-model:visible="top" title="顶部抽屉" direction="top" size="18rem" :mobile-adapt="false" />
    <TxDrawer v-model:visible="bottom" title="底部抽屉" direction="bottom" size="45%" />
  </template>
---
:::

### 尺寸与全屏
`size` 接受数字（px）、CSS 长度、百分比或 `'full'`；`full` 等价于 `size="full"`。
:::TuffDemoWrapper{demo="DrawerCustomWidthDemo" code-lang="vue"}
---
code: |
  <template>
    <TxDrawer v-model:visible="px" title="size = 400px" size="400px" :mobile-adapt="false" />
    <TxDrawer v-model:visible="rem" title="size = 24rem" size="24rem" :mobile-adapt="false" />
    <TxDrawer v-model:visible="percent" title="bottom + size = 55%" direction="bottom" size="55%" />
    <TxDrawer v-model:visible="fullscreen" title="全屏抽屉" full />
  </template>
---
:::

### 头尾插槽与遮罩
`header`、`footer` 插槽接收 `close`；`maskEffect` 设置遮罩效果，`panelTransparent` 让面板透出背部内容。
:::TuffDemoWrapper{demo="DrawerSlotsEffectsDemo" code-lang="vue"}
---
code: |
  <template>
    <TxDrawer
      v-model:visible="visible"
      title="发布策略"
      size="min(460px, 92vw)"
      mask-effect="opacity"
      panel-transparent
    >
      <template #header="{ close }">
        <strong>发布策略</strong>
        <TxButton variant="ghost" @click="close">关闭</TxButton>
      </template>

      <p>抽屉主内容</p>

      <template #footer="{ close }">
        <TxButton @click="close">取消</TxButton>
        <TxButton type="primary" @click="close">保存</TxButton>
      </template>
    </TxDrawer>
  </template>
---
:::

### 表单
不需要 `close` 时，`footer` 插槽可以直接改写外部状态。

:::TuffCodeBlock{lang="vue"}
---
code: |
  <template>
    <TxDrawer v-model:visible="visible" title="表单">
      <form>
        <input type="text" placeholder="姓名" />
      </form>

      <template #footer>
        <TxButton @click="visible = false">取消</TxButton>
        <TxButton type="primary" @click="handleSave">保存</TxButton>
      </template>
    </TxDrawer>
  </template>
---
:::

### 关闭行为

:::TuffCodeBlock{lang="vue"}
---
code: |
  <template>
    <!-- 点击遮罩不关闭 -->
    <TxDrawer v-model:visible="visible" title="持久化" :close-on-click-mask="false">
      <p>只能通过关闭按钮关闭</p>
    </TxDrawer>

    <!-- Escape 不关闭 -->
    <TxDrawer v-model:visible="visible2" title="禁用 Escape" :close-on-press-escape="false">
      <p>Escape 键不会关闭此抽屉</p>
    </TxDrawer>
  </template>
---
:::

### 开关事件

:::TuffCodeBlock{lang="vue"}
---
code: |
  <template>
    <TxDrawer v-model:visible="visible" title="事件示例" @open="handleOpen" @close="handleClose">
      <p>内容</p>
    </TxDrawer>
  </template>
---
:::

### 后台导航
Tabs 固定一级分区，DropdownMenu 承载轻量操作，Popover 放短说明，高密度配置放进 Drawer。
:::TuffDemoWrapper{demo="ComponentsNavigationShellDemo" code-lang="vue"}
---
code: |
  <template>
    <TxDropdownMenu>
      <template #trigger>
        <TxButton>发布操作</TxButton>
      </template>
      <TxDropdownItem>快速发布</TxDropdownItem>
    </TxDropdownMenu>

    <TxPopover>
      <template #reference>
        <TxButton variant="secondary">策略说明</TxButton>
      </template>
      浮层只放短说明和轻量动作。
    </TxPopover>

    <TxTabs v-model="active" placement="left" indicator-variant="pill">
      <TxTabItem name="总览" activation>总览配置</TxTabItem>
      <TxTabItem name="发布">发布配置</TxTabItem>
    </TxTabs>

    <TxDrawer v-model:visible="drawerVisible" title="发布策略" />
  </template>
---
:::

### 最佳实践

- 长表单、审计明细、权限矩阵和需要 Footer 操作区的流程放进 Drawer，不要塞进 Popover。
- 即使 `showHeader=false` 也提供有意义的 `title`，它是对话框的无障碍名称。
- 用 `size` 或 `full` 设置尺寸；`width` 只为旧调用点保留。
- 只有在小屏上保持侧向进入比底部弹出更重要时，才设置 `mobileAdapt=false`。
- 焦点由抽屉管理，外层不要再手动抢焦点；正文的表单字段仍需明确的 label。

## API 参考

### 属性

::TuffPropsTable
---
rows:
  - name: visible
    description: '是否显示，配合 `v-model:visible` 使用。'
    type: 'boolean'
    default: '*必填*'
  - name: title
    description: '标题；不渲染 Header 时作为 `aria-label`。'
    type: 'string'
    default: "'Drawer'"
  - name: size
    description: '当前轴尺寸：左右为宽度，上下为高度；数字按 px，`full` 为 100%。'
    type: "number | string | 'full'"
    default: "'60%'"
  - name: full
    description: '在当前方向全屏打开，等价于 `size="full"`。'
    type: 'boolean'
    default: 'false'
  - name: width
    description: '旧版兼容项，改用 `size`。'
    type: "number | string | 'full'"
    default: '-'
  - name: direction
    description: '滑出方向。'
    type: "'left' | 'right' | 'top' | 'bottom'"
    default: "'right'"
  - name: showHeader
    description: '渲染 Header 区域。'
    type: 'boolean'
    default: 'true'
  - name: showFooter
    description: '渲染 Footer 插槽区域。'
    type: 'boolean'
    default: 'true'
  - name: showClose
    description: '在默认 Header 中显示关闭按钮。'
    type: 'boolean'
    default: 'true'
  - name: closeOnClickMask
    description: '点击遮罩时关闭。'
    type: 'boolean'
    default: 'true'
  - name: closeOnPressEscape
    description: '按 Escape 时关闭。'
    type: 'boolean'
    default: 'true'
  - name: maskEffect
    description: '遮罩效果：模糊、仅变暗或透明。'
    type: "'blur' | 'opacity' | 'transparent'"
    default: "'blur'"
  - name: panelTransparent
    description: '面板半透明，透出背部内容。'
    type: 'boolean'
    default: 'false'
  - name: mobileAdapt
    description: '视口宽度 ≤ 768px 时强制从底部弹出。'
    type: 'boolean'
    default: 'true'
  - name: zIndex
    description: '固定层级；不传时由 z-index manager 从 `10000` 起分配。'
    type: 'number'
    default: '-'
  - name: lazy
    description: '首次打开前不渲染插槽内容，之后一直保留；需要提前挂载时设为 `false`。'
    type: 'boolean'
    default: 'true'
---
::

### 事件

::TuffPropsTable
---
rows:
  - name: update:visible
    description: '可见性变化时触发。'
    type: '(visible: boolean) => void'
    default: '-'
  - name: open
    description: '`visible` 变为 `true` 时触发。'
    type: '() => void'
    default: '-'
  - name: close
    description: '用户关闭时触发；父级改写 `visible` 不触发。'
    type: '() => void'
    default: '-'
---
::

### 插槽

::TuffPropsTable
---
rows:
  - name: default
    description: '主内容。'
    type: '-'
    default: '-'
  - name: header
    description: '替换默认标题与关闭按钮；slot props: `{ close, title, titleId }`。'
    type: '-'
    default: '-'
  - name: footer
    description: '底部操作区；slot props: `{ close }`。'
    type: '-'
    default: '-'
---
::

## 概述

- 根节点为 `role="dialog"`、`aria-modal="true"`；渲染 Header 时用 `aria-labelledby` 关联标题，否则以 `title` 作为 `aria-label`。
- 打开时聚焦抽屉，Tab 在抽屉内循环；关闭或卸载时焦点回到打开前的元素。
- 关闭按钮、遮罩与 Escape 都派发 `update:visible(false)` 与 `close`；`closeOnClickMask`、`closeOnPressEscape` 分别关闭后两条路径。
- 只有最上层的模态对话框响应 Tab 与 Escape，已被其他控件处理的按键会被忽略，因此上层确认框独占这些按键。
- 关闭后根节点仍留在 DOM 中，并设置 `inert` 与 `aria-hidden`。

## 技术实现

- 头尾分隔线复用 `TxDivider`，不要再为它们硬编码边框。
- 源码：`packages/tuffex/packages/components/src/drawer/`。

<TuffDocSourceLink />
