한글 웹 타이포그래피 (6) 위계와 타입스케일: 최적값을 찍어내기

다섯 편을 걸어왔다. 1편에서 서구의 66자·1.5·16px가 한글에 왜 안 맞는지 짚었고, 2편에서 본문 서체를 고딕으로 정했다. 3편에서 크기 17px과 굵기, 대비를 못박고, 4편에서 한 줄 폭을 680px 약 40자로 잡았다. 5편에서 자간 0, 행간 1.8, 문단 간격 24px까지 정했다. 값은 다 나왔다.
이번 편은 그 값들을 하나로 조립하는 마지막 손질이다. 두 가지를 한다. 하나는 아직 안 정한 마지막 조각, 제목부터 캡션까지의 위계를 본문 17px에서 뽑아내는 것. 다른 하나는 앞의 모든 값을 복사해 붙이면 바로 쓰는 하나의 CSS 변수 블록으로 재조립하는 것이다. 이 블로그가 실제로 쓰는 토큰 이름과 숫자 그대로 못박는다. 가상의 변수명은 쓰지 않는다.
모듈러 스케일이라는 사다리
위계를 만드는 흔한 방법이 모듈러 스케일(modular scale)이다. 원리는 단순하다. 기준 크기 하나를 정하고, 거기에 일정한 배율을 곱해 한 칸씩 올라가며 크기를 만든다. 본문 17px에 배율 1.25를 곱하면 약 21px, 거기 또 1.25를 곱하면 약 27px, 이런 식으로 사다리를 놓는다. 크기들이 전부 한 배율로 엮여 있으니 제목과 본문이 따로 노는 게 아니라 한 가족처럼 비율이 맞아떨어진다.
배율을 얼마로 잡느냐가 위계의 성격을 정한다. 서구 웹에서 흔히 쓰는 값은 1.25(장3도)나 1.333(완전4도) 언저리다. 배율이 크면 제목과 본문의 크기 차가 확 벌어져서 대비가 극적이고, 작으면 단계들이 촘촘히 붙어 차분하다. 랜딩 페이지처럼 시선을 끌어야 하는 지면은 배율을 크게, 문서나 블로그처럼 오래 읽는 지면은 배율을 완만하게 잡는 게 보통이다.
여기서 정직하게 짚자. 모듈러 스케일은 음악의 화음 비율에서 따온 미학적 장치이지, 이 배율이 완독률을 몇 퍼센트 올린다는 식으로 실험이 증명한 상수가 아니다. 크기들이 한 비율로 묶이면 눈에 조화롭게 보인다는 조판 관습이자 도구다. 그러니 나는 이걸 지켜야 할 법칙이 아니라, 크기를 아무렇게나 찍지 않게 잡아주는 잣대 정도로 쓴다.
한글에서는 배율을 완만하게
이 블로그의 타입스케일은 배율을 대략 1.2 안팎으로, 서구의 흔한 1.25~1.333보다 완만하게 잡았다. 왜 한 단계 눌렀는지가 이 시리즈의 논리와 이어진다.
1편에서 한 이야기를 다시 꺼내자. 같은 픽셀 크기라도 한글은 라틴보다 시각적으로 크고 꽉 차 보인다. 네모틀을 거의 다 메우기 때문이다. 이 성질은 제목에서 더 두드러진다. 40px 라틴 제목은 대문자와 어센더가 위로 솟고 나머지는 여백인데, 40px 한글 제목은 그 칸을 통째로 채운다. 그래서 같은 배율로 키워도 한글 제목이 라틴 제목보다 더 크고 무겁게 다가온다. 서구 기준으로 1.333을 곱해 제목을 뽑으면 한글에서는 제목만 과하게 부풀어 본문을 짓누른다.
그래서 배율을 완만하게 가져가면 제목이 본문 위에서 적당히 크되 과하지 않게 앉는다. 다만 이 완만함이 한글 몇 배율이 최적이라고 실험으로 나온 숫자는 아니라는 걸 분명히 해두자. 한글이 시각적으로 크다는 구조적 성질과, 오래 읽는 지면은 대비를 낮춘다는 조판 관습을 겹쳐 내린 판단이다. 근거는 관습과 구조지 실증 상수가 아니다.
본문 17px에서 제목과 캡션을 뽑아내기
기준은 본문 17px, 토큰으로는 --text-base다. 여기서 위로 올라가면 제목, 아래로 내려가면 캡션이다. 이 블로그가 실제로 쓰는 위계 토큰을 데스크톱 기준으로 펼치면 이렇다.
| 토큰 | 크기 | 아래 단계 대비 배율 | 용도 |
|---|---|---|---|
--text-h1 | 3rem (48px) | 1.20 | 페이지 대문 제목 |
--text-h2 | 2.5rem (40px) | 1.25 | 큰 제목 |
--text-h3 | 2rem (32px) | 1.33 | 중간 제목 |
--text-h4 | 1.5rem (24px) | 1.20 | 작은 제목 |
--text-h5 | 1.25rem (20px) | 1.18 | 더 작은 제목 |
--text-base | 1.0625rem (17px) | 1.21 | 본문 |
--text-sm | 0.875rem (14px) | 1.17 | 캡션·주석 |
--text-xs | 0.75rem (12px) | — | 라벨·메타 |
배율 칸을 보면 알겠지만 이게 하나의 순수한 배율로 딱 떨어지는 사다리는 아니다. 1.17에서 1.33 사이를 오가고, 평균을 내면 1.2 언저리다. 이론적인 모듈러 스케일은 배율 하나를 끝까지 밀지만, 실무 토큰은 rem 값을 사람이 읽기 좋은 숫자(3rem, 2.5rem)로 반올림하고 단계마다 눈으로 조율하기 때문에 배율이 조금씩 흔들린다. 중요한 건 소수점 아래까지 맞춘 순수한 배율이 아니라, 전체가 대략 1.2 안팎의 완만한 리듬으로 묶여 있다는 점이다.
제목에는 크기만 정하는 게 아니라 5편에서 미뤄둔 두 값을 여기서 얹는다. 행간과 자간이다. 제목은 본문과 반대로 행간을 좁게 잡는다. 본문 행간 1.8(--leading-relaxed)은 여러 줄을 눈이 따라가게 벌린 값인데, 제목은 한두 줄이라 그렇게 벌리면 줄 사이가 휑하게 뜬다. 그래서 제목은 --leading-tight 1.2로 조인다. 자간은 5편에서 정한 대로 큰 제목에만 약한 음수를 준다. 글자가 커지면 사이 간격이 상대적으로 넓어 보여서, h1처럼 큰 제목에 -0.01em쯤을 줘서 덩어리를 단단하게 만든다. 본문 자간은 여전히 0이다.
본문 안에서는 한 단계 눌러 앉힌다
여기서 한 가지 실무 매핑을 짚고 넘어가야 한다. 방금 만든 h1~h5 토큰이 본문 글(.markdown) 안에서 그대로 쓰이지는 않는다.
이유는 위계의 자리다. 글 하나를 열면 페이지 맨 위의 글 제목이 이미 h1 자리를 차지한다. 그게 --text-h1 48px이다. 그다음 본문 안에서 마크다운으로 찍는 제목들은 그 글 제목 아래에 딸린 소제목이라, 페이지 대문만큼 클 이유가 없다. 그래서 이 블로그는 본문 헤딩을 한 단계씩 눌러 매핑한다. 마크다운 ##은 h2 토큰(40px)이 아니라 --text-h4 24px로, ###은 --text-h5 20px로 앉힌다.
예를 들어 지금 읽는 이 글에서 각 절 제목(##)은 40px이 아니라 24px로 나온다. 만약 본문 ##을 h2 토큰 그대로 40px로 뒀다면, 글 제목 48px 바로 아래에 40px 소제목이 붙어서 둘이 크기로 경쟁했을 것이다. 한 단계 눌러 24px로 두니 글 제목이 확실히 위에 서고, 절 제목은 본문 17px보다는 분명히 크되 대문을 넘보지 않는다. 큰 스케일 토큰(h1~h3)은 표지나 대문 같은 디스플레이 자리에 남겨두고, 본문 안에서는 h4·h5부터 쓰는 셈이다.
앞 편에서 정한 값 총정리
조립하기 전에 지금까지 못박은 값을 한자리에 모은다. 각 값이 어느 편에서 왜 나왔는지는 해당 편에 있고, 여기서는 무엇을 어떤 토큰으로 쓰는지만 정리한다.
| 항목 | 값 | 토큰 | 편 |
|---|---|---|---|
| 서체 | 고딕(라틴 Geist + 한글 Pretendard) | --font-sans | 2편 |
| 본문 크기 | 17px, 모바일 하한 16px | --text-base | 3편 |
| 굵기 | 본문 400 / 강조 500 / 굵게 700 | --font-normal·--font-medium·--font-bold | 3편 |
| 대비 | 라이트 #F5F0E1·#0A0A0A, 다크 #121212·#F0F0F0 (4.5:1↑) | --bg-100·--color-fg | 3편 |
| measure | 본문 폭 680px(컨테이너 720px), 약 40자 | --container-content | 4편 |
| 줄끝 | 어절 단위 줄바꿈, 왼쪽 정렬(양끝맞춤 회피) | word-break: keep-all | 4편 |
| 자간 | 본문 0, 큰 제목만 -0.01em | — | 5편 |
| 행간 | 본문 1.8 / 제목 1.2 | --leading-relaxed·--leading-tight | 5편 |
| 문단 간격 | 24px | --space-6 | 5편 |
| 위계 | h1~h5 + 캡션, 배율 ~1.2 | --text-h1~--text-h5·--text-sm·--text-xs | 6편 |
하나의 토큰 블록으로 조립
이제 이 값들을 하나의 CSS 커스텀 프로퍼티 블록으로 합친다. 먼저 변수 선언이다. 라이트 값을 :root에 두고, 다크에서 달라지는 색만 data-theme="dark"에서 덮어쓴다.
:root {
/* 서체 (2편): 본문 고딕. 라틴 Geist + 한글 Pretendard */
--font-sans: "Geist", "Pretendard Variable", Pretendard,
-apple-system, sans-serif;
/* 굵기 (3편) */
--font-normal: 400; /* 본문 */
--font-medium: 500; /* 강조 */
--font-bold: 700; /* 굵게 */
/* 크기 (3편) + 캡션 */
--text-base: 1.0625rem; /* 17px, 본문 기준 */
--text-sm: 0.875rem; /* 14px, 캡션 */
--text-xs: 0.75rem; /* 12px, 라벨 */
/* 위계 타입스케일 (6편): 17px 위로 배율 약 1.2 */
--text-h1: 3rem; /* 48px */
--text-h2: 2.5rem; /* 40px */
--text-h3: 2rem; /* 32px */
--text-h4: 1.5rem; /* 24px */
--text-h5: 1.25rem; /* 20px */
/* 행간 (5편) */
--leading-relaxed: 1.8; /* 본문 */
--leading-tight: 1.2; /* 제목 */
/* measure·문단 간격 (4·5편) */
--container-content: 720px; /* 컨테이너, 텍스트 폭 ≈ 680px */
--space-6: 24px; /* 문단 간격 */
/* 대비 (3편): 라이트 */
--bg-100: #F5F0E1;
--color-fg: #0A0A0A;
}
:root[data-theme="dark"] {
/* 대비 (3편): 다크. 순흑·순백 대신 눌러 할레이션 완화, 4.5:1↑ */
--bg-100: #121212;
--color-fg: #F0F0F0;
}
다음은 이 토큰을 실제 글에 바르는 부분이다. 본문 래퍼 .markdown에 서체·크기·행간·자간·대비·measure·줄끝을 한 번에 걸고, 그 안의 제목과 문단, 캡션에 위계를 입힌다.
.markdown {
max-width: 680px; /* 720 − 좌우 패딩 40 = 텍스트 680, 한글 약 40자 (4편) */
margin-inline: auto;
font-family: var(--font-sans);
font-size: var(--text-base); /* 17px */
font-weight: var(--font-normal);
line-height: var(--leading-relaxed); /* 1.8 */
letter-spacing: 0; /* 본문 자간 0 (5편) */
color: var(--color-fg);
background-color: var(--bg-100);
text-align: left; /* 양끝맞춤 회피 (4편) */
word-break: keep-all; /* 어절 단위 줄바꿈 (4편) */
overflow-wrap: anywhere; /* 끊을 자리 없는 긴 문자열만 강제 분리 */
}
/* 본문 강조는 medium까지 (3편) */
.markdown strong {
font-weight: var(--font-medium);
}
/* 문단 간격 24px + 외톨이 줄 방지 (4·5편) */
.markdown p {
margin: 0 0 var(--space-6);
text-wrap: pretty;
}
/* 본문 헤딩 매핑: 큰 스케일을 한 단계 눌러 앉힌다 */
.markdown h2 {
font-size: var(--text-h4); /* 24px (## → h4 토큰) */
font-weight: var(--font-bold);
line-height: var(--leading-tight); /* 1.2 */
letter-spacing: -0.01em; /* 제목만 약한 음수, 본문은 0 (5편) */
text-wrap: balance;
}
.markdown h3 {
font-size: var(--text-h5); /* 20px (### → h5 토큰) */
font-weight: var(--font-bold);
line-height: var(--leading-tight);
text-wrap: balance;
}
/* 캡션 (6편) */
.markdown figcaption {
font-size: var(--text-sm); /* 14px */
}
/* 코드·표는 measure 예외, 넓게 (4편) */
.markdown pre,
.markdown table {
max-width: none;
overflow-x: auto;
}
/* 페이지 대문 제목: 디스플레이 스케일은 여기서 (6편) */
.post-title {
font-family: var(--font-sans);
font-size: var(--text-h1); /* 48px */
font-weight: var(--font-bold);
line-height: var(--leading-tight);
letter-spacing: -0.01em; /* 큰 제목 음수 자간 */
}
이게 시리즈의 최종 산출물이다. 서체 한 줄부터 위계 여덟 단계, 라이트·다크 대비, 줄끝 처리까지 앞 다섯 편의 결론이 이 두 블록 안에 다 들어 있다.
복붙한 다음, 자기 값으로 바꾸는 법
이 블록을 그대로 복사해 붙이면 이 블로그와 같은 조판이 나온다. 하지만 이 값들은 이 블로그의 조건, 곧 미색 배경에 Pretendard 본문, 720px 컨테이너에 맞춰 고른 것이라 그대로가 정답은 아니다. 자기 프로젝트에 옮길 때는 이 순서로 손대면 흔들리지 않는다. 먼저 --text-base를 자기 본문 서체가 편히 읽히는 크기로 정하고(한글 고딕이면 17px 언저리에서 시작), 그 위에 배율을 곱해 --text-h1부터 다시 뽑는다. 오래 읽는 지면이면 1.2 안팎으로 완만하게, 시선을 끌어야 하면 1.25~1.333으로 크게. 그다음 measure를 자기 본문 크기와 폭에 맞춰 다시 계산하고(본문 폭 ÷ 글자 폭 ≈ 40자), measure가 넓어졌으면 행간을 1.8보다 위로 올린다. 색은 반드시 대비 검사기로 4.5:1을 넘는지 찍어본 뒤 확정한다. 토큰 이름은 그대로 두고 값만 갈아끼우면, 이 시리즈가 각 편에서 설명한 논리가 자기 프로젝트에서도 그대로 굴러간다.
시리즈를 닫으며
여섯 편을 관통한 문장은 1편의 그것 하나였다. 한글은 라틴보다 크게, 넓게, 짧게. 크게는 본문 17px과 완만한 타입스케일로, 넓게는 행간 1.8로, 짧게는 measure 680px 약 40자로 값이 됐다. 그 사이에 서체를 고딕으로 정하고, 대비로 할레이션을 피하고, 어절 단위로 줄을 끊고, 자간은 손대지 않는 판단들이 붙었다. 매 편이 원리를 대고 근거를 밝히고 값을 남겼고, 이번 편이 그 값을 하나의 토큰 블록으로 묶었다.
한 가지는 시리즈 내내 되풀이했으니 마지막에도 남겨둔다. 여기 나온 숫자 대부분은 실험이 증명한 상수가 아니라 한글 글자 구조와 조판 관습에서 유도한 실무 기본값이다. 그러니 복붙한 값을 절대 법칙으로 모시지 말고, 자기 지면에서 눈으로 확인하며 조율하는 출발선으로 쓰면 된다.
남은 건 하나다. 한글 본문에 라틴을 섞을 때다. 지금까지는 한글만 놓고 값을 정했지만, 실제 글에는 영문 단어와 숫자가 끼어들고 그때 크기와 기준선이 어긋난다. 다음 편에서 한영 혼용 조판, 곧 라틴을 섞을 때 크기와 기준선 차이를 어떻게 보정하는지를 다룬다. 이 시리즈의 선택 보강편이다.