Dialog

Dialog

출처: 문서

본문

현재 페이지 상태를 보존하면서 사용자에게 정보를 알려주는 다이얼로그 컴포넌트예요. 확인·취소 같은 사용자 결정이 필요하거나 추가 정보를 요구할 때 사용해요.

기본 사용 (Basic usage)

Dialog는 다이얼로그 상자를 띄우고, 상당히 커스터마이즈할 수 있어요. model-value / v-model 속성에 Boolean을 설정하면, true일 때 Dialog가 표시돼요. Dialog는 본문(body)과 푸터(footer) 두 부분으로 이루어지고, 푸터에는 footer라는 이름의 슬롯이 필요해요. 선택적인 title 속성(기본값은 빈 문자열)은 제목을 정의하는 데 써요. 마지막으로 이 예시는 before-close를 어떻게 사용하는지 보여줘요.

<template>
  <el-button @click="dialogVisible = true">open</el-button>
  <el-dialog
    v-model="dialogVisible"
    title="Tips"
    width="30%"
    :before-close="handleClose"
  >
    <span>This is a message</span>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="dialogVisible = false">Cancel</el-button>
        <el-button type="primary" @click="dialogVisible = false">Confirm</el-button>
      </span>
    </template>
  </el-dialog>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { ElMessageBox } from 'element-plus'

const dialogVisible = ref(false)

const handleClose = (done: () => void) => {
  ElMessageBox.confirm('Are you sure to close this dialog?')
    .then(() => {
      done()
    })
    .catch(() => {
      // catch error
    })
}
</script>

TIP

before-close는 사용자가 닫기 아이콘이나 배경을 클릭했을 때만 동작해요. Dialog를 닫는 버튼이 footer라는 이름의 슬롯에 있다면, 그 버튼들의 클릭 이벤트 핸들러에 before-close에서 하고 싶은 일을 추가하면 돼요.

커스텀 내용 (Customized Content)

Dialog의 내용은 무엇이든 될 수 있어요. 테이블이나 폼도 가능해요. 이 예시는 Element Plus Table과 Form을 Dialog와 함께 사용하는 방법을 보여줘요.

<template>
  <el-button @click="dialogTableVisible = true">
    Open a Table nested Dialog
  </el-button>
</template>

<script setup lang="ts">
import { reactive, ref } from 'vue'

const dialogTableVisible = ref(false)
const dialogFormVisible = ref(false)
const formLabelWidth = '140px'

const form = reactive({
  name: '',
  region: '',
  date1: '',
  date2: '',
  delivery: false,
  type: [],
  resource: '',
  desc: '',
})

const gridData = [
  {
    date: '2016-05-02',
    name: 'John Smith',
    address: 'No.1518,  Jinshajiang Road, Putuo District',
  },
  {
    date: '2016-05-04',
    name: 'John Smith',
    address: 'No.1518,  Jinshajiang Road, Putuo District',
  },
  {
    date: '2016-05-01',
    name: 'John Smith',
    address: 'No.1518,  Jinshajiang Road, Putuo District',
  },
  {
    date: '2016-05-03',
    name: 'John Smith',
    address: 'No.1518,  Jinshajiang Road, Putuo District',
  },
]
</script>

커스텀 헤더 (Customized Header)

header 슬롯을 사용해 제목이 표시되는 영역을 커스터마이즈할 수 있어요. 접근성을 유지하려면 이 슬롯을 사용하면서도 title 속성을 함께 사용하거나, titleId 슬롯 속성으로 어떤 요소를 다이얼로그 제목으로 읽어야 할지 지정해야 해요.

<template>
  <el-button @click="visible = true">Customized Header</el-button>
  <el-dialog v-model="visible" width="30%" title="title">
    <template #header="{ close, titleId, titleClass }">
      <div class="my-header">
        <h4 :id="titleId">This is a custom header!</h4>
        <el-button type="danger" :icon="CircleCloseFilled" circle plain @click="close" />
      </div>
    </template>
    <span>This is dialog content.</span>
  </el-dialog>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { CircleCloseFilled } from '@element-plus/icons-vue'

const visible = ref(false)
</script>
.my-header {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  gap: 16px;
}

중첩 Dialog (Nested Dialog)

Dialog 안에 Dialog를 중첩한다면 append-to-body가 필요해요. 보통 중첩 Dialog는 권장하지 않아요. 페이지에 여러 Dialog를 렌더링해야 한다면, 그냥 평평하게 배치해서 서로 형제(sibling)가 되게 하면 돼요. 어쩔 수 없이 Dialog 안에 Dialog를 중첩해야 한다면, 중첩된 Dialog의 append-to-body를 true로 설정하세요. 그러면 부모 노드가 아니라 body에 붙어서 두 Dialog가 올바르게 렌더링될 수 있어요.

<template>
  <el-button @click="outerVisible = true">open the outer Dialog</el-button>
  <el-dialog v-model="outerVisible" title="Outer Dialog" append-to-body>
    <el-button @click="innerVisible = true">open the inner Dialog</el-button>
    <el-dialog width="30%" v-model="innerVisible" title="Inner Dialog" append-to-body>
      <span>This is inner dialog</span>
    </el-dialog>
  </el-dialog>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const outerVisible = ref(false)
const innerVisible = ref(false)
</script>

중앙 정렬된 내용 (Centered content)

Dialog의 내용을 중앙에 정렬할 수 있어요. center를 true로 설정하면 dialog의 헤더와 푸터가 가로로 중앙 정렬돼요. center는 Dialog의 헤더와 푸터에만 영향을 줘요. Dialog의 본문은 무엇이든 될 수 있기 때문에 중앙 정렬했을 때 보기 좋지 않을 수도 있어요. 본문도 중앙에 정렬하고 싶다면 CSS를 직접 작성해야 해요.

<template>
  <el-button @click="centerDialogVisible = true">Centered Dialog</el-button>
  <el-dialog
    v-model="centerDialogVisible"
    title="Tips"
    width="30%"
    align-center
    center
  >
    <span>This is a message</span>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="centerDialogVisible = false">Cancel</el-button>
        <el-button type="primary" @click="centerDialogVisible = false">Confirm</el-button>
      </span>
    </template>
  </el-dialog>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const centerDialogVisible = ref(false)
</script>

TIP

Dialog의 내용은 지연 렌더링돼요. 즉 default 슬롯은 처음 열리기 전까지 DOM에 렌더링되지 않아요. 그래서 DOM 조작을 하거나 ref로 컴포넌트에 접근해야 한다면 open 이벤트 콜백에서 하세요.

중앙 정렬 Dialog (Align Center dialog)

화면 중앙에서 Dialog를 엽니다. align-center를 true로 설정하면 dialog를 가로와 세로 모두 중앙에 정렬해요. dialog가 flexbox에서 세로로 중앙 정렬되기 때문에 top prop은 동시에 동작하지 않아요.

닫을 때 파괴 (Destroy on Close)

이 기능이 활성화되면 default 슬롯 아래의 내용이 v-if 디렉티브로 파괴돼요. 성능이 걱정될 때 이 기능을 활성화하세요. 이 기능을 활성화하면 콘텐츠가 transition.beforeEnter가 발송되기 전에는 렌더링되지 않고, 오버레이 헤더(있으면)와 푸터(있으면)만 남는다는 점에 유의하세요.

드래그 가능한 Dialog (Draggable Dialog)

헤더 부분을 드래그해 보세요. draggable을 true로 설정하면 드래그할 수 있어요. 2.5.4의 overflow를 true로 설정하면 뷰포트 밖으로 드래그할 수 있어요.

<template>
  <div>
    <el-button @click="dialogVisible = true">Open a draggable Dialog</el-button>
    <el-dialog v-model="dialogVisible" title="Draggable Dialog" draggable>
      <span>It's a draggable Dialog</span>
      <template #footer>
        <el-button @click="dialogVisible = false">Cancel</el-button>
        <el-button type="primary" @click="dialogVisible = false">Confirm</el-button>
      </template>
    </el-dialog>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const dialogVisible = ref(false)
const dialogOverflowVisible = ref(false)
const customDraggingVisible = ref(false)
</script>
:global(.custom-dragging-style.is-dragging) {
  border: 2px dashed var(--el-color-primary);
  opacity: 0.65;
}

TIP

modal = false를 사용할 때는 append-to-body를 true로 설정했는지 확인해 주세요. Dialog가 position: relative로 위치하기 때문에, modal이 제거되면 Dialog는 Document.Body가 아니라 DOM에서의 현재 위치를 기준으로 위치하게 되어 스타일이 깨질 수 있어요.

전체 화면 (Fullscreen)

fullscreen 속성을 설정해 전체 화면 dialog를 열 수 있어요.

TIP

fullscreen이 true이면 width와 top, draggable 속성은 동작하지 않아요.

modal을 false로 설정하면 dialog의 modal(오버레이)을 숨길 수 있어요. 2.10.5 버전부터 modal-penetrable 속성이 추가되었는데, 이 속성으로 modal을 관통(penetrable)하게 할 수 있어요.

커스텀 애니메이션 (Custom Animation) 2.10.5

transition 속성으로 dialog 애니메이션을 커스터마이즈할 수 있어요. 이 속성은 다음을 받아요:

  • 전환 이름(Transition name, 문자열)
  • Vue transition 설정(객체)

예시로 scale, slide, fade, bounce 애니메이션과 커스텀 이벤트 핸들러가 있는 객체 기반 설정이 있어요.

<template>
  <el-dialog
    v-model="dialogVisible"
    :transition="transitionConfig"
    title="transition"
  >
    <span>
      Current animation: {{ currentAnimation }}
      <code v-if="isObjectConfig">{{ JSON.stringify(transitionConfig, null, 2) }}</code>
    </span>
  </el-dialog>
</template>

<script setup lang="ts">
import { computed, ref } from 'vue'

import type { DialogTransition } from 'element-plus'

const dialogVisible = ref(false)
const currentAnimation = ref('fade')
const isObjectConfig = ref(false)

const transitionConfig = computed(() => {
  if (isObjectConfig.value) {
    return {
      name: 'dialog-custom-object',
      appear: true,
      mode: 'out-in',
      duration: 500,
    }
  }
  return `dialog-${currentAnimation.value}`
})

const openDialog = (type: string) => {
  currentAnimation.value = type
  isObjectConfig.value = false
  dialogVisible.value = true
}

const openDialogWithObject = () => {
  currentAnimation.value = 'object-config'
  isObjectConfig.value = true
  dialogVisible.value = true
}
</script>
code {
  background: var(--el-bg-color-page);
  padding: 4px 8px;
  border-radius: 4px;
  font-size: 12px;
  display: block;
  margin-top: 8px;
}

/* Scale Animation */
.dialog-scale-enter-active,
.dialog-scale-leave-active,
.dialog-scale-enter-active .el-dialog,
.dialog-scale-leave-active .el-dialog {
  transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
}

.dialog-scale-enter-from,
.dialog-scale-leave-to {
  opacity: 0;
}

.dialog-scale-enter-from .el-dialog,
.dialog-scale-leave-to .el-dialog {
  transform: scale(0.5);
  opacity: 0;
}

/* Slide Animation */
.dialog-slide-enter-active,
.dialog-slide-leave-active,
.dialog-slide-enter-active .el-dialog,
.dialog-slide-leave-active .el-dialog {
  transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.dialog-slide-enter-from,
.dialog-slide-leave-to {
  opacity: 0;
}

.dialog-slide-enter-from .el-dialog,
.dialog-slide-leave-to .el-dialog {
  transform: translateY(-100px);
  opacity: 0;
}

/* Bounce Animation */
.dialog-bounce-enter-active,
.dialog-bounce-leave-active,
.dialog-bounce-enter-active .el-dialog,
.dialog-bounce-leave-active .el-dialog {
  transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

.dialog-bounce-enter-from,
.dialog-bounce-leave-to {
  opacity: 0;
}

.dialog-bounce-enter-from .el-dialog,
.dialog-bounce-leave-to .el-dialog {
  transform: scale(0.3) translateY(-50px);
  opacity: 0;
}

/* Object Configuration Animation */
.dialog-custom-object-enter-active,
.dialog-custom-object-leave-active,
.dialog-custom-object-enter-active .el-dialog,
.dialog-custom-object-leave-active .el-dialog {
  transition: all 0.5s cubic-bezier(0.25, 0.8, 0.25, 1);
}

.dialog-custom-object-enter-from,
.dialog-custom-object-leave-to {
  opacity: 0;
}

.dialog-custom-object-enter-from .el-dialog,
.dialog-custom-object-leave-to .el-dialog {
  transform: rotate(180deg) scale(0.5);
  opacity: 0;
}

TIP

애니메이션 클래스는 transition 이름을 기반으로 동적으로 생성돼요. 애니메이션 동작을 세밀하게 제어하려면 이 클래스들을 명시적으로 정의할 수 있어요. 자세한 내용은 custom-transition-classes를 참고하세요.

이벤트 (Events)

개발자 콘솔(ctrl + shift + J)을 열어 이벤트 순서를 확인해 보세요.

API

Attributes

Name Description Type Default
model-value / v-model visibility of Dialog boolean false
title title of Dialog. Can also be passed with a named slot (see the following table) string ''
width width of Dialog, default is 50% string / number ''
fullscreen whether the Dialog takes up full screen boolean false
top value for margin-top of Dialog CSS, default is 15vh string ''
modal whether a mask is displayed boolean true
modal-penetrable 2.10.5 whether the mask is penetrable. The modal attribute must be false. boolean false
modal-class custom class names for mask string —
header-class 2.9.3 custom class names for header wrapper string —
body-class 2.9.3 custom class names for body wrapper string —
footer-class 2.9.3 custom class names for footer wrapper string —
append-to-body whether to append Dialog itself to body. A nested Dialog should have this attribute set to true boolean false
append-to 2.4.3 which element the Dialog appends to. Will override append-to-body CSSSelector / HTMLElement body
lock-scroll whether scroll of body is disabled while Dialog is displayed boolean true
open-delay the Time(milliseconds) before open number 0
close-delay the Time(milliseconds) before close number 0
close-on-click-modal whether the Dialog can be closed by clicking the mask boolean true
close-on-press-escape whether the Dialog can be closed by pressing ESC boolean true
show-close whether to show a close button boolean true
before-close callback before Dialog closes, and it will prevent Dialog from closing, use done to close the dialog Function —
draggable enable dragging feature for Dialog boolean false
overflow 2.5.4 draggable Dialog can overflow the viewport boolean false
center whether to align the header and footer in center boolean false
align-center 2.2.16 whether to align the dialog both horizontally and vertically boolean false
destroy-on-close destroy elements in Dialog when closed boolean false
close-icon custom close icon, default is Close string / Component —
z-index same as z-index in native CSS, z-order of dialog number —
header-aria-level a11y header's aria-level attribute string 2
transition 2.10.5 custom transition configuration for dialog animation. Can be a string (transition name) or an object with Vue transition props string / object dialog-fade
custom-class deprecated custom class names for Dialog string ''

WARNING

custom-class는 deprecated 되었고 2.4.0에서 제거될 예정이에요. class를 사용해 주세요.

Slots

Name Description
default default content of Dialog
header content of the Dialog header; Replacing this removes the title, but does not remove the close button.
footer content of the Dialog footer
title deprecated works the same as the header slot. Use that instead.

WARNING

title은 deprecated 되었고 3.0.0에서 제거될 예정이에요. header를 사용해 주세요.

Events

Name Description Type
open triggers when the Dialog opens Function
opened triggers when the Dialog opening animation ends Function
close triggers when the Dialog closes Function
closed triggers when the Dialog closing animation ends Function
open-auto-focus triggers after Dialog opens and content focused Function
close-auto-focus triggers after Dialog closed and content focused Function

Exposes

Name Description Type
resetPosition 2.8.1 reset position Function
handleClose 2.9.8 close dialog Function

FAQ

SFC에서 dialog를 사용하면 scope style이 적용되지 않아요

전형적인 이슈: #10515 참고: dialog는 Teleport를 사용해 렌더링되므로, 루트 노드의 스타일은 전역으로 작성하는 것이 좋아요.

dialog가 표시되고 숨겨질 때 페이지 요소가 앞뒤로 밀리는 상황이 생겨요

전형적인 이슈: #10481 참고: 스크롤 영역을 vue mounted 노드 안(예: <div id="app" />)에 두고, body에는 overflow: hidden 스타일을 사용하는 것이 좋아요.

더 알아보기 (Learn more)