페이지 위에 레이어로 뜨는 그 창, 모달.
개념부터 3가지 유형 구현까지 — 복붙 한 번에 내 사이트에 바로 적용해요.
현재 페이지를 떠나지 않고, 그 위에 레이어처럼 덮여서 나타나는 다이얼로그 창
웹사이트에서 버튼을 클릭하면 화면이 어두워지면서 가운데에 창이 하나 떠오르는 걸 본 적 있죠?
그게 바로 모달 팝업이에요.
새 페이지로 이동하는 게 아니라, 현재 페이지 위에 반투명 오버레이를 깔고
그 위에 콘텐츠 박스(.modal-box)를 올리는 방식이에요.
모달은 사용자의 주의를 특정 콘텐츠에 집중시킬 때 탁월해요.
삭제 확인, 로그인 폼, 이미지 확대 보기, 쿠키 동의 안내 등
"잠깐 이것 좀 봐주세요"가 필요한 상황에서 가장 많이 사용돼요.
구현 원리는 생각보다 단순해요 — CSS position: fixed와 z-index,
그리고 JavaScript 클래스 토글 몇 줄이면 완성이에요.
모달이 열릴 때는 배경 스크롤을 막아야 자연스러워요.
JavaScript에서 document.body.style.overflow = 'hidden'을 설정하면
모달 뒤 페이지가 움직이지 않아요.
모달을 닫을 때는 반드시 overflow를 원래대로 복구해야 한다는 것도 기억해주세요.
배경을 어둡게 만들어 사용자의 시선을 모달 콘텐츠에만 집중시켜요. 중요한 메시지나 선택을 놓치지 않게 도와줘요.
현재 페이지 맥락을 그대로 유지하면서 추가 정보나 폼을 보여줄 수 있어요. UX가 부드럽고 자연스러워요.
알림, 확인 다이얼로그, 로그인 폼, 이미지 확대, 쿠키 동의 등 수많은 UI 패턴에 활용할 수 있어요.
별도 라이브러리 없이 HTML + CSS + JS 30줄 내외로 완전히 구현할 수 있어요. 가볍고 빠르게 동작해요.
모달 vs 팝업 vs 드로어 차이 — 모달은 페이지 가운데 뜨는 레이어, 팝업은 새 브라우저 창(요즘은 차단됨), 드로어(Drawer)는 화면 옆에서 슬라이드로 열리는 패널이에요. 대부분 "팝업"이라고 말하지만 실제로 구현하는 건 모달인 경우가 많아요.
| 상황 | 적합한 모달 유형 | 효과 |
|---|---|---|
| 데이터 삭제 · 위험한 작업 전 | 확인 다이얼로그 (취소/확인) | 실수 방지 |
| 갤러리 · 포트폴리오 이미지 | 라이트박스 (이미지 확대 보기) | 몰입감 향상 |
| 회원가입 · 로그인 유도 | 폼 모달 (인풋 포함) | 전환율 ↑ |
| 공지사항 · 이벤트 알림 | 기본 알림 모달 | 노출 강화 |
| 쿠키 · 개인정보 동의 | 하단 고정형 또는 중앙 모달 | 법적 요건 |
주의: 모달은 사용자 흐름을 강제로 끊어요. 꼭 필요한 경우에만 사용하고, 항상 쉽게 닫을 수 있어야 해요. 닫기 버튼(×) + 오버레이 클릭 + ESC 키, 세 가지 방법 모두 지원하는 게 좋은 UX예요.
가장 자주 쓰이는 3가지 모달 패턴을 미리보기 카드로 확인하고, 직접 클릭해서 열어보세요.
HTML 구조 → CSS 스타일링 → JavaScript 동작 순서로 한 줄씩 이해해요
position: fixed; inset: 0으로 뷰포트 전체를 채워요.max-width로 너비를 제한해요.<!-- HTML 구조 --> <button onclick="openModal()">모달 열기</button> <div class="modal-overlay" id="myModal" onclick="handleOverlayClick(event)"> <div class="modal-box"> <button class="modal-close" onclick="closeModal()">✕</button> <h2>모달 제목</h2> <p>모달 내용이 여기에 들어가요.</p> <button onclick="closeModal()">확인</button> </div> </div> /* CSS 스타일 */ .modal-overlay { position: fixed; inset: 0; /* top:0; right:0; bottom:0; left:0 */ z-index: 1000; background: rgba(0, 0, 0, 0.55); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; padding: 24px; opacity: 0; visibility: hidden; transition: opacity .22s ease, visibility .22s ease; } .modal-overlay.open { opacity: 1; visibility: visible; } .modal-box { background: #fff; border-radius: 16px; box-shadow: 0 24px 64px rgba(0,0,0,.18); width: 100%; max-width: 440px; padding: 32px; position: relative; transform: translateY(16px) scale(.97); transition: transform .25s cubic-bezier(.34,1.56,.64,1); } .modal-overlay.open .modal-box { transform: translateY(0) scale(1); } .modal-close { position: absolute; top: 16px; right: 16px; background: #f3f4f6; border: none; border-radius: 50%; width: 30px; height: 30px; cursor: pointer; } // JavaScript function openModal() { document.getElementById('myModal').classList.add('open'); document.body.style.overflow = 'hidden'; // 배경 스크롤 막기 } function closeModal() { document.getElementById('myModal').classList.remove('open'); document.body.style.overflow = ''; // 스크롤 복구 } function handleOverlayClick(e) { if (e.target === e.currentTarget) closeModal(); // 오버레이만 클릭 시 } // ESC 키로 닫기 document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeModal(); });
핵심 포인트 3가지
① visibility: hidden + opacity: 0을 함께 쓰면 display: none과 달리 CSS 트랜지션(페이드 효과)이 부드럽게 작동해요.
② transform: translateY(16px) scale(.97)로 시작해서 translateY(0) scale(1)로 끝나면 모달이 살짝 위로 올라오며 열리는 느낌을 줘요.
③ 오버레이 클릭 감지 시 e.target === e.currentTarget 조건을 확인해야 해요. 모달 박스 안쪽을 클릭해도 닫히는 버그를 방지해요.
아래 버튼을 클릭해서 3가지 모달 유형을 직접 체험해보세요. 오버레이 클릭이나 ESC 키로 닫을 수 있어요.
모달을 닫는 방법: × 버튼 클릭 / 어두운 배경(오버레이) 클릭 / 키보드 ESC 키 누르기
모달을 만들 때 AI에게 어떻게 설명하면 좋은지, 실제로 잘 동작하는 프롬프트를 소개해요.
AI에게 모달을 요청할 때는 어떤 상황에서 열리는지, 버튼이 몇 개인지, 확인 시 어떤 동작을 할지를 구체적으로 알려주면 훨씬 정확한 코드를 받을 수 있어요.
아래 프롬프트를 복붙해서 바로 사용해보세요 👇
내 사이트에 확인 모달을 만들어줘.
"삭제하시겠습니까?" 메시지와 취소/확인 버튼이 있고,
확인 클릭 시 console.log('삭제 완료')가 실행되게 해줘.
배경은 반투명 블러 처리해줘.
닫는 방법은 × 버튼, 오버레이 클릭, ESC 키 세 가지 모두 지원해줘.
모달이 열릴 때는 부드럽게 위로 올라오는 애니메이션도 넣어줘.
img 태그를 클릭하면 모달이 열리면서 이미지가 크게 보이게 해줘. 배경은 거의 검은색으로 해줘.
이름과 이메일을 입력받는 폼이 있는 모달을 만들어줘. 제출 시 유효성 검사도 해줘.
페이지 처음 방문 시 쿠키 동의 모달이 뜨게 해줘. 동의하면 localStorage에 저장해서 다음엔 안 뜨게 해줘.
모달 구현 중 막히는 부분, 여기서 해결하세요.
z-index 문제예요. 모달 오버레이에 z-index: 1000 이상의 값을 주세요. 사이트 네비게이션이 보통 z-index: 100~500을 사용하기 때문에, 모달은 그보다 훨씬 높은 값이어야 해요.
또한 부모 요소에 transform, filter, will-change 속성이 있으면 position: fixed가 해당 부모를 기준으로 동작해서 화면 전체를 덮지 못할 수 있어요. 모달을 <body>의 직계 자식으로 이동시켜 보세요.
모달이 열릴 때 document.body.style.overflow = 'hidden'을 설정하면 배경 스크롤이 막혀요. 모달을 닫을 때는 반드시 document.body.style.overflow = ''로 복구해야 해요.
iOS Safari에서는 추가로 document.body.style.position = 'fixed'와 document.body.style.width = '100%'가 필요할 수 있어요. 단, 이 경우 스크롤 위치가 초기화되므로 열기 전 스크롤 위치를 저장해뒀다가 닫을 때 복구해야 해요.
CSS transition을 활용하면 돼요. visibility: hidden + opacity: 0 상태에서 시작해서, .open 클래스가 추가될 때 opacity: 1로 전환하면 부드러운 페이드인이 만들어져요.
더 생동감 있는 효과를 원한다면 모달 박스에 transform: translateY(20px)을 초기값으로 주고, 열릴 때 translateY(0)으로 전환하세요. cubic-bezier(.34,1.56,.64,1) 이징을 쓰면 살짝 튀어오르는 느낌이 나요.
모달 박스(.modal-box)에 max-height: 90vh; overflow-y: auto;를 추가하면 내용이 길어질 때 박스 안에서 스크롤이 생겨요. 모바일 대응을 위해 padding: 24px도 조정해주세요.
모달 헤더(제목 + 닫기버튼)와 푸터(버튼 영역)는 고정하고 가운데 콘텐츠만 스크롤되게 하려면 flex 레이아웃을 활용해보세요.
각 모달에 고유한 id를 붙이고, openModal('modalId')처럼 ID를 인자로 받는 함수를 만들어요. 함수 안에서 document.getElementById(id)로 특정 모달만 열 수 있어요.
현재 열린 모달을 추적하는 변수를 하나 두면, ESC 키 이벤트도 하나로 처리할 수 있어요: let currentModal = null;로 선언하고, 열 때 currentModal = modal, 닫을 때 currentModal = null로 관리하세요.
오버레이에 padding: 16px 이상을 주면 모달 박스가 화면 가장자리에 딱 붙지 않아요. 모달 박스의 width: 100%; max-width: 440px 설정이 모바일에서는 100%, 큰 화면에서는 440px로 제한되도록 동작해요.
세로가 너무 긴 경우엔 max-height: calc(100vh - 48px); overflow-y: auto;를 추가해서 모달이 화면을 넘어가지 않도록 막아요.