Contents
see List모달은 화면 효과가 아니라 입력 흐름을 제어하는 UI다
모달 창은 삭제 확인, 설정 변경, 로그인처럼 사용자의 결정을 잠시 분리해야 할 때 유용하다. 하지만 일반 div를 화면 가운데에 띄우는 방식만으로는 충분하지 않다. 키보드 사용자가 창 안으로 이동할 수 있어야 하고, 닫았을 때에는 원래 누르던 버튼으로 돌아가야 하며, 배경 콘텐츠가 실수로 조작되지 않아야 한다. 이러한 흐름이 깨지면 마우스로는 정상처럼 보여도 접근성, 모바일 조작, 자동화 테스트에서 문제가 생긴다.
브라우저가 제공하는 HTML dialog 요소는 이 기본 동작을 표준 API로 제공한다. showModal()로 열면 대화상자가 최상위 레이어에 표시되고, 브라우저는 배경을 비활성화하는 처리와 포커스 이동을 지원한다. 별도 라이브러리를 쓰기 전에 이 요소로 요구사항을 충족하는지 먼저 확인하면 코드와 유지보수 범위를 줄일 수 있다.
기본 마크업은 버튼과 dialog를 명확히 연결한다
dialog 안에는 무엇을 묻는지 알려 주는 제목과 설명을 넣고, 취소와 실행 버튼의 의미를 구분한다. form의 method를 dialog로 지정하면 버튼의 value가 dialog의 returnValue로 전달되고 창도 닫힌다. 취소 버튼에는 value를 빈 문자열로 두어 실행 여부를 단순하게 판별할 수 있다.
<button id="delete-button" type="button">프로젝트 삭제</button>
<dialog id="delete-dialog" aria-labelledby="delete-title" aria-describedby="delete-description">
<form method="dialog">
<h2 id="delete-title">프로젝트를 삭제할까요?</h2>
<p id="delete-description">삭제 후에는 연결된 작업 목록도 복구할 수 없습니다.</p>
<menu><button value="">취소</button><button value="confirm" class="danger">삭제</button></menu>
</form>
</dialog>
<script>
const button = document.querySelector('#delete-button');
const dialog = document.querySelector('#delete-dialog');
button.addEventListener('click', () => dialog.showModal());
dialog.addEventListener('close', () => {
if (dialog.returnValue === 'confirm') deleteProject();
button.focus();
});
</script>실제 삭제 요청은 close 이벤트에서 확인값을 검사한 뒤 실행한다. 클릭 이벤트만으로 처리하면 Enter 키로 form을 제출했을 때 누락될 수 있다. 서버 요청이 필요한 경우에는 삭제 버튼을 누른 직후 중복 제출을 막고, 실패하면 dialog를 다시 열기보다 오류 메시지를 별도 영역에 표시할지 제품의 흐름에 맞게 결정한다.
열기와 닫기 동작에서 놓치기 쉬운 조건
- 모달을 열기 전, 이미 열린 상태인지 확인한다. showModal()을 중복 호출하면 예외가 발생할 수 있으므로 비동기 화면 전환과 함께 쓸 때 특히 주의한다.
- Escape 키는 기본적으로 cancel 이벤트를 발생시킨다. 저장 중인 작업처럼 즉시 닫으면 안 되는 상황이라면 cancel 이벤트에서 preventDefault()를 호출하고 사용자에게 이유를 안내한다.
- 배경을 눌러 닫는 동작은 명시적으로 구현한다. dialog 자체의 클릭 좌표와 내부 콘텐츠 영역을 구분하지 않으면 버튼을 누른 클릭도 닫힘으로 처리될 수 있다.
- 닫힌 뒤에는 모달을 연 제어 요소에 포커스를 돌려준다. 목록에서 항목을 삭제해 그 버튼이 사라지는 경우에는 목록 제목이나 다음 항목처럼 예측 가능한 대체 위치로 이동시킨다.
배경과 시각 상태를 CSS로 정리한다
::backdrop 의사 요소는 모달 뒤 화면을 덮는 영역이다. 너무 진한 색은 현재 문맥을 잃게 하고, 너무 옅은 색은 모달의 우선순위를 약하게 만든다. 대화상자의 최대 폭과 화면 여백을 같이 지정하면 작은 화면에서도 가로 스크롤을 피할 수 있다. 애니메이션을 추가할 때는 사용자의 모션 감소 설정을 존중해야 한다.
dialog {
width: min(32rem, calc(100vw - 2rem));
border: 0; border-radius: 0.75rem; padding: 1.5rem;
color: #1f2937; box-shadow: 0 1rem 3rem rgb(0 0 0 / 0.25);
}
dialog::backdrop { background: rgb(15 23 42 / 0.55); }
dialog menu { display: flex; justify-content: flex-end; gap: 0.75rem; padding: 0; margin: 1.5rem 0 0; }
.danger { background: #b91c1c; color: white; }
@media (prefers-reduced-motion: no-preference) {
dialog[open] { animation: dialog-in 160ms ease-out; }
}
@keyframes dialog-in { from { opacity: 0; transform: translateY(0.5rem); } }기능 테스트와 접근성 테스트를 분리해서 확인한다
테스트에서는 단순히 모달이 화면에 보이는지보다 사용자 흐름을 검증해야 한다. 열기 버튼을 Tab으로 선택한 뒤 Enter로 열고, 첫 포커스가 대화상자 내부에 들어오는지 확인한다. Tab과 Shift+Tab을 반복했을 때 포커스가 배경 링크로 빠져나가지 않는지도 점검한다. Escape로 닫은 뒤 원래 버튼으로 돌아오는지, 취소와 확인이 서로 다른 결과를 내는지도 확인한다.
브라우저 호환 범위를 지원해야 한다면 대상 브라우저에서 dialog와 showModal API를 실제로 시험하고, 구형 환경에는 검증된 폴리필 또는 대체 화면을 준비한다. 자체 구현으로 aria-modal을 추가하는 것만으로는 포커스 가두기와 배경 비활성화가 자동으로 해결되지 않는다. 표준 dialog를 기반으로 하고, 제품 고유의 예외만 JavaScript로 보완하는 편이 안전하다.
배포 전 체크리스트
- 모달의 목적을 설명하는 제목과 필요한 보조 설명이 있는가
- 키보드만으로 열기, 취소, 확인, 닫기가 가능한가
- 닫힌 뒤 포커스가 논리적인 위치로 복귀하는가
- 확인 동작이 중복 실행되지 않고 실패 상태가 안내되는가
- 작은 화면, 고대비 환경, 모션 감소 설정에서 읽고 조작하기 쉬운가
모달은 작은 컴포넌트지만 화면의 입력 흐름 전체에 영향을 준다. HTML dialog의 기본 동작을 활용하고 위 체크리스트를 회귀 테스트에 포함하면, 기능 추가 뒤에도 예측 가능한 사용자 경험을 유지할 수 있다.
html
| No | 작성일 | Title |
|---|---|---|
| 314 | 2015. 08. 05. | [ CSS ] div 에 수직 정렬 ( vertical-align ) 설정하기 |
| 276 | 2015. 05. 08. | [ css ] 테이블 글자 자동으로 내려쓰기 |
| 273 | 2015. 05. 02. | [ css ] table border 시에 td 간격 없애기 |
| 165 | 2015. 02. 24. | [ css ] form selector |
| 108 | 2014. 12. 17. | [ Html ] Special Characters ( 특수문자 ) 코드표 |
| 29 | 2014. 09. 25. | [ css ] 리스트 스타일 선택 |
| 28 | 2014. 09. 25. | [ css ] 마우스 오버시 효과주기 |