🎨 Frontend CSS4. 타이포그래피01 — Font Stack & Fallback

01 — Font Stack & Fallback

font-family에 폰트 이름 하나만 적는 것은 권총 한 발에 모든 운을 거는 것과 같다. 폰트 스택은 실패 시나리오의 순서다.


한 문장 답 (Pyramid Top)

font-family: A, B, C, sans-serifA를 쓰겠다가 아니라 “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)

브라우저는 다음 순서로 매칭한다:

  1. Family 매칭 — 이름이 일치하는 폰트를 찾는다 (대소문자 무시).
  2. Style 매칭 — italic이 없으면 oblique로 대체, 그것도 없으면 normal을 기울여 합성 (faux italic).
  3. Weight 매칭 — weight: 600이 없으면 가까운 weight으로. 400과 700 사이는 볼드 합성(faux bold).
  4. Glyph 매칭 — 해당 글자가 폰트에 있는지 확인.
  5. 실패하면 다음 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-uiOS 시스템 폰트SF Pro / Segoe UI / Roboto
ui-serif, ui-monospaceOS의 해당 카테고리macOS New York / SF Mono
emoji이모지Apple Color Emoji / Segoe UI Emoji

system-uiOS가 추천하는 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-uiTimes처럼 보이는 폰트로 매핑된 사례가 있었다 (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-ui generic 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-familyfallback 체인의 선언이다. 항상 generic family로 끝낸다.
  • 매칭은 글자 단위 — 한 단어 안에서도 폰트가 섞일 수 있다.
  • Latin first, CJK second — 의도한 폰트가 라틴을 그리도록.
  • Modern system stack + web font + size-adjustCLS 0.
  • local()은 fingerprinting 때문에 일부 브라우저에서 제한된다.

다음 문서 → 02-web-fonts: 그 web font를 어떻게 로드할 것인가.