본문 바로가기

Grid 레이아웃

행과 열의 트랙·간격·영역을 정의하고 아이템 위치와 정렬을 제어해 반응형 2차원 페이지를 구성합니다.

앞 글에서는 Flexbox로 아이템을 한 줄이나 한 열로 배치하고 정렬했습니다. Flexbox는 내비게이션 바나 카드 목록처럼 한 방향으로 흐르는 내용에 알맞지만, 웹 페이지에는 여러 열로 나뉜 신문 기사, 대시보드, 사진 갤러리처럼 행과 열을 함께 맞춰야 하는 2차원 배치도 많습니다. 이런 배치를 위해 만든 것이 CSS Grid Layout입니다.

Grid는 이름처럼 격자 모양의 레이아웃을 만듭니다. 부모 요소에 행과 열의 구조를 정의하고, 자식 요소를 원하는 칸에 놓아 페이지 전체의 큰 틀이나 복잡한 격자 화면을 만들 수 있습니다. 이 글에서는 Grid의 기본 개념과 주요 속성을 익힌 뒤, 헤더, 내비게이션, 본문, 사이드바, 푸터로 이루어진 페이지를 Grid로 만들어 봅니다. Flexbox와 Grid는 서로를 보완하므로, 한 페이지에서 함께 쓰는 경우가 많습니다.

Grid 트랙·라인·영역과 자동 배치 모델

GRID COORDINATES

트랙은 공간이고, 라인은 그 공간의 경계입니다

네 개의 동일한 열 트랙은 다섯 경계 라인을 만듭니다. 영역을 지정한 항목과 자동 배치되는 A/B의 좌표를 비교합니다. 아래 그림은 본문의 3열 실습과 구별되는 4열 좌표 설명용 예입니다.

같은 네 열·다섯 라인의 Grid 배치네 열의 경계선은 1부터5다. header는 열1/5, sidebar는 열1/2·행2/5, main은 열2/4·행2/5다. row 자동 배치 A는 열4/5·행2/3, B는 열4/5·행3/4이며 오른쪽 아래 칸은 빈다. 모바일 보조는 같은 sidebar의 이름이며 좌표와 트랙 수가 달라진 것이 아니다. 1122334455headersidebarmainABheader: column 1/5 · sidebar: column 1/2, row 2/5main: column 2/4, row 2/5 · auto A/B: column 4/5
같은 네 열·다섯 라인의 Grid 배치네 열의 경계선은 1부터5다. header는 열1/5, sidebar는 열1/2·행2/5, main은 열2/4·행2/5다. row 자동 배치 A는 열4/5·행2/3, B는 열4/5·행3/4이며 오른쪽 아래 칸은 빈다. 모바일 보조는 같은 sidebar의 이름이며 좌표와 트랙 수가 달라진 것이 아니다.1122334455header보조mainAB보조 = sidebar열 1/2 · 행 2/5A: 열 4/5 · 행 2/3B: 열 4/5 · 행 3/4

4개 트랙 → 5개 라인

grid-template-columns: repeat(4, minmax(0, 1fr)); header의 1 / 5는 네 열 전체를 사용합니다. span은 라인 개수가 아니라 차지하는 트랙 수입니다.

  • 명시 line 배치
  • span·named area
  • auto-placement

line은 track의 경계이고 item은 line 번호 사이에 놓인다. 이 예시는 gap: 0, grid-auto-flow: row, 모든 order: 0이며 header·sidebar·main의 두 축을 지정합니다. A/B는 남은 4열의 2행·3행에 순서대로 배치됩니다. 일부 축만 지정된 item도 나머지 축의 자동 배치에 관여할 수 있습니다.

위 그림은 Grid의 좌표가 트랙과 라인으로 이루어진다는 것을 보여 줍니다. 같은 너비의 열 트랙 네 개는 1부터 5까지 다섯 개의 라인을 만들며, 아이템은 이 라인 번호 사이에 놓입니다. header는 열 라인 1에서 5까지 네 열 전체를, sidebar는 열 1에서 2, 행 2에서 5를, main은 열 2에서 4, 행 2에서 5를 차지합니다. 위치를 지정하지 않은 A와 B는 자동 배치로 남은 4열의 2행과 3행에 차례로 들어가고, 오른쪽 아래 칸은 비어 있습니다.

그림 아래의 설명처럼 이 예는 gap이 0이고 자동 배치 방향이 행 우선이며, 이 글의 세 열 실습과는 다른 네 열짜리 좌표 설명용 예입니다. span은 라인의 개수가 아니라 차지할 트랙의 수이고, 한 축만 위치를 정한 아이템은 나머지 축에서 자동 배치를 따른다는 점을 기억해 둡니다.


기본 개념

Grid도 부모와 자식의 관계로 동작합니다. display: grid나 display: inline-grid를 준 부모 요소가 Grid 컨테이너(grid container)가 되고, 그 바로 아래 자식 요소들이 Grid 아이템(grid item)이 됩니다. 컨테이너의 속성은 행과 열의 구조와 아이템 전체의 배치를, 아이템의 속성은 그 아이템이 차지할 영역을 정합니다.

Grid에서 쓰는 용어는 다음과 같습니다.

  • 그리드 라인(grid line): 트랙과 트랙을 나누는 선입니다. 한 축에 트랙을 N개 정의하면 그 축의 라인은 N + 1개이며, 번호는 1부터 셉니다.
  • 그리드 트랙(grid track): 두 라인 사이의 공간으로, 행(row)이나 열(column) 하나를 말합니다. 크기는 grid-template-columns와 grid-template-rows로 정합니다.
  • 그리드 셀(grid cell): 행 트랙 하나와 열 트랙 하나가 만나는 가장 작은 칸입니다.
  • 그리드 영역(grid area): 여러 셀을 묶은 사각형 영역입니다.

컨테이너 속성

Grid 컨테이너 만들기: display

display: grid는 블록 수준의 Grid 컨테이너를, display: inline-grid는 인라인 수준의 Grid 컨테이너를 만듭니다. 보통 grid를 씁니다.

열 정의: grid-template-columns

grid-template-columns는 열 트랙의 개수와 크기를 공백으로 나열해 정합니다. px, %, em, rem 같은 단위와 함께, Grid에는 다음 단위와 함수가 있습니다.

  • fr(fraction): 고정 크기와 간격을 빼고 남는 공간을 나누는 비율입니다. 1fr 2fr 1fr은 남는 공간을 1 대 2 대 1로 나눕니다.
  • repeat(횟수, 크기): 같은 크기의 트랙을 반복합니다. repeat(3, 1fr)은 1fr 1fr 1fr과 같습니다.
  • minmax(최소, 최대): 트랙 크기의 범위를 정합니다. minmax(100px, 1fr)은 100px보다 작아지지 않으면서 남는 공간을 나눠 가집니다.
  • auto: 내용의 크기에 맞추고, 남는 공간이 있으면 늘어납니다.

다음은 서로 다른 세 가지 열 구성의 예입니다.

/* 세 열: 첫째 200px, 둘째와 셋째는 남는 공간을 2 대 1로 */
.fixed-and-fluid {
    display: grid;
    grid-template-columns: 200px 2fr 1fr;
}

/* 같은 너비의 네 열 */
.four-columns {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
}

/* 150px 이상인 열을 들어가는 만큼 자동으로 만듦 (반응형 그리드에 자주 사용) */
.auto-columns {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}

마지막 예의 repeat(auto-fit, minmax(150px, 1fr))은 컨테이너에 150px 이상의 열이 몇 개 들어가는지 계산해 그만큼 열을 만들고, 남는 공간은 열들이 똑같이 나눠 가집니다. 컨테이너가 넓어지면 열이 늘고 좁아지면 줄어들므로, 미디어 쿼리 없이도 화면에 맞는 갤러리를 만들 수 있습니다.

더 깊이: 1fr 열의 너비가 똑같지 않을 때

repeat(3, 1fr)로 세 열을 똑같이 나눴는데, 한 열에 긴 URL이나 넓은 표가 들어가면 그 열만 넓어지고 그리드가 컨테이너 밖으로 넘치기도 합니다. Grid 명세에 따르면 minmax() 밖에 단독으로 쓴 1fr은 minmax(auto, 1fr)과 같습니다. 즉 최댓값은 남는 공간의 비율이지만, 최솟값은 auto라서 대체로 그 열에 든 아이템의 최소 내용 크기 아래로는 줄어들지 않습니다. 남는 공간을 나누기 전에 각 열이 먼저 이 최솟값을 확보하므로, 내용이 넓은 열은 다른 열보다 커집니다.

열을 내용과 상관없이 정확히 같은 비율로 나누려면 최솟값을 직접 0으로 정해 repeat(3, minmax(0, 1fr))처럼 씁니다. 그러면 열이 내용보다 좁아질 수 있으므로, 넘치는 내용은 아이템 안에서 overflow-wrap: anywhere로 줄을 바꾸거나 overflow: auto로 스크롤하게 정해 둡니다. 반대로 열이 내용 때문에 좁아지지 않기를 바란다면 1fr의 기본 동작이 오히려 알맞습니다.

앞의 minmax(150px, 1fr)은 최솟값을 150px로 직접 정했으므로 이 문제와 관계가 없습니다. 단독으로 쓴 fr의 정의는 CSS Grid 명세의 트랙 크기 절에 있습니다.

행 정의: grid-template-rows

grid-template-rows는 행 트랙을 같은 방식으로 정합니다. 적은 값의 개수가 행의 개수가 되고, 열에서 본 px, fr, auto, repeat()를 똑같이 쓸 수 있습니다.

.container {
    display: grid;
    /* 3개의 행: 첫 번째 100px, 두 번째 콘텐츠 크기만큼, 세 번째 남은 공간의 1배 */
    grid-template-rows: 100px auto 1fr;
}

auto 행은 그 행에 든 내용의 높이만큼, 1fr 행은 남는 높이를 가져갑니다. 다만 열과 달리 컨테이너의 높이는 보통 내용에 맞춰 정해지므로, height나 min-height를 따로 주지 않으면 남는 높이가 없어 1fr 행도 내용 높이만큼만 됩니다.

간격: gap

gap은 트랙 사이의 간격입니다. row-gap은 행 사이, column-gap은 열 사이의 간격이고, gap은 둘을 한 번에 정하는 단축 속성으로 gap: 20px은 행과 열 모두 20px, gap: 10px 20px은 행 사이 10px, 열 사이 20px입니다. 예전에는 grid-gap, grid-row-gap, grid-column-gap이라는 이름을 썼으며, 지금도 같은 뜻으로 동작합니다. 간격은 트랙 사이에만 생기고 바깥쪽 가장자리에는 생기지 않습니다.

.container {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: auto auto;
    gap: 20px; /* 모든 그리드 셀 사이 20px 간격 */
}

칸 안의 정렬: justify-items, align-items

justify-items는 각 칸 안에서 아이템을 인라인 축, 즉 이 교재처럼 가로로 쓰는 문서에서는 가로 방향으로 정렬하고, align-items는 블록 축인 세로 방향으로 정렬합니다. 값은 start, end, center, stretch입니다. stretch는 그 축의 크기가 auto인 아이템을 칸에 맞춰 늘립니다. 두 속성을 따로 정하지 않으면 normal이 쓰입니다. 이 값은 보통의 아이템에서 stretch처럼 동작하지만, 이미지처럼 고유한 크기나 비율을 가진 아이템은 start처럼 칸의 시작 쪽에 놓습니다.

.container {
    display: grid;
    grid-template-columns: repeat(3, 150px); /* 각 열의 너비를 150px로 고정 */
    justify-items: center; /* 각 셀 내부의 아이템들을 가로 중앙 정렬 */
}
.container {
    display: grid;
    grid-template-rows: repeat(2, 100px); /* 각 행의 높이를 100px로 고정 */
    align-items: center; /* 각 셀 내부의 아이템들을 세로 중앙 정렬 */
}

두 예 모두 트랙 크기를 고정한 데는 이유가 있습니다. 칸이 아이템보다 넓거나 높아야 정렬로 움직일 공간이 생기기 때문입니다. 예를 들어 행 높이가 auto라면 칸이 아이템 높이에 맞춰지므로, align-items: center를 주어도 눈에 띄는 변화가 없습니다.

그리드 전체의 정렬: justify-content, align-content

트랙의 크기를 모두 고정해 그리드 전체가 컨테이너보다 작으면 남는 공간이 생깁니다. 이때 justify-content는 그리드 전체를 가로 방향으로, align-content는 세로 방향으로 정렬하며, 값은 Flexbox처럼 start, end, center, space-between, space-around, space-evenly입니다. 트랙에 fr이 있으면 남는 공간을 fr이 모두 가져가므로 이 두 속성의 효과가 없습니다.

.container {
    display: grid;
    grid-template-columns: 100px 100px 100px; /* 그리드 총 너비: 300px */
    width: 500px; /* 컨테이너 너비: 500px */
    justify-content: center; /* 300px 그리드를 500px 컨테이너의 중앙에 배치 */
}
.container {
    display: grid;
    grid-template-rows: 50px 50px; /* 그리드 총 높이: 100px */
    height: 300px; /* 컨테이너 높이: 300px */
    align-content: center; /* 100px 그리드를 300px 컨테이너의 중앙에 배치 */
}

아이템 속성

라인 번호로 배치하기

grid-column-start와 grid-column-end는 아이템이 시작하고 끝날 열 라인의 번호를, grid-row-start와 grid-row-end는 행 라인의 번호를 정합니다. 단축 속성 grid-column: 시작 / 끝과 grid-row: 시작 / 끝으로 한 번에 적을 수 있고, 끝 대신 span N을 쓰면 시작 위치에서 N개의 트랙을 차지합니다.

.item-a {
    grid-column: 1 / 3; /* 첫 번째 세로 라인에서 시작하여 세 번째 세로 라인에서 끝남 (2칸 차지) */
    grid-row: 1 / 2; /* 첫 번째 가로 라인에서 시작하여 두 번째 가로 라인에서 끝남 (1칸 차지) */
}
.item-b {
    grid-column: span 2; /* 두 칸 너비를 차지 */
    grid-row: span 3; /* 세 칸 높이를 차지 */
}

.item-a는 열 라인 1에서 3까지, 즉 두 열과 그 사이의 간격을 차지하고, 행 라인 1에서 2까지 한 행을 차지합니다. 차지하는 트랙 수는 두 라인 번호의 차이로 읽습니다. .item-b는 시작 위치를 정하지 않았으므로 자동 배치가 정한 자리에서 두 열과 세 행을 차지합니다.

영역 이름으로 배치하기: grid-template-areas, grid-area

grid-template-areas는 컨테이너에서 각 칸에 이름을 붙여, 레이아웃을 그림처럼 적게 합니다. 따옴표로 감싼 문자열 하나가 한 행이고, 그 안의 이름 하나가 한 열입니다. 같은 이름을 이어 쓴 칸들은 하나의 영역이 되며, 영역은 사각형이어야 합니다.

.container {
    display: grid;
    grid-template-columns: 1fr 2fr 1fr;
    grid-template-rows: auto 1fr auto;
    grid-template-areas:
        "header header header" /* 첫 번째 행: 모두 header 영역 */
        "nav    main   aside"  /* 두 번째 행: nav, main, aside */
        "footer footer footer"; /* 세 번째 행: 모두 footer 영역 */
}

문자열 안에서 이름 사이의 공백은 몇 칸이든 상관없으므로, "nav main aside"처럼 칸을 맞춰 적으면 표처럼 읽기 쉽습니다. 문자열 옆의 주석은 설명을 위해 붙인 것으로, 동작에는 영향이 없습니다.

아이템에는 grid-area로 영역의 이름을 적어 그 자리에 놓습니다. 이름은 따옴표 없이 적으며, HTML에서 요소가 어떤 순서로 나오든 이 이름이 가리키는 자리에 배치됩니다.

header { grid-area: header; }
nav { grid-area: nav; }
main { grid-area: main; }
aside { grid-area: aside; }
footer { grid-area: footer; }

이 방식은 CSS 코드만 봐도 레이아웃의 모양이 드러나 읽고 고치기 쉽습니다. 빈칸으로 두고 싶은 칸에는 이름 대신 .을 적습니다.

아이템 하나의 정렬: justify-self, align-self

justify-self와 align-self는 아이템 하나를 자기 칸 안에서 가로와 세로로 정렬하며, 컨테이너의 justify-items와 align-items보다 우선합니다. 값은 start, end, center, stretch입니다.


실습: Grid로 만드는 반응형 페이지

헤더, 내비게이션, 본문, 사이드바, 푸터가 있는 전형적인 페이지를 Grid로 만들고, 좁은 화면에서는 한 열로 바뀌게 해 봅니다. 다음 구조로 파일을 준비합니다.

grid_layout.html
grid.css

grid_layout.html에는 다음 코드를 씁니다. 본문 안의 갤러리에는 사진 대신 색칠한 상자 네 개를 넣어, 사진 파일 없이도 그리드 안의 그리드를 확인할 수 있게 했습니다.

grid_layout.html
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Grid 레이아웃 실습</title>
    <link rel="stylesheet" href="css/grid.css">
</head>
<body>
    <div class="grid-container">
        <header class="header">
            <h1>내 Grid 웹사이트</h1>
            <p>CSS Grid로 만드는 멋진 레이아웃</p>
        </header>

        <nav class="navbar">
            <ul>
                <li><a href="#">홈</a></li>
                <li><a href="#">제품</a></li>
                <li><a href="#">소개</a></li>
                <li><a href="#">문의</a></li>
            </ul>
        </nav>

        <main class="main-content">
            <h2>환영합니다!</h2>
            <p>이곳은 CSS Grid Layout을 이용해 만들어진 반응형 웹 페이지입니다. Grid는 2차원 레이아웃에 특화되어 복잡한 웹 페이지 구조를 효율적으로 설계할 수 있게 해줍니다.</p>
            <div class="image-gallery">
                <div class="gallery-item">사진 1</div>
                <div class="gallery-item">사진 2</div>
                <div class="gallery-item">사진 3</div>
                <div class="gallery-item">사진 4</div>
            </div>
            <h3>Grid의 장점</h3>
            <ul>
                <li>복잡한 2차원 레이아웃 설계 용이</li>
                <li>아이템들의 위치와 크기 정교하게 제어</li>
                <li>반응형 디자인에 유리</li>
            </ul>
        </main>

        <aside class="sidebar">
            <h3>관련 정보</h3>
            <p>Grid Layout은 Flexbox와 함께 현대 웹 개발의 필수 기술입니다.</p>
            <button type="button">자세히 보기</button>
        </aside>

        <footer class="footer">
            <p>&copy; 2026 Grid Layout 학습. 모든 권리 보유.</p>
        </footer>
    </div>
</body>
</html>

.grid-container 바로 아래의 header, nav, main, aside, footer 다섯 요소가 바깥 그리드의 아이템이고, 본문 안의 .image-gallery가 그 안에서 또 하나의 그리드가 됩니다.

css/grid.css에는 다음 CSS를 씁니다. .grid-container의 grid-template-areas가 페이지 전체의 배치를 정하고, 각 아이템은 클래스 선택자로 header, navbar, main, sidebar, footer 중 하나의 영역 이름을 받습니다. 맨 아래 @media 규칙은 화면 너비가 768px 이하일 때 하나의 열에 모든 영역을 세로로 쌓도록 배치를 바꿉니다.

css/grid.css
/* 기본 스타일 및 박스 사이징 */
* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: #f0f2f5;
    color: #333;
    line-height: 1.6;
}

/* Grid 컨테이너 설정 */
.grid-container {
    display: grid; /* Grid 활성화 */
    gap: 20px; /* 그리드 셀 사이의 간격 */
    min-height: 100vh; /* 최소 높이를 뷰포트 높이로 설정 */
    max-width: 1200px; /* 최대 너비 제한 */
    margin: 0 auto; /* 중앙 정렬 */

    /* 그리드 영역 이름 정의 및 배치 */
    grid-template-areas:
        "header  header  header"
        "navbar  main    sidebar"
        "footer  footer  footer";

    /* 각 열의 너비 정의 */
    grid-template-columns: 200px 1fr 250px; /* nav 200px, main 가변, sidebar 250px */
    /* 각 행의 높이 정의 */
    grid-template-rows: auto 1fr auto; /* header, footer는 콘텐츠만큼, 가운데 행은 남은 공간 모두 */
}

/* 그리드 아이템 기본 스타일 */
.grid-container > * {
    background-color: white;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

/* Grid 아이템을 정의된 영역에 배치 */
.header {
    grid-area: header;
    background-color: #2c3e50;
    color: white;
    text-align: center;
    padding: 30px;
}

.header h1 {
    font-size: 2.8em;
    margin-bottom: 10px;
}

.navbar {
    grid-area: navbar;
    background-color: #34495e;
    color: white;
}
.navbar ul {
    list-style: none;
    padding: 0;
}
.navbar li {
    margin-bottom: 10px;
}
.navbar a {
    display: block; /* 링크 영역 확장 */
    color: white;
    text-decoration: none;
    padding: 10px;
    border-radius: 5px;
    transition: background-color 0.3s ease;
}
.navbar a:hover {
    background-color: #4a6581;
}

.main-content {
    grid-area: main;
}
.main-content h2 {
    color: #2980b9;
    margin-bottom: 15px;
    border-bottom: 2px solid #2980b9;
    padding-bottom: 5px;
}
.main-content h3 {
    color: #4a6581;
    margin-top: 25px;
    margin-bottom: 10px;
}
.main-content ul {
    list-style-type: disc;
    margin-left: 20px;
}
.main-content li {
    margin-bottom: 5px;
}

/* 이미지 갤러리 (그리드 안의 그리드) */
.image-gallery {
    display: grid; /* 갤러리 내부를 다시 그리드로 */
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); /* 최소 180px, 가변 열 */
    gap: 15px;
    margin: 20px 0;
    padding: 15px;
    background-color: #f9f9f9;
    border-radius: 5px;
}
.gallery-item {
    display: grid;
    place-items: center; /* 글자를 상자 가운데에 */
    height: 120px;
    background-color: #dfe6e9; /* 사진 자리를 나타내는 회색 상자 */
    border-radius: 5px;
    color: #4a6581;
}

.sidebar {
    grid-area: sidebar;
    background-color: #ecf0f1;
}
.sidebar h3 {
    color: #2c3e50;
    margin-bottom: 15px;
}
.sidebar button {
    background-color: #28a745;
    color: white;
    padding: 10px 20px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    margin-top: 15px;
    transition: background-color 0.3s ease;
}
.sidebar button:hover {
    background-color: #218838;
}

.footer {
    grid-area: footer;
    background-color: #34495e;
    color: white;
    text-align: center;
    padding: 20px;
    font-size: 0.9em;
}

/* 반응형 디자인: 화면 너비 768px 이하 */
@media (max-width: 768px) {
    .grid-container {
        /* 모바일에서는 모든 영역을 하나의 열에 세로로 쌓음 */
        grid-template-areas:
            "header"
            "navbar"
            "main"
            "sidebar"
            "footer";
        grid-template-columns: 1fr; /* 하나의 열만 사용 */
        grid-template-rows: auto auto 1fr auto auto; /* 각 영역 높이 조정 */
        gap: 15px; /* 간격도 조금 줄임 */
    }

    .header, .navbar, .main-content, .sidebar, .footer {
        padding: 15px; /* 패딩도 줄임 */
    }

    .navbar ul {
        display: flex; /* 내비게이션 메뉴를 Flexbox로 가로 정렬 */
        flex-wrap: wrap; /* 공간 부족 시 줄바꿈 */
        justify-content: center;
        gap: 10px;
    }
    .navbar li {
        margin-bottom: 0;
    }
    .navbar a {
        padding: 8px 12px;
        font-size: 0.9em;
    }

    .image-gallery {
        grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); /* 모바일 갤러리 조정 */
        gap: 10px;
    }
}

Live Server로 grid_layout.html을 열면 헤더가 맨 위를 가로지르고, 그 아래에 내비게이션 200px, 본문, 사이드바 250px이 나란히 놓이며, 맨 아래를 푸터가 가로지릅니다. CSS의 grid-template-areas에 적은 세 줄이 화면의 모양과 그대로 대응하는 것을 확인해 보세요. 가운데 행은 1fr이라, 내용이 짧아도 페이지가 화면 높이를 채우고 푸터가 화면 아래쪽에 놓입니다.

본문의 갤러리는 그 자체가 다시 Grid 컨테이너입니다. 넓은 화면에서 본문 영역은 최대 1200 − 200 − 250 − 40 = 710px이고, 본문과 갤러리의 안쪽 여백을 빼면 갤러리의 내용 폭은 최대 640px입니다. 그래서 180px 이상의 열은 많아야 세 개 들어가고, 창 너비에 따라 세 상자와 한 상자, 두 상자씩 두 줄, 또는 한 줄에 한 상자씩 놓입니다. 네 상자가 한 줄에 놓일 수 있는 것은 768px 이하에서 열의 최솟값이 120px로 줄어든 뒤입니다. 브라우저 창을 768px 이하로 줄이면 미디어 쿼리가 영역의 배치를 한 열로 바꿔 다섯 영역이 세로로 쌓이고, 내비게이션 메뉴는 Flexbox로 가로로 놓입니다. 사이드바의 버튼은 모양만 있고 동작은 없습니다.


정리

  • display: grid를 준 부모가 Grid 컨테이너, 그 바로 아래 자식이 Grid 아이템이 되며, 트랙 N개는 라인 N + 1개를 만듭니다.
  • grid-template-columns와 grid-template-rows로 트랙을 정하며, fr은 남는 공간의 비율, repeat()는 반복, minmax()는 크기의 범위입니다.
  • repeat(auto-fit, minmax(최소, 1fr))은 들어가는 만큼 열을 만들어 미디어 쿼리 없이도 화면에 맞춥니다.
  • gap은 트랙 사이의 간격이고, justify-items와 align-items는 칸 안, justify-content와 align-content는 그리드 전체를 정렬합니다.
  • 아이템은 grid-column, grid-row의 라인 번호와 span으로, 또는 grid-template-areas와 grid-area의 이름으로 배치합니다.
  • Flexbox는 한 방향의 나열에, Grid는 행과 열을 함께 맞추는 배치에 알맞으며, 한 페이지에서 함께 씁니다.

연습 문제

1. 열의 너비 계산하기

가로로 쓰는 컨테이너의 내용 폭이 650px이고, grid-template-columns: 200px 2fr 1fr, column-gap: 20px입니다. 내용의 최소 크기에 따른 제한은 없다고 할 때, 세 열의 너비와 열 라인의 개수, grid-column: 1 / 3이 차지하는 트랙 수를 구해 보세요.

해설 보기

열 사이의 간격 두 개가 40px, 고정 열이 200px이므로 fr로 나눌 공간은 650 − 40 − 200 = 410px입니다. 둘째 열은 410 × 2/3 ≈ 273.33px, 셋째 열은 410 × 1/3 ≈ 136.67px입니다.

트랙이 셋이므로 라인은 넷입니다. 1 / 3은 라인 1에서 3까지라 두 트랙과 그 사이의 간격 20px을 덮습니다. “3번째 열까지”가 아니라, 두 라인 번호의 차이인 2를 트랙 수로 읽습니다.

2. 내비게이션을 오른쪽으로

실습의 넓은 화면에서 HTML은 그대로 두고 내비게이션을 오른쪽에, 사이드바를 왼쪽에 놓으려고 합니다. CSS에서 무엇을 바꿔야 할까요? 바꾼 뒤 키보드의 Tab 순서는 어떻게 될까요?

해설 보기

grid-template-areas의 두 번째 행을 "sidebar main navbar"로 바꿉니다. 영역의 위치만 바꾸면 왼쪽 열은 여전히 200px, 오른쪽 열은 250px이므로, 각 영역의 원래 너비를 유지하려면 열 선언도 250px 1fr 200px로 바꿉니다. 768px 이하의 한 열 배치는 그대로 둡니다.

Tab 순서는 바뀌지 않습니다. 화면에서는 사이드바가 먼저 보이지만, 초점은 HTML의 순서대로 내비게이션의 링크에 먼저 간 뒤 사이드바의 버튼으로 갑니다. 보이는 순서와 읽는 순서가 크게 어긋나면 사용자가 혼란스러울 수 있으므로, 의미상 순서까지 바꿔야 한다면 HTML의 순서를 바꿉니다.

3. 갤러리의 열 수

본문 영역의 내용 폭이 600px일 때 실습의 갤러리에는 한 줄에 상자가 몇 개 놓일까요? 갤러리에는 안쪽 여백 15px이 있고, gap은 15px입니다.

해설 보기

갤러리의 내용 폭은 600 − 15 × 2 = 570px입니다. 180px 열이 n개 들어가려면 180n + 15(n − 1) ≤ 570이어야 하고, n = 3이면 540 + 30 = 570으로 꼭 맞습니다. 그래서 한 줄에 세 상자가 놓이고, 네 번째 상자는 다음 줄로 갑니다.

세 열은 남는 공간이 없으므로 각각 정확히 180px입니다. 본문이 조금만 좁아져도 두 열로 바뀌므로, 창의 너비를 바꿔 가며 열의 수가 바뀌는 지점을 확인해 보세요.

다음 글 반응형 웹 디자인 기초에서는 뷰포트와 미디어 쿼리, 유연한 단위로 화면 크기에 맞춰 바뀌는 페이지를 만드는 방법을 다룹니다.