01 — Font Stack & Fallback
font-family에 폰트 이름 하나만 적는 것은 권총 한 발에 모든 운을 거는 것과 같다. 폰트 스택은 실패 시나리오의 순서다.
한 문장 답 (Pyramid Top)
font-family: A, B, C, sans-serif는 A를 쓰겠다가 아니라 “A가 없으면 B를, B가 없으면 C를, 다 없으면 시스템 기본 sans-serif를” 라는 fallback 체인의 선언이다. 브라우저는 이 체인을 글자 단위로 평가한다 — A 폰트에 한글이 없으면, 한글만 다음 폰트로 넘어간다 (font matching).
Why — 왜 스택이 필요한가
세 가지 현실 때문이다.
1) Web font는 늦거나 실패한다
- 네트워크 지연 (3G, 도시 외곽), CDN 장애, 광고 차단기에 의한 폰트 차단.
- 그 동안 무엇이 그려질지를 명시하지 않으면 FOIT (보이지 않는 텍스트) 또는 브라우저 기본 폰트(Times New Roman 같은)가 갑자기 튀어나온다.
2) 폰트마다 글리프 커버리지가 다르다
Pretendard는 한글·라틴을,Inter는 라틴만 제공한다.- 한 폰트에 모든 언어 글리프를 넣으면 파일이 10MB를 넘는다 (Noto Sans Universal이 그렇다).
- 그래서 언어별 폰트를 스택으로 조립한다 — Latin은 Inter, 한글은 Pretendard, 이모지는 Apple Color Emoji.
3) OS마다 자연스러운 폰트가 다르다
- macOS의 SF Pro, Windows의 Segoe UI, Android의 Roboto — 같은 화면 같은 글자라도 OS 네이티브 폰트가 가장 익숙하다.
- “OS의 시스템 폰트를 그대로 쓰겠다” 는 디자인 결정이 GitHub, Medium, Bootstrap 5의 기본값이다 (system font stack).
How — 어떻게 동작하는가
1) 폰트 매칭 알고리즘
브라우저가 <p>안녕 Hello</p>를 그릴 때:
1. <p>의 computed font-family 체인을 가져온다.
2. 글자 "안" 을 그린다:
- Pretendard에 "안" 있나? → 있으면 사용, 없으면 다음
- Inter에 "안" 있나? → 없음 (라틴만)
- Apple SD Gothic Neo에 "안" 있나? → 있음 → 사용
3. 글자 "H" 를 그린다:
- Pretendard에 "H" 있나? → 있음 → 사용
4. 결과: "안" 은 Apple SD, "H" 는 Pretendard핵심: 매칭은 글자(glyph) 단위다. 한 단어 안에서 폰트가 섞일 수 있다.
2) 폰트 매칭의 세부 (CSS Fonts Level 4)
브라우저는 다음 순서로 매칭한다:
- Family 매칭 — 이름이 일치하는 폰트를 찾는다 (대소문자 무시).
- Style 매칭 —
italic이 없으면oblique로 대체, 그것도 없으면normal을 기울여 합성 (faux italic). - Weight 매칭 —
weight: 600이 없으면 가까운 weight으로. 400과 700 사이는 볼드 합성(faux bold). - Glyph 매칭 — 해당 글자가 폰트에 있는지 확인.
- 실패하면 다음 family로 넘어간다.
3) Generic family (마지막 fallback)
스택의 마지막은 항상 generic family 중 하나를 둔다:
| Generic | 의미 | OS별 매핑 예 |
|---|---|---|
serif | 세리프 | Times New Roman / Cambria / Noto Serif |
sans-serif | 산세리프 | Helvetica / Arial / Roboto |
monospace | 고정폭 | Menlo / Consolas / DejaVu Mono |
system-ui | OS 시스템 폰트 | SF Pro / Segoe UI / Roboto |
ui-serif, ui-monospace | OS의 해당 카테고리 | macOS New York / SF Mono |
emoji | 이모지 | Apple Color Emoji / Segoe UI Emoji |
system-ui는 OS가 추천하는 UI 폰트를 자동 선택한다 — 2018년 이후 사실상 표준.
What — 구체 스택 예시
1) Modern System Stack (Bootstrap 5, GitHub)
:root {
--font-system: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
}
body { font-family: var(--font-system); }system-ui— 표준 (Chrome 56+, Safari 11+).-apple-system— 구형 Safari 호환 (벤더 prefix)."Segoe UI",Roboto, … — 명시적 OS 폰트들."Apple Color Emoji","Segoe UI Emoji"— 이모지를 컬러로.
2) Web Font + Fallback (한국어 사이트)
:root {
--font-display: "Pretendard Variable", "Pretendard", -apple-system,
BlinkMacSystemFont, system-ui, Roboto, "Helvetica Neue", "Segoe UI",
"Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic", sans-serif;
}- Pretendard 로드 전 → system-ui → Apple SD Gothic Neo (macOS) / Malgun Gothic (Windows).
- Variable 버전 먼저, 정적 fallback.
"Noto Sans KR"은 Android·Linux의 한글 fallback.
3) Monospace (코드)
:root {
--font-mono: ui-monospace, "SF Mono", "Menlo", "Monaco", "Cascadia Mono",
"Roboto Mono", "Source Code Pro", "Fira Mono", "Droid Sans Mono",
"Courier New", monospace;
}4) local()로 OS 설치 폰트 우선 사용
@font-face {
font-family: "Inter";
src: local("Inter"), local("Inter-Regular"),
url("/fonts/inter-var.woff2") format("woff2-variations");
}- 사용자가 OS에 Inter를 이미 설치했다면 네트워크 요청 없이 즉시 사용.
- 다만 fingerprinting 우려로 2020년 이후 Safari·Firefox는 일부
local()을 무시한다.
What — CJK + Latin 스택 우선순위
원칙: 라틴은 라틴 폰트가 그리고, 한글은 한글 폰트가 그린다. 하지만 순서가 중요하다.
잘못된 순서
/* Pretendard가 라틴도 한글도 가지고 있어서, Inter의 라틴이 사용되지 않는다 */
font-family: "Pretendard", "Inter", sans-serif;올바른 순서 (Latin first)
/* Inter가 라틴을 먼저 그리고, Inter에 없는 한글만 Pretendard로 넘어간다 */
font-family: "Inter", "Pretendard", sans-serif;이유: font matching은 글리프 단위다. Inter는 한글이 없으므로 한글은 자동으로 Pretendard로 넘어간다. 반대 순서면 라틴마저 Pretendard로 그려진다.
디자인 의도가 “라틴은 Inter, 한글은 Pretendard”라면 반드시 Latin first.
What — Fallback metric 맞추기 (size-adjust, ascent-override)
Web font가 늦게 로드되면 fallback 폰트로 먼저 그리는데, 두 폰트의 높이가 다르면 텍스트가 뛴다 — CLS (Cumulative Layout Shift).
@font-face로 fallback의 metric 조정
@font-face {
font-family: "Pretendard Fallback";
src: local("Apple SD Gothic Neo"), local("Malgun Gothic");
size-adjust: 102%; /* 102%로 키워 Pretendard와 높이 맞춤 */
ascent-override: 86%; /* ascent (윗변)를 줄여 정렬 맞춤 */
descent-override: 22%;
line-gap-override: 0%;
}
:root {
font-family: "Pretendard", "Pretendard Fallback", sans-serif;
}효과: Pretendard 로드 전에 fallback이 그리고, 로드 후에 진짜 Pretendard가 그려도 줄 높이·글자 폭이 거의 같다 → CLS 0.
도구: Fontaine, Next.js next/font, Capsize — 자동으로 metric을 계산해 size-adjust 값을 생성.
What-if — 잘못 쓰면
1) Generic family 누락
font-family: "MyFont"; /* 망함 — MyFont 없으면 브라우저 기본 (Times) */→ 항상 sans-serif/serif/monospace로 끝낸다.
2) system-ui만 사용
font-family: system-ui;- Linux/일부 Android에서
system-ui가 Times처럼 보이는 폰트로 매핑된 사례가 있었다 (2019년 Ubuntu 이슈). - →
system-ui는 첫 항목으로 두고, 명시적 폰트들로 백업한다.
3) Faux bold/italic
폰트에 600 weight이 없는데 font-weight: 600을 주면 브라우저가 합성한다 — 글자가 굵어지긴 하지만 디자이너가 그린 진짜 굵기가 아니다 (왜곡).
→ 변동 가능한 weight을 모두 @font-face로 로드하거나, Variable font를 사용한다.
4) 폰트 이름 따옴표 누락
font-family: Apple SD Gothic Neo; /* 깨짐 — 공백 있는 이름은 따옴표 필요 */
font-family: "Apple SD Gothic Neo"; /* 올바름 */Insight — 흥미로운 이야기
“
system-ui는 2015년 GitHub에서 시작됐다”GitHub의 CSS는 한때
font-family: Helvetica, Arial, sans-serif를 썼다. 2015년 Mark Otto(Bootstrap 만든 사람)가 “OS의 네이티브 폰트로 가자” 라는 PR을 올렸다 — 디자인이 OS와 일치하면 사용자가 더 빨리 읽는다는 가설이었다. 당시system-ui는 표준이 아니어서-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, ...같은 긴 스택을 손으로 적었다. 이 스택을 GitHub blog의 “System font stack” 글로 공개했고, Medium·Trello·Slack이 차례로 채택했다. 2017년 CSS Fonts Level 4에system-uigeneric family가 들어가며 사실상 표준이 표준이 됐다. 지금 거의 모든 모던 사이트가 이 스택을 일부 변형해서 쓴다.
“한국 웹의 폰트 진화: 굴림 → 맑은 고딕 → 본고딕 → Pretendard”
2000년대 초 한국 웹은 굴림(Gulim) 이 표준이었다 (Windows 기본 한글). 2007년 Vista와 함께 맑은 고딕(Malgun Gothic) 이 도입되며 화면 가독성이 개선됐다. 2014년 Adobe·Google이 본고딕(Noto Sans CJK / Source Han Sans) 을 오픈 소스로 공개했고, 2021년 길형진이 Pretendard를 발표했다 — 한글에 라틴을 자연스럽게 섞기 위해 디자인된 폰트다. 지금 토스·네이버·당근마켓이 모두 Pretendard 또는 Pretendard Variable을 쓴다. 한국 웹의 font-family 트렌드는 거의 5년 주기로 바뀐다.
요약 + Mermaid
font-family는 fallback 체인의 선언이다. 항상 generic family로 끝낸다.- 매칭은 글자 단위 — 한 단어 안에서도 폰트가 섞일 수 있다.
- Latin first, CJK second — 의도한 폰트가 라틴을 그리도록.
- Modern system stack + web font +
size-adjust로 CLS 0. local()은 fingerprinting 때문에 일부 브라우저에서 제한된다.
다음 문서 → 02-web-fonts: 그 web font를 어떻게 로드할 것인가.