04 — Grid Placement (line / span / area / implicit / dense)

이 문서가 답하는 질문: Grid 항목은 어떻게 격자에 배치되며, line / span / area / implicit / dense는 각각 무엇을 푸는가?


한 줄 답

Grid 항목은 시작/끝 라인 번호로 배치된다. grid-column: 2 / 4(2부터 4 직전까지) 또는 grid-column: 2 / span 2(2부터 2칸). 명시되지 않은 항목은 **암시적 그리드(implicit grid)**가 받아내며, grid-auto-flow: dense는 빈 셀을 채우기 위해 항목 순서를 재배치한다.


Why — 배치 모델

flex처럼 순서대로 흐르기만 하면 충분할 때도 많다. 하지만 다음 요구를 한 번에 풀려면 라인 기반 배치가 필요하다.

  1. “이 항목은 1열에서 3열까지 차지” — 가로 span.
  2. “이 카드는 항상 마지막 행” — 음수 라인 인덱스(-1)로 표현.
  3. “빈 셀을 자동으로 채우되, 순서는 깨지지 않게”auto-flow 기본.
  4. “빈 셀이 있으면 작은 항목이 끼어들어가 채우게”auto-flow: dense.

Flex로는 (1)은 가능하지만 (2)~(4)는 불가능하다.


How — 라인과 셀

1) 라인 번호

n개 컬럼이면 라인은 n+1개 — 1이 시작, n+1이 끝. 음수 인덱스는 끝에서부터-1이 마지막 라인.

2) grid-column / grid-row

.item {
  grid-column: 1 / 3;        /* line 1부터 line 3 직전까지 = 2칸 */
  grid-column: 1 / span 2;   /* line 1부터 2칸 — 동일 결과 */
  grid-column: span 2;       /* 2칸, 시작은 auto-flow가 결정 */
  grid-column: 1 / -1;       /* 1부터 끝까지 (full width) */
  grid-row: 2 / 4;
}

shorthand:

.item {
  grid-area: 2 / 1 / 4 / 3;  /* row-start / col-start / row-end / col-end */
}

3) 이름 있는 라인

.grid {
  grid-template-columns: [content-start] 1fr [content-end gutter-start] 200px [gutter-end];
}
.item {
  grid-column: content-start / content-end;
}

repeat()와 결합:

grid-template-columns: repeat(3, [col-start] 1fr [col-end]);
/* col-start 1, col-start 2, col-start 3 자동 생성 */
.item { grid-column: col-start 2 / col-end 3; }

4) template-areas 이름

.app {
  grid-template-areas: "sidebar main";
}
.sidebar { grid-area: sidebar; }

grid-area이름이거나 4-line shorthand — 형태로 구분된다.

5) Implicit Grid

명시 안 한 트랙은 암시적으로 생성된다.

.grid {
  display: grid;
  grid-template-columns: 200px 1fr;  /* 명시 2개 컬럼 */
  /* grid-template-rows 없음 */
  grid-auto-rows: minmax(80px, auto); /* 암시 행의 크기 */
  grid-auto-columns: 100px;            /* 항목이 컬럼을 넘으면 암시 컬럼 생성 */
  grid-auto-flow: row;                 /* 새 항목을 행 우선으로 채움 (기본) */
}
속성의미
grid-auto-rows암시 행의 기본 크기
grid-auto-columns암시 컬럼의 기본 크기
grid-auto-flow자동 배치 알고리즘 — row / column / dense / row dense

6) auto-flow: dense

기본 auto-flow항목을 DOM 순서대로 배치하며, 큰 항목 때문에 빈 셀이 생기면 비워둔다.

dense빈 셀을 채우기 위해 뒤 항목을 앞으로 당긴다 — 시각적 순서가 DOM 순서와 달라짐.

갤러리·Pinterest 류에서 시각 밀도를 높이는 패턴. 다만 키보드/스크린리더 순서가 시각 순서와 어긋남에 주의.


What — 항목 정렬

Grid 항목 속성

속성의미
grid-column / grid-rowline 기반 배치
grid-column-start/end, grid-row-start/end분리 형
grid-area이름 또는 4-line shorthand
justify-self셀 내부 main 축(가로) 정렬
align-self셀 내부 cross 축(세로) 정렬
place-selfalign-self justify-self shorthand
order자동 배치 시 순서 (DOM 순서와 별개)

컨테이너 정렬 속성

속성의미
justify-items모든 항목의 셀 내부 가로 정렬 (기본)
align-items셀 내부 세로 정렬
place-itemsshorthand
justify-content트랙 전체가 컨테이너보다 작을 때 컨테이너 내부 가로 정렬
align-content동일, 세로
place-contentshorthand

Grid에서는 justify-items / justify-selfFlex와 달리 동작한다 (Flex는 무시). Grid는 셀이 항상 정의되므로 셀 내부 정렬이 의미 있다.

기본값

  • justify-items, align-items 기본 stretch — 항목이 셀 전체를 채움.
  • Flex와 마찬가지로 이미지 squashing 발생 — align-items: start 또는 align-self: start로 회피.

What-if — 잘못 쓰면

1) grid-column: 1 / 3인데 항목이 1칸만 차지한다

원인: 컬럼이 1개뿐이면 line 3이 없어 implicit grid가 생성된다. 결과적으로 항목이 1칸으로 보이거나, 새 컬럼이 생기며 다른 항목을 밀어낸다.

해결: grid-template-columns에 명시된 트랙 수 확인.

2) grid-area 이름이 안 먹는다

원인: grid-template-areas에 그 이름이 없거나 오타. 빈 칸은 . 필수.

해결: 이름이 사각형을 이루는지, 모든 행 문자열의 셀 수가 같은지 확인.

3) Implicit row가 너무 작다

원인: grid-auto-rows가 기본 auto — 콘텐츠 폭만. 디자인 시스템상 최소 행 높이를 원하면 grid-auto-rows: minmax(80px, auto).

4) dense로 갤러리를 짰는데 키보드 포커스가 어색하다

원인: dense는 시각 배치만 재정렬, DOM(=tab) 순서는 그대로.

해결:

  • DOM 순서를 의미 있게 유지.
  • 또는 dense 포기하고 빈 셀 허용.
  • 또는 display: grid + subgrid로 더 정밀한 배치 (06-subgrid).

5) -1 라인이 의도와 다르다

원인: -1명시 트랙의 마지막. implicit 트랙으로 grid가 늘어나도 -1은 변하지 않는다 (예상과 반대).

해결: 끝까지 채우려면 grid-template-columns를 명시적으로 충분히 정의, 또는 grid-column: 1 / -1 대신 grid-column: 1 / span 12 같이 명시.


Insight — line vs area, 무엇이 먼저인가

CSSWG는 두 가지 배치 문법을 의도적으로 같이 제공했다.

  • Line 기반 (grid-column: 2 / 4) — 계산적, JS로 동적으로 만들 때 유리.
  • Area 기반 (grid-area: main) — 선언적, 디자이너가 와이어프레임 그리듯 표현.

같은 결과를 두 방식으로 다 표현할 수 있다. 어느 쪽이 낫다기보다 변하는 차원이 다르다.

상황권장
페이지 골격(header/sidebar/main/footer)template-areas (선언적)
카드 그리드, 갤러리, 12-columnrepeat() + line/span (계산적)
동적으로 항목 위치 바뀜 (JS)grid-row/column 직접
미디어 쿼리로 레이아웃 재배치template-areas (다시 그리기 쉬움)

흥미로운 패턴: 반응형 area 재배치.

.app {
  grid-template-areas:
    "header"
    "main"
    "sidebar"
    "footer";
}
@media (min-width: 768px) {
  .app {
    grid-template-columns: 200px 1fr;
    grid-template-areas:
      "header  header"
      "sidebar main"
      "footer  footer";
  }
}

자식 CSS는 한 줄도 안 바뀐다. 이게 Grid가 약속한 진짜 반응형.

auto-flow: dense의 역사도 재밌다 — 2014년 스펙 초안에서는 기본이었다가, 순서가 어긋나는 게 더 큰 사고라는 피드백으로 opt-in이 됐다. 같은 논리로 min-width: autoopt-in이 아니라 opt-out인 것과 대비된다.


요약 + Mermaid

  • 항목은 line 번호 또는 area 이름으로 배치 — 1 / 3, 1 / span 2, 1 / -1, grid-area: name.
  • 명시 안 한 트랙은 implicit grid가 생성 — grid-auto-rows/columns/flow로 제어.
  • auto-flow: dense는 빈 셀 채움 — 시각/DOM 순서가 어긋남에 주의.
  • Grid에서는 justify-items / justify-self가 의미가 있다 (Flex와 다름).

다음: 05-intrinsic-sizing — minmax, auto-fill vs auto-fit.