Contents
see List모달은 화면을 가리는 기능이 아니라 작업 흐름을 제어하는 UI다
삭제 확인, 상세 설정, 로그인처럼 사용자의 즉시 판단이 필요한 화면에는 모달이 자주 쓰입니다. 하지만 배경을 흐리게 보이게 하는 것만으로는 충분하지 않습니다. 키보드 사용자가 탭 키를 눌렀을 때 모달 밖의 링크로 이동하거나, 닫은 뒤 포커스가 사라지면 실제 서비스에서는 작업을 계속하기 어렵습니다. 모바일 화면에서 스크롤이 뒤로 움직이거나 스크린 리더가 모달 제목을 읽지 못하는 문제도 같은 범주입니다.
가능하면 자바스크립트로 div를 조합하기보다 브라우저의 HTML dialog 요소를 기본으로 사용하세요. showModal()로 열면 브라우저가 모달 상태와 배경 비활성화를 처리하고 Esc 키로 닫는 기본 동작도 제공합니다. 다만 제목 연결, 초기 포커스, 닫은 뒤 포커스 복원, 취소 동작의 의도를 애플리케이션 코드에서 명확하게 설계해야 합니다.
기본 마크업: 제목·설명·행동을 분리한다
dialog 내부에는 무엇을 확인하거나 수정하는지 알 수 있는 제목을 두고, 위험한 작업이면 결과를 구체적으로 설명합니다. 단순히 ‘확인’이라고 쓰기보다 ‘프로젝트 삭제’처럼 결과가 드러나는 버튼 문구를 사용합니다. form method="dialog"는 취소 버튼처럼 별도 데이터 저장 없이 모달만 닫는 동작에 적합합니다. 실제 삭제나 저장은 클릭 이벤트에서 서버 응답을 확인한 뒤 수행하는 편이 안전합니다.
<button id="deleteButton" type="button">프로젝트 삭제</button>
<dialog id="deleteDialog" aria-labelledby="deleteTitle" aria-describedby="deleteDescription">
<form method="dialog">
<h2 id="deleteTitle">프로젝트를 삭제할까요?</h2>
<p id="deleteDescription">삭제한 프로젝트와 연결된 작업 기록은 복구할 수 없습니다.</p>
<menu>
<button value="cancel" type="submit">취소</button>
<button id="confirmDelete" value="confirm" type="button">프로젝트 삭제</button>
</menu>
</form>
</dialog>aria-modal 속성은 showModal()로 열린 네이티브 dialog에 일반적으로 별도 지정할 필요가 없습니다. 반대로 div 기반 모달에 role="dialog"만 추가하고 배경 포커스를 막지 않으면 접근성 문제는 해결되지 않습니다. 브라우저 지원 범위와 기존 디자인 시스템 때문에 div를 사용해야 한다면 focus trap, inert 처리, Esc 처리, 스크롤 잠금을 모두 구현하고 자동화 테스트로 검증해야 합니다.
열기와 닫기에서 포커스를 잃지 않게 한다
모달을 열기 전의 버튼을 기억해 두었다가 닫을 때 되돌리면 키보드 사용자는 원래 작업 위치를 자연스럽게 이어갈 수 있습니다. 모달 안에서는 가장 의미 있는 첫 행동에 포커스를 둡니다. 삭제 확인이라면 실수 방지를 위해 기본 포커스를 취소 버튼에 놓는 경우가 많습니다. 서버 요청 중에는 확인 버튼을 비활성화해 중복 실행을 막고, 실패하면 오류 문구를 보여 준 뒤 버튼에 다시 포커스를 둡니다.
const dialog = document.querySelector('#deleteDialog');
const openButton = document.querySelector('#deleteButton');
const confirmButton = document.querySelector('#confirmDelete');
let returnFocus;
openButton.addEventListener('click', () => {
returnFocus = document.activeElement;
dialog.showModal();
dialog.querySelector('button[value="cancel"]').focus();
});
dialog.addEventListener('close', () => {
returnFocus?.focus();
});
confirmButton.addEventListener('click', async () => {
confirmButton.disabled = true;
try {
await deleteProject();
dialog.close('confirmed');
} catch (error) {
confirmButton.disabled = false;
confirmButton.focus();
}
});cancel 이벤트는 사용자가 Esc를 누르거나 브라우저가 취소 동작을 요청할 때 발생합니다. 저장 중에는 Esc로 창이 닫히지 않게 해야 하는 경우도 있으므로 무조건 막기보다 현재 요청 상태를 기준으로 판단하세요. 닫기 아이콘을 제공한다면 aria-label="닫기"를 넣어 아이콘만으로도 목적을 전달해야 합니다. 네트워크 오류가 발생했을 때에는 모달을 조용히 닫지 말고 오류 원인과 다시 시도할 수 있는 버튼을 보존해야 합니다.
스타일은 작은 화면과 긴 내용까지 고려한다
dialog는 화면보다 내용이 길어질 수 있습니다. 폭을 고정값 하나로만 지정하지 말고 최대 폭과 뷰포트 기준 폭을 함께 사용하며, 본문 영역이 길면 내부 스크롤이 가능하도록 구성합니다. backdrop은 대비를 확보하되 과도한 투명도로 배경의 클릭 대상이 혼동되지 않도록 합니다. 모바일 Safari를 포함한 주요 대상 브라우저에서 열기, Esc, 탭 이동, 화면 회전을 직접 점검하는 것이 좋습니다. 다이얼로그를 연 상태에서 브라우저 확대를 적용했을 때도 버튼이 화면 밖으로 밀리지 않는지 확인하세요.
dialog {
width: min(32rem, calc(100vw - 2rem));
max-height: calc(100dvh - 2rem);
border: 0;
border-radius: 0.75rem;
padding: 1.5rem;
overflow: auto;
}
dialog::backdrop {
background: rgb(0 0 0 / 0.5);
}배포 전 체크리스트
- 모달 제목과 작업 결과가 텍스트로 명확히 표시되는가
- 열 때 의미 있는 첫 요소에 포커스가 가는가
- Tab과 Shift+Tab으로 모달 밖으로 이동하지 않는가
- Esc, 취소 버튼, 닫기 버튼의 동작이 현재 작업 상태와 일치하는가
- 닫은 뒤 모달을 연 요소로 포커스가 복원되는가
- 키보드 전용 사용자와 모바일 화면에서 실제로 검증했는가
모달의 품질은 애니메이션보다 포커스와 작업 결과의 예측 가능성에서 결정됩니다. HTML dialog를 기반으로 필요한 상태만 코드로 보완하고 핵심 흐름을 키보드 테스트에 포함하면 재사용 가능한 안전한 UI 패턴으로 정착시킬 수 있습니다.
html
| No | 작성일 | Title |
|---|---|---|
| 2303 | 2026. 04. 05. | CSS Anchor Positioning 완벽 가이드: JavaScript 없이 툴팁과 드롭다운 만들기 |
| 2278 | 2026. 04. 04. | 2026년 필수 HTML/CSS 신기능: Popover API, Anchor Positioning, Scroll-Driven Animations |
| 2277 | 2026. 04. 04. | CSS Container Queries 실전 가이드: 미디어 쿼리를 넘어서는 반응형 컴포넌트 설계 |
| 2221 | 2026. 02. 11. | HTML Dialog와 Popover API로 모던 모달 구현 |
| 2220 | 2026. 02. 11. | CSS Grid와 Subgrid 레이아웃 마스터하기 |
| 2219 | 2026. 02. 11. | 웹 접근성(A11y) 완벽 가이드 2025 |
| 2218 | 2026. 02. 11. | Web Components와 Shadow DOM 실전 가이드 |
| 2217 | 2026. 02. 11. | 모던 CSS: Container Queries와 :has() 선택자 실전 활용 |
| 2181 | 2026. 02. 11. | Web Components 실전: Shadow DOM과 Custom Elements |
| 2180 | 2026. 02. 11. | 반응형 이미지 최적화: srcset, picture, 그리고 AVIF |