CSS 강좌 / Grid 반복 열·자동 배치·영역 이름 사용하기
Grid의 repeat()는 같은 열 정의를 반복하고, auto-fit과 auto-fill은 컨테이너에 들어갈 열 수를 자동으로 정합니다. 이름으로 큰 구역을 배치하려면 grid-template-areas를 사용하며, 자동 배치와 영역 이름은 서로 다른 문제를 해결하는 기능입니다.
고정 반복과 자동 반복을 구분하기
.cards {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
}
repeat(3, ...)는 화면 크기와 관계없이 세 열을 정의합니다. 같은 선언을 세 번 쓰는 것과 의미가 같습니다. 항목 수가 네 개이면 세 열을 유지하면서 다음 행에 네 번째 항목이 들어갑니다. 카드 수에 맞춰 열 수도 네 개로 늘어난다는 뜻은 아닙니다.
.cards {
grid-template-columns: repeat(auto-fit, minmax(min(100%, 192px), 1fr));
}
auto-fit은 지정한 최소 크기의 열이 몇 개 들어갈지 계산합니다. minmax의 첫 값은 최소, 둘째 값은 최대이며 1fr은 남는 공간을 나누어 갖게 합니다. 여기서 min(100%, 192px)는 최소 열 너비가 부모보다 커지지 않도록 합니다. 열의 최소값을 192px로만 고정하면 부모가 192px보다 좁을 때 한 열조차 부모 밖으로 넘칠 수 있습니다.
예를 들어 800px 컨테이너에서 최소 192px 열과 16px gap을 사용하면 세 열과 두 gap은 608px, 네 열과 세 gap은 816px입니다. 따라서 세 열까지 들어갈 수 있습니다. 이것은 열의 수를 결정하는 단계이고, 이후 실제 열 너비는 남는 공간을 분배하는 단계에서 달라집니다. 카드에 긴 문자열이 있으면 열 수와 별개로 줄바꿈 규칙도 확인해야 합니다.
auto-fill과 auto-fit을 적은 항목으로 비교하기
| 값 | 아이템 배치 후 빈 반복 트랙 | 항목이 적은 넓은 화면 |
|---|---|---|
| auto-fill | 빈 열도 유지 | 빈 열의 공간이 남음 |
| auto-fit | 아이템이 없는 반복 트랙을 접음 | 사용 중인 열이 남는 공간을 더 채움 |
두 값 모두 처음에는 들어갈 수 있는 반복 열을 계산합니다. auto-fit의 차이는 배치 후 비어 있는 반복 트랙을 접는 과정에 있습니다. 항목이 모든 열을 채우면 두 값이 비슷하게 보일 수 있으므로 비교할 때는 컨테이너를 넓게 만들고 카드 수를 적게 두세요. 모든 카드가 작은 화면 한 열에 들어가는 조건에서도 차이가 잘 보이지 않습니다. MDN의 repeat() 설명에서 자동 반복 규칙을 확인할 수 있습니다.
자동 배치와 dense의 순서 문제 이해하기
위치를 지정하지 않은 아이템은 기본 grid-auto-flow: row에 따라 행 방향으로 배치됩니다. 앞의 아이템이 넓은 영역을 차지해 빈 칸이 생기더라도 기본 배치는 다음 아이템을 배치하면서 지나간 빈 칸으로 계속 되돌아가지 않습니다. 이 기본 동작은 HTML 순서와 시각적인 진행을 이해하기 쉽게 만듭니다.
grid-auto-flow: row dense는 뒤의 작은 아이템을 앞의 빈 칸에 채울 수 있습니다. 공간은 촘촘해지지만 화면에서 보이는 순서와 DOM(Document Object Model)의 순서가 달라질 수 있습니다. CSS 배치는 HTML을 재정렬하지 않으므로 키보드 포커스와 읽기 순서를 함께 검토해야 합니다. 번호가 의미 있는 단계, 시간순 기록, 양식 항목에서는 빈 칸을 없애는 것보다 순서를 지키는 것이 우선입니다. MDN의 grid-auto-flow 설명에서 자동 배치 조건을 확인할 수 있습니다.
전체 예제로 반복 열과 자동 배치 실습하기
아래 코드를 grid-auto-repeat.html로 저장해 엽니다. 첫 구역에는 카드가 두 개뿐이고 컨테이너 최대 너비는 800px입니다. 둘째 구역은 두 칸짜리 링크 두 개와 한 칸짜리 링크 두 개로 빈 칸을 만들었습니다.
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>grid-auto-repeat.html</title>
<style>
* { box-sizing: border-box; }
body { margin: 16px; font: 16px/1.6 sans-serif; color: #243447; }
h1 { font-size: 24px; } h2 { font-size: 20px; margin-top: 32px; }
a { color: #145f8f; } a:focus-visible { outline: 3px solid #b4470b; outline-offset: 3px; }
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 192px), 1fr));
gap: 16px; width: min(100%, 800px); }
.cards > div { min-width: 0; padding: 16px; background: #dceefa; overflow-wrap: anywhere; }
.flow { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px;
grid-auto-flow: row; max-width: 600px; margin-top: 16px; }
.flow > a { padding: 12px; background: #e8f5de; }
.flow > .wide { grid-column: span 2; }
</style>
</head>
<body>
<h1>빈 반복 열 비교</h1><div class="cards"><div>첫 카드</div><div>둘째 카드</div></div>
<h2>기본 자동 배치</h2><div class="flow"><a class="wide" href="#result">1: 두 칸</a><a class="wide" href="#result">2: 두 칸</a><a href="#result">3: 한 칸</a><a href="#result">4: 한 칸</a></div>
<p id="result">자동 배치 링크의 이동 대상입니다.</p>
</body>
</html>
- 넓은 화면에서 첫 구역이 800px이면 auto-fit의 두 카드가 각각 392px를 차지합니다. auto-fit만 auto-fill로 바꾸면 세 열이 유지되어 첫 두 열의 카드는 약 256px이고 빈 열이 남습니다. 확인 후 auto-fit으로 복원합니다.
- 첫 구역에 div를 더 복사해 네 개로 만듭니다. 세 열이 채워지고 네 번째 카드가 다음 행에 놓입니다. 이 조건에서는 비어 있는 반복 열이 없어 두 값의 차이가 줄어듭니다. 실습 후 원래 두 카드로 돌립니다.
- 화면을 좁혀 부모 너비가 192px보다 작아지도록 확인합니다. min(100%, 192px) 덕분에 한 열이 부모에 맞습니다. 최소값을 192px로만 바꾸면 더 좁은 영역에서 넘칠 수 있습니다. 비교 후 원래 식을 복원합니다.
- .flow에서 1번 링크 뒤의 빈 칸과 3번 링크 위치를 확인합니다. grid-auto-flow만 row dense로 바꾸면 3번이 앞의 빈 칸에 올라갑니다. Tab으로는 1 → 2 → 3 → 4 순서가 유지되는지 확인합니다. 실습 후 row로 되돌립니다.
영역 이름으로 페이지 구조 만들기
열·행 번호를 각각 계산하기보다 header·main·aside·footer 같은 이름으로 큰 구역을 표현하면 페이지의 배치 의도를 읽기 쉽습니다. grid-template-areas의 각 문자열은 한 행이고, 문자열 안의 공백으로 구분한 이름은 셀입니다. 자식의 grid-area 값이 그 이름과 일치해야 해당 영역에 들어갑니다.
같은 이름을 여러 셀에 반복하면 하나의 직사각형 영역을 만듭니다. 영역은 L자나 떨어진 섬 모양으로 지정할 수 없습니다. 각 행은 같은 셀 수를 가져야 하고 비워 둘 셀은 점(.)으로 표시합니다. 선언이 유효하지 않으면 브라우저가 해당 선언을 적용하지 않아 자동 배치처럼 보일 수 있습니다. MDN의 grid-template-areas 설명에서 유효한 영역 조건을 확인할 수 있습니다.
아래 코드는 grid-areas.html로 저장합니다. 데스크톱에서는 본문과 보조 정보가 옆으로 놓이고 640px 이하에서는 한 열로 바뀝니다. 화면 배치에 맞추어 HTML 순서를 뒤집지 않고 본문이 보조 정보보다 앞에 오도록 구성했습니다.
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>grid-areas.html</title>
<style>
* { box-sizing: border-box; }
body { margin: 16px; font: 16px/1.6 sans-serif; color: #243447; }
h1 { font-size: 24px; } h2 { font-size: 20px; margin-top: 32px; }
a { color: #145f8f; } a:focus-visible { outline: 3px solid #b4470b; outline-offset: 3px; }
.page { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
grid-template-areas: "header header" "main aside" "footer footer"; gap: 16px; max-width: 1000px; }
.page > * { min-width: 0; padding: 20px; background: #dceefa; overflow-wrap: anywhere; }
header { grid-area: header; } main { grid-area: main; } aside { grid-area: aside; } footer { grid-area: footer; }
@media (max-width: 640px) {
.page { grid-template-columns: minmax(0, 1fr); grid-template-areas: "header" "main" "aside" "footer"; }
}
</style>
</head>
<body>
<h1>페이지 영역 이름</h1><div class="page"><header>사이트 제목</header><main><h2>주요 콘텐츠</h2><p>본문 영역입니다. 모바일에서도 본문이 먼저 나오도록 HTML 순서를 구성했습니다.</p></main><aside>관련 정보</aside><footer>연락처와 사이트 안내</footer></div>
</body>
</html>
- 넓은 화면에서 header와 footer가 두 열을 차지하는지 확인합니다. 화면을 640px 이하로 좁히면 header → main → aside → footer가 한 열로 이어집니다. 열 정의와 영역 문자열을 함께 바꿔야 하는 이유를 확인하세요.
- aside의 grid-area만 다른 이름으로 바꿉니다. 템플릿에서 예약한 aside 영역과 실제 요소의 연결이 끊겨 의도한 칸에 놓이지 않을 수 있습니다. 이름을 aside로 복원합니다.
- 넓은 화면 템플릿만 "header header" "header aside" "footer footer"로 바꾸면 header가 L자라 해당 grid-template-areas 선언이 유효하지 않습니다. 개발자 도구에서 적용 여부를 확인한 뒤 원래 세 문자열을 복원합니다.
실무에서는 카드 수에 따라 열을 만드는 영역과 페이지 구역 이름을 지정하는 영역을 분리하면 관리하기 쉽습니다. 자동 반복은 개수와 최소 너비를, 영역 이름은 페이지 구조와 전환점을 결정합니다. 둘을 한 규칙에 억지로 섞기 전에 각 컨테이너가 해결하려는 문제가 무엇인지 먼저 정하세요.










