<dialog>는 모달·대화상자를 만들기 위한 HTML 표준 요소다. showModal() 하나로 배경 비활성화, 포커스 가둠, ESC 닫기, 접근성 트리 처리까지 브라우저가 알아서 해준다. 예전엔 라이브러리로 힘겹게 구현하던 것들이 이제 기본 제공이다.
나는 모달을 직접 만들 때마다 접근성 체크리스트 앞에서 한숨을 쉬곤 했다. 포커스 트랩, aria 역할, 스크롤 잠금, ESC 처리… 이걸 매번 손으로 하다 보면 꼭 하나씩 빠뜨린다. <dialog>는 그 목록의 절반 이상을 지워준다.
기본 사용법
마크업은 놀랄 만큼 단순하다.
<dialog id="confirm">
<form method="dialog">
<h2>삭제할까요?</h2>
<p>이 작업은 되돌릴 수 없습니다.</p>
<button value="cancel">취소</button>
<button value="ok">삭제</button>
</form>
</dialog>
<script>
const d = document.getElementById('confirm')
document.querySelector('#open').onclick = () => d.showModal()
d.addEventListener('close', () => {
console.log('결과:', d.returnValue) // 'ok' 또는 'cancel'
})
</script>
method="dialog"인 폼 안의 버튼을 누르면 페이지 이동 없이 다이얼로그가 닫히고, 그 버튼의 value가 returnValue로 넘어온다. 상태 변수 없이 결과를 주고받을 수 있어 깔끔하다.
showModal()이 공짜로 해주는 것
- 백드롭과 배경 비활성화. 다이얼로그 뒤 나머지는 자동으로
inert상태가 되어 클릭·포커스가 막힌다. - 포커스 가둠. Tab이 다이얼로그 밖으로 새지 않는다. 직접 포커스 트랩을 짤 필요가 없다.
- ESC로 닫기. 별도 키 핸들러가 필요 없다.
- 접근성 역할. 브라우저가
role="dialog"와 모달 시맨틱을 부여해 스크린 리더가 올바르게 안내한다. - 최상위 레이어. z-index 지옥에서 해방된다. top layer에 렌더되어 항상 위에 뜬다.
스타일링과 백드롭
백드롭은 ::backdrop 가상 요소로 꾸민다. top layer에 있어 backdrop-filter도 잘 먹는다.
dialog::backdrop {
background: rgba(0, 0, 0, .5);
backdrop-filter: blur(2px);
}
dialog[open] {
animation: pop .18s ease-out;
}
@keyframes pop {
from { opacity: 0; transform: translateY(8px) scale(.98); }
}
놓치기 쉬운 디테일
| 주제 | 주의점 |
|---|---|
| show() vs showModal() | show()는 비모달이라 배경 비활성화·백드롭이 없음 |
| 백드롭 클릭 닫기 | 기본 미제공, 직접 구현 필요 |
| 닫힘 애니메이션 | 즉시 사라짐, allow-discrete 필요 |
| body 스크롤 | 뒤 배경 스크롤은 별도 처리 권장 |
백드롭 바깥 클릭으로 닫기는 은근히 자주 필요한데 기본 제공이 아니다. 나는 이렇게 처리한다.
d.addEventListener('click', (e) => {
const box = d.getBoundingClientRect()
const outside =
e.clientY < box.top || e.clientY > box.bottom ||
e.clientX < box.left || e.clientX > box.right
if (outside) d.close()
})
닫힘 애니메이션은 좀 까다롭다.close()하면 요소가 곧바로 사라져 트랜지션이 안 보인다.transition-behavior: allow-discrete와@starting-style을 조합하면 CSS만으로 해결되는데, 지원 범위를 감안해 자바스크립트로 잠깐 지연시키는 방식도 여전히 유효하다.
자주 묻는 질문
라이브러리 모달을 전부 <dialog>로 갈아타야 하나요?
새로 만드는 확인창·알림·간단한 폼 모달이라면 강력히 권합니다. 다만 복잡한 애니메이션 오케스트레이션이나 중첩 모달 관리가 많다면 기존 솔루션이 편할 수 있으니 상황에 맞게 선택하세요.
포커스 트랩을 정말 직접 안 짜도 되나요?
네, showModal()로 연 경우 브라우저가 포커스를 다이얼로그 안에 가둡니다. 다만 show()(비모달)로 열면 트랩이 없으니 주의하세요.
React에서도 쓸 수 있나요?
됩니다. ref로 요소를 잡고 showModal()/close()를 호출하면 됩니다. 다만 open 상태를 선언적으로 다루려면 useEffect에서 열고 닫는 동기화를 신경 써야 합니다.
중첩 모달(모달 위 모달)도 되나요?
됩니다. 각 다이얼로그가 top layer에 순서대로 쌓여 z-index 없이도 올바른 순서로 표시됩니다. 다만 UX상 중첩 모달은 되도록 피하는 편이 좋습니다.

댓글 0