CSS Grid fr 단위, %·px보다 나은 이유

CSS Grid에서 열 너비를 %나 px로 고정하면 콘텐츠 개수나 화면 폭이 바뀔 때마다 레이아웃이 깨지기 쉽습니다. fr 단위는 "gap과 고정 폭을 제외하고 남는 공간을 비율대로 나눈 몫"을 의미하기 때문에, 카드 목록이나 갤러리처럼 개수가 자주 바뀌는 레이아웃에서 훨씬 안정적으로 동작합니다. 여기에 repeat() 문법을 더하면 반복되는 컬럼 정의를 한 줄로 줄이고, 화면 폭에 따라 열 개수가 자동으로 바뀌는 반응형 그리드까지 CSS만으로 만들 수 있습니다.
요약 ①
fr은 전체 폭이 아니라 **gap과 고정 폭을 뺀 "남는 공간"**을 비율대로 나눈 몫입니다. ②repeat(3, 1fr)처럼 쓰면1fr 1fr 1fr을 반복해서 적을 필요가 없고,repeat(auto-fit, minmax(200px, 1fr))로 바꾸면 미디어 쿼리 없이 화면 폭에 맞춰 열 개수가 자동 조절됩니다. ③ 원하는 열·행 개수와 간격을 정해 CSS를 즉시 복사하고 싶다면 그리드 생성기로 확인할 수 있습니다.
문제: %나 px로 짠 그리드가 화면 폭에서 깨지는 이유
카드 3개를 한 줄에 나란히 놓으려고 width: 33.3%를 준 경험이 있을 겁니다. 그런데 카드 사이에 여백(margin)을 넣는 순간 폭 합계가 100%를 넘어버려서 세 번째 카드가 다음 줄로 밀려 내려갑니다.
이걸 피하려고 calc(33.33% - 16px)처럼 gap을 손으로 빼주는 계산을 넣기도 하는데, 열 개수를 4개로 바꾸면 이 계산식을 전부 다시 써야 합니다. px로 폭을 고정하면 문제는 더 단순하지만 더 나쁩니다. 화면이 좁아져도 카드 폭이 줄어들지 않아 컨테이너 밖으로 넘치고, 가로 스크롤이 생기거나 레이아웃이 잘립니다.
즉 %와 px 둘 다 "간격을 포함한 전체 폭에서 내 몫이 얼마인지"를 개발자가 직접 계산해야 하는 방식이라서, 열 개수나 gap이 바뀔 때마다 수식을 다시 손봐야 하는 게 근본적인 문제입니다.
해결: fr이 "남는 공간"을 나누는 방식부터 이해하기
CSS Grid의 fr(fraction) 단위는 계산 순서 자체가 다릅니다. 브라우저가 먼저 gap과 px로 고정된 트랙의 폭을 전체 컨테이너 폭에서 빼고, 남은 공간만 fr 값의 비율대로 나눠 배분합니다. 그래서 gap을 따로 빼는 계산을 개발자가 할 필요가 없습니다.
가장 기본적인 3열 그리드는 다음처럼 작성합니다.
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
repeat(3, 1fr)은 1fr 1fr 1fr을 줄여 쓴 표기이며, gap 16px 두 군데를 제외한 나머지 폭을 세 칸이 똑같이(1:1:1) 나눠 갖습니다. 열 개수를 5개로 늘리고 싶으면 repeat(5, 1fr)로 숫자만 바꾸면 되고, gap 계산은 브라우저가 알아서 다시 합니다.
폭 비율을 다르게 주고 싶을 때도 fr 값의 비율만 바꾸면 됩니다. 예를 들어 grid-template-columns: 2fr 1fr 1fr;은 첫 번째 칸이 나머지 두 칸보다 두 배 넓은 3단 레이아웃을 만듭니다. 고정 폭 트랙과도 섞어 쓸 수 있어서, grid-template-columns: 240px 1fr;처럼 쓰면 사이드바는 240px로 고정하고 본문 영역만 남는 공간 전부(1fr)를 차지하게 됩니다.
열 개수 자체를 화면 폭에 맞춰 자동으로 바꾸고 싶다면 repeat()의 반복 횟수 자리에 auto-fit을, fr 자리에 minmax()를 넣습니다.
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
}
이렇게 쓰면 브라우저는 "각 칸이 최소 200px은 되어야 한다"는 조건 아래 화면 폭에 들어갈 수 있는 열 개수를 스스로 계산합니다. 화면이 넓으면 열이 늘어나고, 좁아지면 줄어들며, 남는 공간은 다시 fr 비율대로 각 열에 고르게 분배됩니다. 미디어 쿼리를 한 줄도 쓰지 않고 완전한 반응형 그리드가 완성되는 셈입니다.
직접 계산한 값이 원하는 레이아웃과 맞는지 저희 도구로 확인하기 바랍니다.
무료 · 가입 불필요 · 열·행·간격 조절 즉시 CSS 코드 복사

자주 막히는 지점
| 상황 | 원인 | 해결 |
|---|---|---|
1fr인데 안의 이미지·텍스트가 넘쳐요 | grid item의 기본 최소 폭(min-width)이 auto라서 콘텐츠 크기 아래로는 줄어들지 않음 | 해당 아이템에 min-width: 0 지정 |
| 열 개수를 바꿨더니 간격이 이상해 보여요 | 가로(column-gap)와 세로(row-gap)를 다르게 줘놓고 착각한 경우 | 가로·세로를 같이 쓸지 따로 쓸지부터 확인 |
auto-fit과 auto-fill 결과가 달라요 | auto-fill은 빈 트랙도 폭을 차지한 채로 남겨두고, auto-fit은 빈 트랙을 접어 남는 공간을 기존 아이템에 몰아줌 | 카드를 꽉 채워 늘리고 싶으면 auto-fit |
33.33% 3열이 4번째 줄에서 밀려요 | %는 gap을 포함한 계산을 해주지 않아 폭 합이 100%를 넘음 | repeat(3, 1fr) + gap 조합으로 교체 |
정리
fr 단위는 컨테이너 전체 폭이 아니라 gap과 고정 트랙을 뺀 "남는 공간"을 비율대로 나누기 때문에, 열 개수나 간격이 바뀌어도 개발자가 다시 계산할 필요가 없습니다. 여기에 repeat()과 minmax()를 더하면 미디어 쿼리 없이도 화면 폭에 맞춰 열 개수가 자동으로 조절되는 그리드를 만들 수 있습니다. 직접 값을 조합해 CSS를 바로 복사하고 싶다면 그리드 생성기를 열어 확인해 보세요.
무료 · 가입 불필요 · 열·행·간격 조절 즉시 CSS 코드 복사
자주 묻는 질문
grid-template-columns: 1fr 1fr 1fr 대신 repeat(3, 1fr)을 꼭 써야 하나요?
결과 자체는 완전히 동일합니다. repeat()은 문법을 줄여 쓰는 표기법일 뿐이며, 열 개수가 많아지거나(예: 12열) 값이 자주 바뀌는 코드에서 유지보수를 쉽게 해주는 것이 실질적인 장점입니다.
auto-fit과 auto-fill의 차이가 정확히 뭔가요?
둘 다 "화면 폭에 minmax 조건을 만족하는 트랙을 몇 개나 넣을 수 있는가"를 먼저 계산한다는 점은 같습니다. 차이는 그 이후입니다. auto-fill은 실제 아이템이 채우지 못한 빈 트랙도 폭을 차지한 채로 남겨두고, auto-fit은 빈 트랙의 폭을 0으로 접어서 그만큼의 공간을 기존 아이템들에게 fr 비율대로 다시 나눠줍니다. 아이템 개수가 적을 때 화면을 꽉 채우고 싶다면 auto-fit, 트랙 폭을 일정하게 유지하고 싶다면 auto-fill이 맞습니다.
고정 폭과 fr을 한 줄에 같이 쓸 수 있나요?
가능합니다. grid-template-columns: 240px 1fr 1fr;처럼 쓰면 첫 번째 트랙은 240px로 고정되고, 남은 공간(전체 폭 - 240px - gap)을 나머지 두 개의 1fr 트랙이 절반씩 나눠 가집니다. 사이드바는 고정 폭, 본문 영역만 반응형으로 늘고 줄게 하고 싶을 때 자주 쓰는 조합입니다.
