Contents
see List개요
HTML의 네이티브 <dialog> 요소와 Popover API는 JavaScript 라이브러리 없이 모달, 툴팁, 드롭다운을 구현할 수 있게 합니다. 2024년부터 모든 주요 브라우저에서 안정적으로 지원되며, 접근성, 키보드 내비게이션, 포커스 관리가 내장되어 있습니다. 수십 KB의 모달 라이브러리를 대체할 수 있는 웹 표준 솔루션입니다.
핵심 개념
<dialog> 요소는 HTML5 표준 모달/비모달 대화상자입니다. showModal() 메서드는 모달 모드로, show()는 비모달 모드로 열립니다. 모달 모드에서는 자동으로 배경이 비활성화되고 ESC 키로 닫을 수 있습니다.
Popover API는 툴팁, 드롭다운, 토스트 알림 등 경량 오버레이 UI를 위한 API입니다. popover 속성만으로 자동 표시/숨김, 스택 관리, Light Dismiss(바깥 클릭 시 닫기)가 처리됩니다.
::backdrop 의사 요소는 모달의 배경 오버레이를 스타일링합니다.
실전 예제
<dialog> 요소로 모달을 구현합니다.
<button id="open-dialog">모달 열기</button>
<dialog id="my-dialog">
<form method="dialog">
<h2>설정</h2>
<p>여기에 설정 옵션이 표시됩니다.</p>
<label for="username">사용자명</label>
<input type="text" id="username" name="username">
<div class="dialog-actions">
<button type="button" id="cancel">취소</button>
<button type="submit" value="save">저장</button>
</div>
</form>
</dialog>
dialog {
max-width: 500px;
border: none;
border-radius: 8px;
padding: 2rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
/* 모달 배경 오버레이 */
dialog::backdrop {
background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
}
/* 오픈 애니메이션 */
dialog[open] {
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.dialog-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
margin-top: 1.5rem;
}
const dialog = document.getElementById('my-dialog');
const openBtn = document.getElementById('open-dialog');
const cancelBtn = document.getElementById('cancel');
openBtn.addEventListener('click', () => {
dialog.showModal(); // 모달 모드로 열기
});
cancelBtn.addEventListener('click', () => {
dialog.close(); // 닫기
});
// 폼 제출 처리
dialog.addEventListener('close', () => {
if (dialog.returnValue === 'save') {
console.log('저장 버튼 클릭됨');
const formData = new FormData(dialog.querySelector('form'));
console.log('사용자명:', formData.get('username'));
}
});
// 배경 클릭 시 닫기
dialog.addEventListener('click', (e) => {
if (e.target === dialog) {
dialog.close();
}
});
Popover API로 드롭다운 메뉴를 구현합니다.
<button popovertarget="menu">메뉴 열기</button>
<div id="menu" popover>
<ul>
<li><a href="/profile">프로필</a></li>
<li><a href="/settings">설정</a></li>
<li><a href="/logout">로그아웃</a></li>
</ul>
</div>
[popover] {
margin: 0;
padding: 0.5rem 0;
border: 1px solid #e5e7eb;
border-radius: 8px;
background: white;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1),
0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
[popover]::backdrop {
background: transparent; /* Popover는 배경 오버레이 없음 */
}
[popover] ul {
list-style: none;
padding: 0;
margin: 0;
}
[popover] li {
padding: 0;
}
[popover] a {
display: block;
padding: 0.75rem 1.5rem;
color: #374151;
text-decoration: none;
}
[popover] a:hover {
background: #f3f4f6;
}
/* 오픈 애니메이션 */
[popover]:popover-open {
animation: fadeIn 0.2s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
Popover API로 툴팁을 구현합니다.
<button popovertarget="tooltip" popovertargetaction="hover">
도움말
</button>
<div id="tooltip" popover="manual" role="tooltip">
이 기능은 사용자 프로필을 편집합니다.
</div>
// Manual popover로 호버 동작 구현
const button = document.querySelector('[popovertarget="tooltip"]');
const tooltip = document.getElementById('tooltip');
button.addEventListener('mouseenter', () => {
tooltip.showPopover();
});
button.addEventListener('mouseleave', () => {
tooltip.hidePopover();
});
button.addEventListener('focus', () => {
tooltip.showPopover();
});
button.addEventListener('blur', () => {
tooltip.hidePopover();
});
중첩된 모달을 구현합니다.
<button id="open-main">메인 다이얼로그 열기</button>
<dialog id="main-dialog">
<h2>메인 다이얼로그</h2>
<button id="open-nested">중첩 다이얼로그 열기</button>
<button onclick="this.closest('dialog').close()">닫기</button>
</dialog>
<dialog id="nested-dialog">
<h2>중첩 다이얼로그</h2>
<p>메인 다이얼로그 위에 표시됩니다.</p>
<button onclick="this.closest('dialog').close()">닫기</button>
</dialog>
document.getElementById('open-main').addEventListener('click', () => {
document.getElementById('main-dialog').showModal();
});
document.getElementById('open-nested').addEventListener('click', () => {
document.getElementById('nested-dialog').showModal();
});
Popover의 자동 닫기 동작을 제어합니다.
<!-- auto (기본값): 다른 popover가 열리거나 바깥 클릭 시 자동으로 닫힘 -->
<div popover="auto">자동 닫힘</div>
<!-- manual: 명시적으로 hidePopover() 호출해야만 닫힘 -->
<div popover="manual">수동으로만 닫힘</div>
활용 팁
- 접근성 내장: <dialog>는 자동으로 포커스 트랩, ESC 키 닫기, ARIA 역할을 제공합니다. 추가 JavaScript 없이 WCAG 준수가 가능합니다.
- 폼 통합:
<form method="dialog">내부의 submit 버튼은 자동으로 다이얼로그를 닫고, button의 value가 returnValue가 됩니다. - 브라우저 지원: <dialog>는 Chrome 37+, Safari 15.4+, Firefox 98+에서 지원됩니다. Popover API는 Chrome 114+, Safari 17+, Firefox 125+입니다.
- Polyfill: dialog-polyfill 라이브러리로 구형 브라우저를 지원할 수 있습니다.
- Popover 위치: CSS Anchor Positioning API와 결합하면 Popover를 정확한 위치에 표시할 수 있습니다.
- 애니메이션:
@starting-style규칙으로 열기/닫기 애니메이션을 더욱 세밀하게 제어할 수 있습니다.
마무리
<dialog>와 Popover API는 모달 UI 구현의 복잡성을 웹 플랫폼 레벨에서 해결합니다. 접근성, 포커스 관리, 키보드 내비게이션이 자동으로 처리되며, JavaScript 라이브러리 의존성을 제거할 수 있습니다. 모던 웹 애플리케이션에서 모달과 오버레이 UI를 구현할 때 가장 먼저 고려해야 할 표준 솔루션입니다.