CSS / 반응형 레이아웃 만들기

반응형 레이아웃은 브라우저의 표시 영역에 맞춰 콘텐츠의 너비와 열 배치를 바꾸는 방식입니다. 작은 화면에서는 한 열로 읽기 쉽게 배치하고, 공간이 충분해지면 @media 조건으로 본문과 사이드바를 나란히 놓을 수 있습니다. 기기의 이름이나 물리적인 화면 해상도보다 실제로 콘텐츠를 표시할 수 있는 뷰포트 너비를 기준으로 설계하는 것이 핵심입니다.

반응형 배치에 필요한 세 가지

반응형 웹디자인에는 화면에 맞춰 줄어드는 바깥 상자, 넘치지 않는 내부 콘텐츠, 필요할 때 배치를 바꾸는 조건이 함께 필요합니다. 미디어 쿼리만 추가하고 본문에 큰 고정 너비가 남아 있으면 좁은 화면에서는 여전히 가로 스크롤이 생깁니다.

  • 유동적인 너비: 페이지가 사용 가능한 공간 안에 들어오도록 백분율이나 calc()로 너비를 정하고 max-width로 너무 넓어지는 것을 제한합니다.
  • 콘텐츠의 크기 제한: Grid 항목에는 필요에 따라 min-width: 0을 사용하고, 이미지에는 max-width: 100%; height: auto;를 적용합니다.
  • 레이아웃 전환: @media (min-width: ...) 등의 조건에서 열 개수와 배치를 바꿉니다.

또한 HTML의 <head> 안에 다음 메타 태그를 넣습니다. 모바일 브라우저가 기기의 표시 너비에 맞는 뷰포트를 사용하도록 하는 설정입니다. 확대 기능을 막는 user-scalable=no나 과도한 확대 제한을 추가하지 마세요. MDN의 viewport 메타 태그 설명에서 각 값의 역할을 확인할 수 있습니다.

<meta name="viewport" content="width=device-width, initial-scale=1">

모바일 우선과 데스크톱 우선의 차이

모바일 우선은 좁은 화면에 필요한 기본 스타일부터 작성하고 min-width 조건에서 넓은 화면의 배치를 추가하는 방식입니다. 반대로 데스크톱 우선은 넓은 화면의 기본 배치를 작성하고 max-width 조건에서 작은 화면의 배치를 덮어씁니다. 둘 다 반응형 페이지를 만들 수 있지만, 이 글에서는 기본 한 열 구조가 명확한 모바일 우선을 사용합니다.

미디어 쿼리는 해당 조건에 들어간 스타일만 단독으로 실행하는 기능이 아닙니다. 기본 스타일과 조건을 만족한 규칙들이 함께 CSS의 우선순위에 따라 적용됩니다. 예를 들어 1,100px 화면은 min-width: 768px과 min-width: 1100px 조건을 모두 만족합니다. 같은 선택자와 속성을 사용했다면 뒤에 작성한 1,100px 규칙이 앞의 값을 덮어씁니다.

배치가 바뀌는 기준점을 브레이크포인트(breakpoint)라고 합니다. 예제의 768px과 1,100px은 이 콘텐츠를 배치하기 위한 실습 값이며, 모든 태블릿이나 데스크톱에 적용해야 하는 표준 기기 크기는 아닙니다. 실제 콘텐츠와 번역문 길이, 확대 상태에서 열이 지나치게 좁아지는 지점을 보고 기준을 정합니다.

전체 예제: 한 열에서 두 열·세 열로 바꾸기

다음 코드를 responsive-layout.html로 저장해 브라우저에서 엽니다. HTML 한 파일로 실행되며 외부 라이브러리는 필요하지 않습니다. 브라우저 창을 줄이거나 개발자 도구의 반응형 화면에서 너비를 바꿔 보세요.

<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>반응형 레이아웃 실습</title>
  <style>
    *, *::before, *::after { box-sizing: border-box; }
    body {
      margin: 0;
      background: #eef2f6;
      color: #243449;
      font-family: "Noto Sans KR", sans-serif;
      line-height: 1.7;
    }
    .page {
      width: calc(100% - 32px);
      max-width: 1100px;
      margin: 24px auto;
    }
    .panel {
      min-width: 0;
      padding: 20px;
      border: 1px solid #b8c6d4;
      background: white;
      overflow-wrap: anywhere;
    }
    header.panel, footer.panel { background: #e8f0f9; }
    h1, h2 { margin-top: 0; }
    img { max-width: 100%; height: auto; }
    .layout {
      display: grid;
      grid-template-columns: minmax(0, 1fr);
      grid-template-areas: "menu" "main" "side";
      gap: 20px;
      margin-block: 20px;
    }
    .menu { grid-area: menu; }
    .content { grid-area: main; }
    .sidebar { grid-area: side; background: #fff4d9; }
    .menu ul {
      display: flex;
      flex-wrap: wrap;
      gap: 12px;
      padding: 0;
      margin: 0;
      list-style: none;
    }
    a { color: #174e94; }
    @media (min-width: 768px) {
      .layout {
        grid-template-columns: minmax(0, 1fr) 240px;
        grid-template-areas: "menu menu" "main side";
      }
    }
    @media (min-width: 1100px) {
      .layout {
        grid-template-columns: 160px minmax(0, 1fr) 240px;
        grid-template-areas: "menu main side";
      }
      .menu ul { display: block; }
      .menu li + li { margin-top: 12px; }
    }
  </style>
</head>
<body>
  <div class="page">
    <header class="panel">
      <h1>반응형 레이아웃</h1>
      <p>화면 너비를 바꾸며 콘텐츠가 읽기 좋은 배치를 확인합니다.</p>
    </header>
    <div class="layout">
      <nav class="panel menu" aria-label="페이지 목차">
        <ul>
          <li><a href="#article">본문 보기</a></li>
          <li><a href="#notice">안내 보기</a></li>
        </ul>
      </nav>
      <main class="panel content" id="article">
        <h2>본문</h2>
        <p>좁은 화면에서는 메뉴, 본문, 안내를 위에서 아래로 읽습니다.</p>
        <p>너비가 충분해지면 같은 HTML을 유지하면서 열 배치만 바꿉니다.</p>
      </main>
      <aside class="panel sidebar" id="notice">
        <h2>안내</h2>
        <p>작은 화면에서도 안내 내용은 사라지지 않고 본문 아래에 놓입니다.</p>
      </aside>
    </div>
    <footer class="panel">창의 너비를 바꿔 배치를 비교해 보세요.</footer>
  </div>
</body>
</html>

메뉴, 본문, 안내는 화면이 바뀌어도 같은 내용을 유지합니다. 좁은 화면에서는 차례대로 쌓이고, 중간 너비에서는 메뉴 아래에 본문과 안내가 나란히 놓이며, 넓은 화면에서는 세 영역이 한 행을 이룹니다.

넓은 화면에서 메뉴, 본문, 안내가 세 열로 표시된 반응형 레이아웃 예제 실행 결과

너비별 예상 결과와 확인 방법

뷰포트 너비 적용되는 배치 확인할 점
768px 미만 메뉴 → 본문 → 안내의 한 열 각 패널이 화면을 넘지 않고 내용이 모두 보이는지 확인합니다.
768px 이상, 1,100px 미만 메뉴는 위, 본문과 안내는 두 열 오른쪽 안내 열은 240px이며 본문이 남는 너비를 사용합니다.
1,100px 이상 메뉴·본문·안내의 세 열 왼쪽 160px, 오른쪽 240px, 가운데는 나머지 너비입니다.

개발자 도구에서 390px, 767px, 768px, 1,099px, 1,100px 순서로 바꿔 보세요. 특히 전환 직전과 직후를 비교해야 겹치거나 비는 범위를 찾기 쉽습니다. 가로 스크롤이 없는지, 문단이 패널 밖으로 나가지 않는지, 메뉴의 두 링크가 각각 본문과 안내로 이동하는지도 확인합니다.

.page의 width: calc(100% - 32px)는 부모의 사용 가능한 너비에서 총 32px을 뺀 값입니다. 가운데 정렬과 결합해 작은 화면에서도 양옆에 기본 여백을 남깁니다. max-width: 1100px는 큰 모니터에서 페이지가 계속 넓어지는 것을 막습니다. 세 열이 되는 기준 1,100px은 뷰포트 조건이고, 페이지가 최대 너비에 도달하는 시점과는 다릅니다.

실습 중 전환점을 바꿔 보고 싶으면 첫 번째 min-width: 768px만 900px로 수정합니다. 그러면 두 열이 시작되는 너비도 900px로 바뀝니다. 비교를 마치면 768px로 되돌려 원래 예상 결과와 다시 대조하세요.

작은 화면에서 배치가 깨질 때 점검하기

  1. viewport 메타 태그를 확인합니다. 빠져 있으면 모바일 브라우저가 넓은 가상 뷰포트를 사용해 의도한 조건이 적용되지 않는 것처럼 보일 수 있습니다.
  2. 남아 있는 고정 너비를 찾습니다. 큰 width, min-width, 고정 크기 이미지나 긴 코드가 어느 영역을 밀어내는지 요소 검사로 확인합니다.
  3. 조건과 덮어쓰기 순서를 확인합니다. 1,100px 이상에서는 두 미디어 쿼리가 모두 적용됩니다. 다른 규칙의 높은 명시도나 !important 때문에 열 설정이 덮어써졌는지도 살펴봅니다.
  4. 실제 콘텐츠와 조작을 확인합니다. 긴 제목, 큰 글자와 브라우저 확대 상태, 키보드 이동도 점검합니다. 화면에 맞추기 위해 중요한 내용을 숨기거나 글자를 지나치게 줄이는 방식은 피합니다.

미디어 쿼리의 조건과 결합 방식은 MDN의 미디어 쿼리 사용 설명에서 확인할 수 있습니다. 반응형 웹디자인 안내는 유동적인 배치와 콘텐츠를 기준으로 전환점을 정하는 방법을 설명합니다.