모달(Modal)은 웹 페이지의 메인 창과 별개로 띄워지는 창입니다. 주된 목적은 메인 창에서의 사용자 상호작용을 일시적으로 차단하고, 사용자에게 정보를 전달하거나 확인 절차를 거치도록 하는 것입니다. 모달 창은 로그인 컴포넌트, 사용자 입력 확인 등 다양한 용도로 널리 활용됩니다.
부트스트랩(Bootstrap)은 컴포넌트를 매우 빠르게 만들 수 있도록 도와주는 프레임워크입니다. 이 글에서는 부트스트랩 설정 방법, 모달이 필요한 이유, 그리고 부트스트랩 프레임워크를 활용한 실제 모달 예제까지 차근차근 살펴봅니다.
시작하기
웹 페이지에서 모달을 정상적으로 확인하려면 필요한 의존성을 먼저 준비해야 합니다. 이 프로젝트에는 부트스트랩, Popper.js, jQuery가 필요합니다. 부트스트랩 공식 문서의 퀵 스타트(Quick Start) 페이지를 참고하면 의존성을 손쉽게 구성할 수 있습니다.
필요한 패키지를 연결하는 방법은 여러 가지가 있지만, 초보자에게 가장 쉬운 방법은 jQuery, Popper.js, 부트스트랩을 CDN(Content Delivery Network)으로 불러오는 것입니다. 이때 <script> 태그의 순서에 유의하세요. 순서가 매우 중요합니다.
모달을 사용해야 하는 경우
모달은 사용자의 주의를 일시적으로 끌어야 하는 웹 애플리케이션에 적합합니다.
예를 들어 은행 웹사이트에 로그인한 상태에서 다른 탭으로 이동했다가 다시 돌아오면, 일정 시간 동안 활동이 없다는 이유로 로그아웃될 것임을 알리는 모달이 뜨는 경우가 있습니다. 비활동 감지는 모달이 필요한 대표적인 사례입니다. 사용자가 계속 로그인할지, 아니면 로그아웃할지 결정하기 전까지는 사이트에서 더 이상 상호작용할 수 없습니다.
개발자 관점에서 보면, 모달은 꼭 필요하지 않은 경우 페이지를 새로고침하지 않도록 도와줍니다. 수많은 쇼핑몰이 '퀵 뷰(Quick View)' 기능에 모달을 활용하는데, 덕분에 사용자는 사이트를 더 빠르게 탐색하고 쇼핑을 마칠 수 있습니다. 퀵 뷰는 상품 정보를 확인하기 위해 전체 페이지를 새로 불러올 필요가 없습니다.
반면, 모달과 상호작용하는 동안 메인 화면의 콘텐츠에도 접근해야 한다면 다른 UI 요소를 사용하는 것이 좋습니다. 모달의 본질은 메인 창에서 제어권을 가져와 모달에 집중시키는 것이기 때문입니다.
부트스트랩으로 모달 만들기
전통적으로 모달은 HTML, CSS, JavaScript로 구현합니다. 세 언어 모두 중요하지만, 모달을 실제로 작동하게 하는 핵심은 JavaScript입니다. JavaScript는 모달을 켜고 끄는 스위치 역할을 합니다.
부트스트랩을 사용할 때는 공식 문서를 곁에 두고 모달 코드를 정확히 복사·붙여넣는 것이 좋습니다. 부트스트랩과 각 컴포넌트의 동작 방식을 배우는 단계에서는 코드를 복사한 뒤 한 줄씩 주석을 달면서 각 코드가 무슨 역할을 하는지 파악해 보길 권합니다.
부트스트랩의 모달 코드를 활용하는 것은 바퀴를 다시 발명하는 것이 아닙니다. 하지만 남에게 설명할 수 있을 정도로 동작 원리를 이해하는 것이 중요합니다. 왜 작동하는지 설명할 수 없다면 단순 복사·붙여넣기는 큰 의미가 없습니다.
버튼으로 모달 실행하기
<button
type="button"
class="btn btn-primary"
data-toggle="modal"
data-target="#exampleModal"
>Launch demo modal
</button>
위 코드는 부트스트랩 공식 문서에 있는 모달 생성 예제입니다. 이어지는 내용에서는 마크업이 정확히 어떤 의미인지 하나씩 살펴보겠습니다. 이 섹션에서는 주로 속성(attribute)을 다룹니다.
class="btn btn-primary"
CSS에서 class가 무엇을 의미하는지는 이미 알고 있을 것입니다. 여기서도 마찬가지입니다. 나열된 클래스들은 HTML 문서의 head에서 참조한 부트스트랩 스타일시트와 연결됩니다.
'btn' 클래스가 버튼의 대부분 스타일을 담당하고, 두 번째 클래스 이름이 색상을 결정합니다. 'btn-primary'를 다른 기본 버튼 색상 클래스(예: btn-secondary 또는 btn-danger)로 바꿔보면 어떻게 달라지는지 직접 확인할 수 있습니다.
data-toggle="modal"
이 속성은 모달 내부 버튼에 있는 data-dismiss 속성과 짝을 이루는 역할을 합니다.
data-target="#exampleModal"
data-target 속성은 id가 exampleModal인 코드 블록을 가리킵니다. exampleModal이 실제 모달 콘텐츠이며, 버튼은 이 식별자가 붙은 블록에서 정보를 가져옵니다.
이 속성은 한 페이지에 여러 개의 모달이 있을 때 특히 중요합니다. data-target 속성과 id를 서로 다르게 지정해야 한 번에 하나의 모달만 열립니다.
이벤트 핸들러는 jQuery와 Popper.js를 통해 부트스트랩이 모두 관리합니다. 사용자가 클릭했을 때 어떻게 처리할지 고민할 필요가 없습니다. 다만 정상 동작을 유지하려면 원본 클래스 이름을 임의로 변경하지 않아야 합니다. CSS가 해당 클래스 이름으로 요소를 선택하기 때문입니다.
메인 모달 요소
<div
class="modal fade"
id="exampleModal"
tabindex="-1"
aria-labelledby="exampleModalLabel"
aria-hidden="true"
>
... Modal stuff here ...
</div>
앞선 코드와 마찬가지로 이 코드 역시 부트스트랩 공식 문서에서 가져온 것입니다. 이 섹션의 목적은 마크업의 의미를 확실히 이해하는 것입니다.
class="modal fade"
메인 모달 요소는 메인 웹 페이지의 버튼을 통해 실행됩니다. 여기의 클래스 이름은 모달에 적용되는 두 개의 선택자를 나타냅니다. 첫 번째는 모달 자체의 스타일을, 두 번째는 페이드 인·페이드 아웃(fade-in/fade-out) 전환 효과를 적용한다는 의미입니다.
id="exampleModal"
id 속성은 앞에서 이미 살펴봤습니다. 스타일 변경을 위해 특정 요소를 지정할 때 사용하는데, 여기서는 메인 페이지의 실행 버튼(data-target)의 타깃이기도 합니다.
tabindex="-1"
tabindex는 보통 해당 요소가 키보드로 접근 가능함을 의미합니다. 하지만 값이 음수라면 그렇지 않습니다. 이 경우 JavaScript나 클릭 이벤트를 통해서만 포커스를 받습니다.
aria-labelledby="exampleModalLabel"
aria-hidden="true"
aria 속성은 접근성(accessibility)과 관련된 속성으로 직관적인 의미를 가집니다. labelledby aria 속성은 보통 modal-title의 id와 연결됩니다. hidden 속성이 true라는 값은 화면 낭독기(screen reader)가 해당 요소를 읽지 않음을 나타냅니다.
부트스트랩 스타터로 나만의 모달 만들기
스타일을 조금씩 바꿔가며 부트스트랩 모달에 자신만의 개성을 더할 수 있습니다. CSS의 캐스케이딩(cascading) 특성은 여전히 강력합니다. 새로운 클래스를 만들어 기존 클래스 이름 뒤에 공백으로 구분해 추가하거나, !important 키워드로 클래스를 덮어쓸 수 있습니다. 다만 !important는 극단적인 경우에만 사용해야 하며, 대부분의 경우 커스텀 클래스 이름으로 스타일을 변경하는 것이 좋습니다.
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js">
<!--<![endif]-->
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Bootstrap Modal</title>
<meta name="description" content="" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- Link to Bootstrap Stylesheet -->
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.min.css"
/>
<style>
body {
max-width: 1400px;
width: 100%;
padding: 20px;
margin: 20px;
}
.btn-ck {
background-color: goldenrod;
border: 1px solid black;
color: black;
}
.btn-ck:hover {
background-color: rgb(231, 203, 131);
border: 1px solid lightgray;
}
.custom-modal {
background: rgb(250, 249, 243);
height: 400px;
width: 500px;
position: fixed;
top: center;
left: center;
}
</style>
</head>
<body>
<!--[if lt IE 7]>
<p class="browsehappy">
You are using an <strong>outdated</strong> browser. Please
<a href="#">upgrade your browser</a> to improve your experience.
</p>
<![endif]-->
<!-- Button triggers modal -->
<h1>Modal Example</h1>
<button
type="button"
class="btn btn-primary btn-ck"
data-toggle="modal"
data-target="#exampleModal"
>
Launch demo modal
</button>
<!-- Modal -->
<div
class="modal fade"
id="exampleModal"
tabindex="-1"
aria-labelledby="exampleModalLabel"
aria-hidden="true"
>
<div class="modal-dialog">
<div class="modal-content custom-modal">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">
CareerKarma Demo Modal
</h5>
<button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
You are reading a modal with a bit of custom styling...
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-secondary"
data-dismiss="modal"
>
Close
</button>
<button type="button" class="btn btn-primary btn-ck">
Save changes
</button>
</div>
</div>
</div>
</div>
<!-- The following CDN's go right before the closing body tag. -->
<!-- jquery CDN first -->
<script
src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"
async
defer
></script>
<!-- popper.js CDN second -->
<script
src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.4.4/umd/popper.min.js"
async
defer
></script>
<!-- bootstrap CDN third -->
<script
src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.2/js/bootstrap.min.js"
async
defer
></script>
</body>
</html>
위 코드에서는 CSS를 활용해 버튼과 모달 배경의 스타일을 변경했습니다. 이 예제에서는 !important 키워드로 부트스트랩이 제공하는 CSS를 덮어쓰는 대신, 커스텀 클래스 이름(btn-ck, custom-modal)을 추가하는 방식을 사용했습니다.
결론
부트스트랩으로 모달을 만들 때 가장 큰 걸림돌은 공식 문서를 정확히 읽고 따르는 것입니다. 오류가 발생한다면 대부분 복사·붙여넣기 실수, CDN이나 스타일시트가 head/body 안에서 잘못된 위치에 놓였거나, CSS가 올바른 요소를 가리키지 않는 경우일 가능성이 높습니다.
커스텀 스타일링은 어느 정도 시행착오를 거치게 됩니다. 다행히 부트스트랩의 공식 문서는 매우 방대하고 친절합니다. 다른 프레임워크나 패키지를 먼저 배워본 경험이 없다면, 문서가 훌륭하고 이해하기 쉽다는 점에서 오히려 유리한 출발점이라 할 수 있습니다.