그리드가 화면 밖으로 넘치는 건 대개 1fr 때문입니다. 그리드 아이템의 최소 크기가 auto라 콘텐츠보다 작아지지 못합니다. minmax(0,1fr) 과 min-width:0 이 답입니다.
다크 테마에서 회색이 안 읽히는 건 어느 배경에서 쟀느냐의 문제입니다. 페이지 배경에서 5.07:1 이던 색이 카드 배경에서는 4.45:1 로 AA 미달이었습니다.
셋 다 내 화면에서만 확인해서 놓친 것들입니다. 헤드리스 브라우저로 실제 폭에서 한 번 찍어보면 3분이면 드러납니다.
이 블로그를 커스텀 테마로 직접 만들면서 18편을 쓰는 동안, 같은 종류의 함정에 세 번 걸렸습니다. 공교롭게도 셋 다 성격이 똑같습니다 — 내가 보고 있는 화면에서는 멀쩡한데, 조건이 조금 바뀌면 깨지는 것들입니다. CSS 그리드 넘침, 다크 테마 대비, 모바일 이미지 크롭. 하나씩 원인과 처방을 적습니다.
CSS 그리드가 왜 화면 밖으로 넘칠까
본문과 사이드바를 나누는 흔한 2단 구성입니다.
.body-grid {
display: grid;
grid-template-columns: 1fr 300px;
}
보통은 잘 돌아갑니다. 그런데 본문에 줄바꿈 없는 긴 줄이 하나 들어가는 순간 무너집니다. 코드블록, 긴 URL, 넓은 표가 흔한 원인입니다.

사이드바가 화면 밖으로 밀려나고 페이지 전체에 가로 스크롤이 생깁니다. 재현해서 재봤더니 뷰포트는 1000px 인데 문서 폭이 1078px — 78px 이 화면 밖으로 나가 있었습니다.
원인: 1fr 은 생각만큼 안 줄어든다
1fr 을 “남는 공간을 채우되 필요하면 줄어드는 칸”으로 이해하기 쉽습니다. 절반만 맞습니다.
그리드 아이템의 min-width 기본값은 0 이 아니라 auto 입니다. 그리고 auto 는 “콘텐츠의 최소 크기”를 뜻합니다. 안에 든 코드 한 줄이 1,200px 라면 그 칸은 1,200px 아래로 내려가기를 거부합니다. overflow-x: auto 를 걸어놨어도 소용없습니다. 스크롤할지 말지를 정하기 전에 칸 자체가 이미 커져버렸기 때문입니다.
처방: 두 줄
.body-grid {
grid-template-columns: minmax(0,1fr) 300px; /* 0까지 줄어들 수 있게 */
}
article {
min-width: 0; /* 아이템 쪽에서도 한 번 더 */
}
minmax(0,1fr) 은 최소치를 auto 가 아니라 0 으로 못박습니다. 이제 칸이 먼저 줄고, 넘치는 코드블록은 overflow-x 대로 자기 안에서 스크롤됩니다.
플렉스도 같은 함정이 있습니다. flex:1 만 주면 안 줄어들고, flex:1; min-width:0 이어야 줄어듭니다. 이 블로그 테마의 검색창·작성자 카드가 전부 이 조합입니다.
| 증상 | 범인 | 처방 |
|---|---|---|
| 그리드 칸이 안 줄어듦 | 1fr 의 최소치가 auto |
minmax(0,1fr) |
| 플렉스 아이템이 안 줄어듦 | 같은 이유 | min-width: 0 |
| 표가 화면을 밀어냄 | 표는 콘텐츠 폭을 고집함 | display:block; overflow-x:auto |
다크 테마에서 회색 글자가 왜 안 읽힐까
검정 배경에 회색 보조 텍스트는 다크 테마의 기본기입니다. 문제는 어느 회색이냐인데, 여기서 저는 두 번 틀렸습니다.
첫 번째는 흔한 실수였습니다. 처음 쓰던 #63666d 는 눈으로 보기엔 괜찮았지만 대비가 3.54:1 로 WCAG AA 기준(4.5:1)에 한참 못 미쳤습니다. #7b7f87 로 올려서 5.07:1 을 만들고 “AA 통과”라고 주석까지 달아뒀습니다.
두 번째 실수가 이 글의 핵심입니다. 배경을 하나만 놓고 쟀습니다.
이 사이트에는 배경이 하나가 아닙니다. 페이지 배경(--bg #050506) 위에도 글자가 놓이지만, 카드·검색창·표 머리는 더 밝은 --surface-2(#17171a) 를 씁니다. 배경이 밝아지면 밝은 회색 글자와의 대비는 떨어집니다.
| 글자색 | --bg 위 |
--surface-2 위 |
|---|---|---|
#63666d (처음) |
3.54:1 미달 | 3.11:1 미달 |
#7b7f87 (1차 수정) |
5.07:1 통과 | 4.45:1 미달 |
#7f838b (현재) |
5.36:1 통과 | 4.70:1 통과 |
“AA 통과”라고 적어둔 색이 실제로는 사이트에서 제일 흔한 카드 배경 위에서 미달이었습니다. 0.05 차이라 눈으로는 절대 못 잡습니다.
처방: 최악 배경을 기준으로 재라
규칙 하나로 정리했습니다. 대비는 가장 밝은 배경 토큰 위에서 잰다. 거기서 통과하면 나머지는 자동으로 통과합니다. 계산은 눈이 아니라 코드로 합니다.
python3 - <<'PY'
def lin(c):
c /= 255
return c/12.92 if c <= 0.03928 else ((c+0.055)/1.055)**2.4
def L(h):
h = h.lstrip('#'); r,g,b = (int(h[i:i+2],16) for i in (0,2,4))
return 0.2126*lin(r) + 0.7152*lin(g) + 0.0722*lin(b)
def ratio(a, b):
la, lb = L(a), L(b)
hi, lo = max(la,lb), min(la,lb)
return (hi+0.05) / (lo+0.05)
for bg in ('#050506', '#101012', '#17171a'): # 배경 토큰 전부
print(bg, round(ratio('#7f838b', bg), 2))
PY
토큰 주석에 적는 숫자도 최악 배경 기준으로 바꿨습니다. 통과하는 숫자만 골라 적어두면 나중의 나를 속이게 됩니다.
모바일에서 이미지가 잘려나갈 때
세 번째는 aspect-ratio 였습니다. 이 블로그의 글 커버는 제목 뒤에 배경으로 깔립니다. 화면 폭에 따라 비율을 다르게 줬습니다.
.cover { aspect-ratio: 16/6; } /* 데스크톱 — 시네마틱한 띠 */
@media (max-width:700px) { .cover { aspect-ratio: 3/4; } } /* 모바일 — 세로로 길게 */
커버 원본은 16:9 입니다. 그런데 object-fit: cover 는 비율이 안 맞으면 잘라서 채웁니다. 16:9 를 3:4 칸에 넣으면 좌우가 대량으로 잘립니다. 커버에 글자가 들어 있으면 그대로 토막이 납니다.

왼쪽이 그 결과입니다. 커버의 글자가 잘린 채 제목과 겹쳐서 둘 다 안 읽힙니다. 그런데 저는 이걸 몇 주 동안 몰랐습니다. 글을 쓰고 확인하는 화면이 늘 노트북이었기 때문입니다.
처방: 모바일에서는 겹치기를 포기한다
좁은 화면에서 이미지 위에 긴 한글 제목을 얹는 건 애초에 무리입니다. 그래서 모바일에서는 겹치지 않게 분리했습니다.
@media (max-width:700px) {
.cover { aspect-ratio: auto; overflow: visible; }
.cover > img { aspect-ratio: 16/9; height: auto; } /* 원본 비율 그대로 */
.cover::after { display: none; } /* 그라데이션 제거 */
.cover .cap { position: static; } /* 제목을 이미지 아래로 */
}
데스크톱은 겹치기를 유지하되 크롭을 16/6 에서 9/4 로 완화하고, 제목 뒤 그라데이션을 진하게 올렸습니다. 그리고 커버를 만들 때 지킬 규칙을 하나 정했습니다 — 제목이 앉는 왼쪽 아래는 비워둔다.
세 함정의 공통점
정리해놓고 보니 셋 다 원인이 하나였습니다. 내 화면 하나에서만 확인했다는 것.
| 함정 | 내 화면에선 | 실제로는 |
|---|---|---|
| 그리드 넘침 | 짧은 코드라 멀쩡 | 긴 줄 하나에 78px 넘침 |
| 회색 대비 | 페이지 배경에서만 확인 | 카드 배경에서 AA 미달 |
| 모바일 크롭 | 노트북에서만 확인 | 390px 에서 글자 토막 |
그래서 발행 절차에 한 단계를 넣었습니다. 헤드리스 브라우저로 실제 폭에서 한 장 찍어보는 것입니다.
# 모바일 폭으로 전체 페이지 캡처
npx -y playwright screenshot --viewport-size=390,844 --full-page \
https://example.com/my-post/ mobile.png
넘침을 잡는 요령도 있습니다. --full-page 로 찍은 PNG 의 가로 픽셀이 지정한 뷰포트보다 크면 그만큼 넘친 것입니다. 위의 그리드 사례에서 1000 으로 찍었는데 1078 이 나온 게 정확히 그 신호였습니다.
$ npx -y playwright screenshot --viewport-size=1000,560 --full-page demo.html out.png
$ sips -g pixelWidth out.png
pixelWidth: 1078 # 78px 넘침
정리
1fr의 최소치는auto— 안 줄어들면minmax(0,1fr)과min-width:0- 플렉스도 같다 —
flex:1만으로는 부족하고min-width:0이 있어야 한다 - 대비는 가장 밝은 배경 토큰 위에서 잰다 — 페이지 배경에서만 재면 카드 위에서 미달난다
- 대비 숫자는 눈이 아니라 코드로 계산하고, 토큰 주석에는 최악값을 적는다
object-fit:cover+aspect-ratio는 비율이 다르면 자른다 — 글자 든 이미지에 쓰지 말 것- 좁은 화면에서는 이미지와 제목을 겹치지 말고 분리하는 편이 낫다
- 발행 전에 실제 폭으로 한 장 찍어볼 것 — 전체 캡처 폭이 뷰포트보다 넓으면 넘친 것이다
간격과 타입 스케일을 정할 때는 규칙을 미리 정해두면 고민이 사라진다고 썼습니다. 이번 셋은 반대쪽 이야기입니다. 규칙을 정해놔도 확인하는 조건이 하나뿐이면 그 규칙이 지켜지고 있는지 알 수가 없습니다.