CSS Grid를 "알고는 있는데 실무에서 잘 안 쓰게 되는" 기술이라고 말하는 사람이 많다. Flexbox로 대부분 해결되니까. 나도 그랬다. 그런데 어느 순간부터 Flexbox로 하면 코드가 지저분해지는 레이아웃이 자꾸 생겼다. 특히 2차원 배치가 필요한 경우.
Grid를 실무에서 쓰기 시작한 건 대시보드 리뉴얼 작업 때였다. 그때 만든 레이아웃 패턴 중에서 지금까지 반복적으로 재사용하는 5개를 정리한다.
1. 대시보드 그리드
대시보드는 Grid의 가장 자연스러운 사용처다. 카드 크기가 다르고, 특정 카드는 2칸을 차지하고, 화면 크기에 따라 배치가 달라져야 한다.
.dashboard {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: 200px;
gap: 16px;
padding: 24px;
}
.card-wide {
grid-column: span 2;
}
.card-tall {
grid-row: span 2;
}
.card-large {
grid-column: span 2;
grid-row: span 2;
}
@media (max-width: 1024px) {
.dashboard {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 640px) {
.dashboard {
grid-template-columns: 1fr;
}
.card-wide,
.card-tall,
.card-large {
grid-column: span 1;
grid-row: span 1;
}
}이걸 Flexbox로 하면 어떻게 될까. flex-wrap을 쓰고, 각 카드에 flex-basis를 퍼센트로 주고, gap 대신 margin을 계산해서 넣고... 가능은 하지만 코드가 두 배는 길어진다. 특히 card-tall처럼 세로로 2칸을 차지하는 건 Flexbox로는 자연스럽게 구현이 안 된다.
2. 사이드바 + 메인 콘텐츠
전형적인 어드민 레이아웃이다. 왼쪽 사이드바는 고정 너비, 오른쪽 메인은 남은 공간을 차지한다.
.layout {
display: grid;
grid-template-columns: 260px 1fr;
grid-template-rows: 64px 1fr;
height: 100vh;
}
.header {
grid-column: 1 / -1;
border-bottom: 1px solid #e5e7eb;
}
.sidebar {
grid-row: 2;
border-right: 1px solid #e5e7eb;
overflow-y: auto;
}
.main {
grid-row: 2;
overflow-y: auto;
padding: 24px;
}"이건 Flexbox로도 쉽게 되잖아"라고 할 수 있다. 맞다. 이 정도는 Flexbox로도 충분하다. 그런데 여기서 요구사항이 추가된다.
사이드바를 접었다 펼 수 있어야 하고, 접었을 때 아이콘만 보여야 한다. Grid에서는 grid-template-columns만 바꾸면 된다.
.layout {
display: grid;
grid-template-columns: 260px 1fr;
transition: grid-template-columns 0.2s ease;
}
.layout.collapsed {
grid-template-columns: 64px 1fr;
}한 줄로 끝난다. 사이드바 안의 내용물은 CSS나 컴포넌트 레벨에서 조건부 렌더링하면 된다. transition도 부드럽게 걸린다.
3. 반응형 카드 그리드
이건 진짜 많이 쓴다. 상품 목록, 블로그 포스트 목록, 갤러리 등. 카드가 화면 크기에 따라 자동으로 열 수가 바뀌는 패턴.
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
}이 한 줄이 미디어 쿼리 없이 반응형 그리드를 만들어준다. auto-fill은 가능한 많은 열을 채우고, minmax(280px, 1fr)은 각 카드가 최소 280px, 최대로는 남은 공간을 균등 분할한다.
auto-fill과 auto-fit의 차이가 종종 헷갈리는데, 실무적으로 중요한 차이는 아이템 수가 적을 때 나타난다.
/* auto-fill: 빈 열도 공간을 차지함 */
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
/* auto-fit: 빈 열은 공간을 0으로 줄여서 기존 아이템이 늘어남 */
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));카드가 2개인데 화면이 넓을 때, auto-fill은 카드 오른쪽에 빈 공간이 남고, auto-fit은 카드 2개가 전체 너비를 나눠 가진다. 대부분의 목록 UI에서는 auto-fill이 더 자연스럽다. 카드가 3개인데 한 줄에 2개씩 들어가는 화면에서 카드가 비정상적으로 커 보이면 안 되니까.
4. Holy Grail 레이아웃
헤더, 푸터, 사이드바 2개, 메인 콘텐츠. 웹의 고전적인 레이아웃인데, Grid의 grid-template-areas를 쓰면 코드가 시각적으로 읽힌다.
.page {
display: grid;
grid-template-areas:
"header header header"
"nav main aside"
"footer footer footer";
grid-template-columns: 200px 1fr 200px;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
.header { grid-area: header; }
.nav { grid-area: nav; }
.main { grid-area: main; }
.aside { grid-area: aside; }
.footer { grid-area: footer; }
@media (max-width: 768px) {
.page {
grid-template-areas:
"header"
"main"
"nav"
"aside"
"footer";
grid-template-columns: 1fr;
grid-template-rows: auto;
}
}grid-template-areas의 장점은 미디어 쿼리에서 레이아웃 순서를 바꾸기가 쉽다는 거다. 모바일에서 nav를 main 아래로 내리고 싶으면 areas 문자열만 수정하면 된다. HTML 순서를 건드릴 필요가 없다.
단점도 있다. 이름을 붙이는 게 귀찮고, 복잡한 레이아웃에서는 area 이름이 많아져서 오히려 가독성이 떨어질 수 있다. 팀 내에서는 "3영역 이하일 때만 areas를 쓰자"는 암묵적 합의가 있다.
5. 폼 레이아웃
폼을 Grid로 만들면 라벨과 인풋의 정렬이 깔끔해진다. 특히 라벨이 왼쪽, 인풋이 오른쪽인 가로 배치 폼에서.
.form {
display: grid;
grid-template-columns: 140px 1fr;
gap: 12px 16px;
align-items: start;
max-width: 600px;
}
.form label {
text-align: right;
padding-top: 8px;
font-weight: 500;
color: #374151;
}
.form .full-width {
grid-column: 1 / -1;
}
.form .actions {
grid-column: 2;
display: flex;
gap: 8px;
}
@media (max-width: 640px) {
.form {
grid-template-columns: 1fr;
}
.form label {
text-align: left;
padding-top: 0;
}
.form .actions {
grid-column: 1;
}
}<form className="form">
<label htmlFor="name">이름</label>
<input id="name" type="text" />
<label htmlFor="email">이메일</label>
<input id="email" type="email" />
<label htmlFor="message">메시지</label>
<textarea id="message" rows={4} />
<div className="full-width">
<p className="help-text">* 개인정보 처리 방침에 동의합니다</p>
</div>
<div className="actions">
<button type="submit">제출</button>
<button type="reset">초기화</button>
</div>
</form>gap: 12px 16px에서 앞이 row gap, 뒤가 column gap이다. 이걸 Flexbox로 하면 각 행을 div로 감싸야 한다. Grid에서는 label과 input을 그냥 나열하면 된다. 2열로 정의했으니까 자동으로 줄바꿈된다.
subgrid는 아직 이르다
CSS subgrid를 쓰면 자식 요소가 부모의 그리드 트랙을 물려받을 수 있다. 카드 내부의 제목, 설명, 버튼 높이를 다른 카드와 맞출 때 유용한데, 브라우저 지원이 아직 완전하지 않다. 2024년 기준 Chrome, Firefox, Safari 최신 버전에서는 되지만, 사내 시스템처럼 IE나 구형 브라우저를 지원해야 하는 프로젝트에서는 못 쓴다.
나도 아직 프로덕션에서 subgrid를 쓴 적은 없다. 써보고 싶은 마음은 있는데, "아직 너무 이르지 않나"라는 생각과 "지원 안 되는 브라우저에서 레이아웃이 깨지면 어쩌지"라는 걱정이 앞선다. 이런 새로운 CSS 기능들은 항상 그렇다. 알고는 있되, 프로덕션에 투입하는 건 1~2년 기다려야 마음이 편하다.
