CSS Grid 상세 설명: 2차원 레이아웃을 제대로 이해하고 쓰는 방법

2026.07.28·약 13분
요약
CSS Grid는 화면을 행과 열로 나누어 설계하는 2차원 레이아웃 방식이다. 이 글에서는 grid-template-columns, fr, repeat(), minmax(), grid-area 같은 핵심 개념을 실무 레이아웃 관점에서 설명한다.

CSS Grid는 어떤 문제를 해결하는가

CSS Grid의 행, 열, 셀, 간격 구조를 설명하는 다이어그램

CSS Grid를 이해할 때 가장 먼저 버려야 할 생각은 이것을 단순히 “몇 열짜리 칸을 만드는 문법”으로 보는 것이다. CSS Grid는 행과 열을 동시에 설계하고, 그 안에 요소를 배치하는 레이아웃 시스템이다. 즉, 가로 방향만 신경 쓰는 것이 아니라 세로 흐름까지 함께 고려한다.

예를 들어 카드 목록을 3열로 놓는 화면, 왼쪽에는 사이드바가 있고 오른쪽에는 본문이 있는 화면, 위젯이 여러 크기로 섞여 있는 대시보드, 이미지가 일정한 간격으로 정렬되는 갤러리를 떠올려 보자. 이런 UI는 단순히 요소를 한 줄로 나열하는 문제가 아니다. 각 영역의 너비, 높이, 간격, 줄바꿈, 위치 관계가 함께 맞아야 한다.

flex도 레이아웃을 만들 수 있지만 기본적으로 한 방향 배치에 강하다. flex-directionrow이면 가로 흐름이 중심이고, column이면 세로 흐름이 중심이다. 반면 grid는 처음부터 행과 열을 모두 가진 판을 만든다. 그래서 화면 전체 구조나 카드 목록처럼 가로와 세로의 규칙이 동시에 필요한 상황에서 더 자연스럽다.

판단 기준은 비교적 단순하다. “이 요소들이 한 줄 안에서 어떻게 나란히 놓일까?”가 핵심이면 flex를 먼저 생각하고, “전체 영역을 몇 개의 행과 열로 나누고 각 요소를 어느 칸에 둘까?”가 핵심이면 grid를 먼저 생각하면 된다. 버튼 안의 아이콘과 텍스트 정렬은 flex가 편하고, 관리자 대시보드의 큰 구조는 grid가 편한 이유가 여기에 있다.

Grid의 기본 구조 이해하기

CSS Grid는 부모 요소와 자식 요소의 관계에서 시작한다. 부모 요소에 display: grid를 지정하면 그 요소는 grid container가 되고, 바로 아래 자식 요소들은 grid item이 된다. 중요한 점은 모든 후손 요소가 자동으로 grid item이 되는 것이 아니라, 직접 자식만 배치 대상이 된다는 것이다.

.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
}

위 예시에서 .cardsgrid container이고, 그 안의 직접 자식 카드들은 grid item이다. grid-template-columns는 열을 정의하고, gap은 아이템 사이의 간격을 만든다. 이때 repeat(3, 1fr)는 같은 크기의 열을 3개 만들겠다는 뜻이다.

Grid를 설명할 때 자주 나오는 용어도 함께 정리해 두면 이후 속성이 훨씬 쉽게 읽힌다. row는 행, column은 열, cell은 행과 열이 만나는 한 칸이다. track은 하나의 행 또는 하나의 열을 의미하고, grid line은 각 행과 열을 나누는 경계선이다. gap은 칸 사이의 간격이다.

처음에는 이 용어들이 많아 보이지만, 실제로는 표를 떠올리면 이해하기 쉽다. 다만 CSS Grid는 표처럼 콘텐츠 자체를 표 형식으로 만드는 것이 아니라, 화면 배치를 위한 레이아웃 판을 만든다는 차이가 있다. 그래서 의미상 표가 아닌 UI 구조를 만들 때 table 대신 grid를 사용하는 것이 적절하다.

열과 행을 만드는 핵심 속성

grid-template-columns는 열의 구조를 정의하고, grid-template-rows는 행의 구조를 정의한다. 열과 행을 어떻게 정의하느냐에 따라 전체 레이아웃의 성격이 결정된다. 고정된 너비가 필요한 영역에는 px를 쓸 수 있고, 남는 공간을 유동적으로 나누고 싶을 때는 fr을 쓴다.

.layout {
  display: grid;
  grid-template-columns: 240px 1fr;
  gap: 32px;
}

이 코드는 왼쪽에 240px 너비의 사이드바를 만들고, 오른쪽 본문은 남은 공간을 차지하게 한다. 여기서 1fr은 “사용 가능한 남은 공간 중 1비율”이라는 뜻이다. 따라서 화면이 넓어지면 본문 영역이 넓어지고, 사이드바는 계속 240px을 유지한다.

fr은 퍼센트와 다르다. 50%는 부모 너비의 절반을 의미하지만, 1fr은 고정 크기와 간격을 제외한 남은 공간을 기준으로 계산된다. 그래서 grid-template-columns: 200px 1fr 2fr처럼 쓰면 첫 번째 열은 200px로 고정되고, 나머지 공간을 두 번째와 세 번째 열이 1대 2 비율로 나눠 가진다.

repeat()는 반복되는 열이나 행을 간결하게 작성할 때 사용한다. repeat(4, 1fr)1fr 1fr 1fr 1fr와 같다. 카드 목록처럼 같은 규칙의 열이 반복되는 경우에는 repeat()를 쓰는 편이 읽기 쉽고 수정하기도 편하다.

minmax()는 최소값과 최대값을 동시에 지정하는 함수다. 예를 들어 minmax(240px, 1fr)는 각 열이 최소 240px은 유지하되, 공간이 남으면 1fr처럼 늘어날 수 있다는 뜻이다. 이 함수는 반응형 카드 레이아웃에서 특히 자주 쓰인다.

간격과 정렬 제어하기

Flex와 Grid의 배치 차이를 비교하는 예시

gapGrid에서 아이템 사이의 간격을 지정한다. 예전에는 margin으로 카드 간격을 만들고 마지막 요소의 여백을 따로 제거하는 방식이 흔했지만, gap을 사용하면 이런 보정이 필요 없다. 행 간격과 열 간격을 다르게 주고 싶다면 row-gapcolumn-gap을 사용할 수 있다.

.gallery {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  row-gap: 32px;
  column-gap: 20px;
}

정렬 속성은 두 그룹으로 나누어 이해하면 쉽다. justify-itemsalign-items는 각 grid item이 자기 셀 안에서 어떻게 정렬될지를 정한다. justify-items는 가로축 정렬, align-items는 세로축 정렬이다. 둘을 한 번에 쓰고 싶으면 place-items를 사용할 수 있다.

반면 justify-contentalign-contentgrid 전체 트랙 묶음이 컨테이너 안에서 어떻게 놓일지를 정한다. 예를 들어 컨테이너가 실제 그리드보다 넓거나 높을 때, 전체 그리드를 가운데로 모으거나 양끝으로 벌릴 수 있다. 둘을 함께 지정할 때는 place-content를 쓴다.

입문 단계에서 자주 헷갈리는 부분은 itemscontent의 차이다. items는 셀 안의 아이템 정렬이고, content는 그리드 트랙 전체의 정렬이다. 카드 자체를 셀 안에서 가운데에 놓고 싶으면 place-items가 맞고, 그리드 묶음 전체를 컨테이너 가운데에 놓고 싶으면 place-content가 맞다.

아이템 위치를 직접 지정하는 방법

Grid는 자동으로 아이템을 배치할 수도 있지만, 특정 아이템을 원하는 위치에 직접 놓을 수도 있다. 이때 사용하는 대표 속성이 grid-columngrid-row다. 이 속성들은 아이템이 어느 grid line에서 시작하고 끝나는지 지정한다.

.featured {
  grid-column: 1 / 3;
  grid-row: 1 / 2;
}

위 코드는 .featured 아이템을 1번 세로 라인에서 시작해 3번 세로 라인까지 차지하게 한다. 즉 두 개의 열을 가로로 넓게 사용한다. grid-column: span 2처럼 작성하면 현재 위치에서 두 칸을 차지하도록 만들 수도 있다.

더 큰 화면 구조에서는 grid-template-areasgrid-area를 함께 쓰면 가독성이 좋아진다. 숫자 라인으로 위치를 지정하는 대신, 각 영역에 이름을 붙여서 배치할 수 있기 때문이다.

.page {
  display: grid;
  grid-template-columns: 240px 1fr;
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
}

.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }

grid-template-areas를 쓰면 코드만 보아도 화면 구조가 어느 정도 보인다. header는 두 열을 모두 차지하고, sidebar는 왼쪽, main은 오른쪽, footer는 아래 전체를 차지한다. 관리자 화면, 블로그 상세 페이지, 문서형 레이아웃처럼 구조가 분명한 화면에서는 grid-template-areas가 유지보수에 도움이 된다.

다만 모든 상황에서 영역 이름을 붙일 필요는 없다. 단순한 카드 목록에는 grid-template-columnsgap만으로 충분하다. 레이아웃의 의미 있는 구역이 여러 개로 나뉠 때 grid-template-areas를 선택하면 코드가 과하게 복잡해지는 일을 줄일 수 있다.

반응형 Grid 패턴

auto-fit과 minmax를 활용한 반응형 카드 레이아웃

반응형 레이아웃에서 Grid가 특히 편한 이유는 열 개수를 자연스럽게 조절할 수 있기 때문이다. 대표적인 패턴은 repeat(), auto-fit, minmax()를 함께 쓰는 방식이다.

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 24px;
}

이 코드는 각 카드가 최소 240px 너비를 유지하도록 하고, 공간이 충분하면 가능한 만큼 여러 열을 만든다. 화면이 좁아져서 240px짜리 열을 여러 개 둘 수 없으면 자동으로 줄 수가 줄어든다. 그래서 간단한 카드 목록에서는 미디어 쿼리 없이도 자연스러운 반응형 배치가 가능하다.

auto-fitauto-fill은 비슷해 보이지만 차이가 있다. auto-fit은 남는 빈 트랙을 접어서 실제 아이템이 공간을 채우는 느낌이 강하고, auto-fill은 가능한 트랙을 유지하려는 성격이 있다. 대부분의 카드 목록에서는 auto-fitminmax() 조합이 기대한 결과에 더 가깝다.

반응형에서 주의할 점은 최소값을 너무 크게 잡지 않는 것이다. minmax(360px, 1fr)처럼 최소 너비가 크면 작은 모바일 화면에서 가로 스크롤이 생길 수 있다. 콘텐츠 성격에 따라 카드가 읽기 좋은 최소 너비를 정하고, 컨테이너 너비와 gap까지 함께 고려해야 한다.

복잡한 화면에서는 미디어 쿼리가 여전히 필요하다. 예를 들어 데스크톱에서는 grid-template-areas로 사이드바와 본문을 나란히 두고, 모바일에서는 사이드바를 본문 위나 아래로 내려야 할 수 있다. 이때는 같은 영역 이름을 유지한 채 grid-template-areas만 바꾸면 구조 변경이 명확하다.

실무에서 자주 하는 실수와 선택 기준

가장 흔한 실수는 Gridflex를 목적 없이 섞는 것이다. 둘을 함께 쓰는 것은 문제가 아니지만, 역할이 분명해야 한다. 페이지 전체 구조는 grid로 잡고, 카드 내부의 아이콘과 텍스트 정렬은 flex로 처리하는 식이면 자연스럽다. 반대로 단순히 한 줄 버튼 그룹을 만들기 위해 복잡한 grid-template를 작성하면 유지보수가 어려워진다.

두 번째 실수는 grid-template-columns에 고정값만 잔뜩 넣는 것이다. 모든 열을 px로 고정하면 화면 크기가 조금만 달라져도 여백이 어색해지거나 가로 스크롤이 생긴다. 고정 폭이 필요한 영역과 유동적으로 늘어날 영역을 구분해 px, fr, minmax()를 섞어 쓰는 편이 안정적이다.

세 번째 실수는 아이템 순서를 시각적으로만 바꾸고 문서 흐름을 고려하지 않는 것이다. grid-column, grid-row, grid-area를 쓰면 화면상 위치를 바꿀 수 있지만, HTML 구조 자체의 의미가 사라지는 것은 아니다. 접근성과 키보드 탐색, 스크린 리더 순서를 고려해야 하는 화면이라면 시각적 배치와 문서 순서가 크게 어긋나지 않도록 설계해야 한다.

  • 한 방향 정렬이 핵심이면 flex를 먼저 고려한다.
  • 행과 열을 동시에 설계해야 하면 grid를 사용한다.
  • 반복 카드 목록은 repeat(), auto-fit, minmax() 조합을 검토한다.
  • 페이지의 큰 영역은 grid-template-areas로 이름을 붙이면 읽기 쉽다.
  • 간격은 개별 margin보다 gap으로 관리하는 편이 단순하다.

결론적으로 CSS Grid는 속성 몇 개를 외워서 쓰는 기술이라기보다, 화면을 행과 열의 관계로 바라보는 방식에 가깝다. 처음에는 grid-template-columnsgap만으로 카드 목록을 만들어 보고, 그다음 fr, minmax(), grid-column, grid-template-areas로 확장하면 부담이 적다. 중요한 것은 “몇 칸을 만들 것인가”보다 “이 화면의 영역들이 어떤 규칙으로 배치되어야 하는가”를 먼저 생각하는 것이다.

이 글이 마음에 드세요?

RSS 피드를 구독하세요!

댓글 남기기