폰트 굵기 표기 Thin부터 Black까지 숫자로 읽어야 CSS 실수가 없어진다

폰트 굵기 표기는 Thin·Light·SemiBold·Black처럼 영단어 이름으로 파일에 붙어 있고, CSS에선 100부터 900까지 숫자로 쓴다. 문제는 이 둘이 항상 1:1로 맞아떨어지지 않는다는 거다.
분명 SemiBold 파일을 골랐는데 CSS에는 font-weight: 600을 넣었고, 화면엔 Bold가 떡하니 나왔다.
"설마 내가 파일 잘못 받은 건가?" 하고 폰트 폴더를 다시 열어봤더니 Noto_Sans_KR-SemiBold.woff2가 멀쩡히 있었다. 파일은 맞는데 화면이 틀렸다. 그때 처음으로 의심했다. 파일명의 영단어가 CSS 숫자와 1:1로 연결되는 게 아닐 수도 있겠다는 걸.
W3C 표준은 '권고'지 '강제'가 아니다. 폰트 제조사마다 숫자 배정이 조금씩 다르고, 지정한 굵기가 없으면 브라우저가 알아서 인접 굵기를 고른다. 그 선택 방향이 꽤 비직관적이다.
폰트 카탈로그의 영단어를 해독해서 CSS를 올바르게 쓰는 흐름을 잡아보자. 표를 외우는 게 아니라, 폰트마다 배정이 어떻게 다른지 파악하고 스스로 확인하는 방법이다.
Thin이 100이고 Black이 900인 이유
CSS font-weight 이름과 숫자의 대응은 W3C CSS Fonts Level 4 사양에 기준이 있다. 100~900 구간을 정리하면 이렇다.
| 숫자 | 통용 이름 | 별칭 |
|---|---|---|
| 100 | Thin | Hairline |
| 200 | ExtraLight | UltraLight |
| 300 | Light | — |
| 400 | Regular | Normal |
| 500 | Medium | — |
| 600 | SemiBold | DemiBold |
| 700 | Bold | — |
| 800 | ExtraBold | UltraBold |
| 900 | Black | Heavy |
CSS 키워드 normal은 400, bold는 700과 완전히 같다. 숫자로 쓰든 키워드로 쓰든 결과는 동일하다.
중요한 건 이 대응이 W3C가 "권고"하는 관례라는 점이다. 강제 규격이 아니기 때문에 폰트 제조사가 자체 판단으로 숫자를 배정할 수 있다. 그래서 파일명에 'Black'이 있어도 반드시 900이 아닐 수 있고, 'DemiLight'처럼 표에 없는 이름이 추가되기도 한다.
굵기 표기 이름과 실제 숫자가 어긋나는 대표 사례들
폰트 카탈로그 파일명을 읽는 건 생각보다 함정이 많다. 이름과 숫자가 항상 1:1이라는 전제가 무너지는 순간들이 있다.
Pretendard처럼 파일명이 표준과 완전히 일치하는 폰트
Pretendard는 100부터 900까지 모든 단계를 제공하고 파일명도 표준 이름과 정확히 일치한다. Pretendard-SemiBold.woff2라면 600이고, Pretendard-ExtraBold.woff2면 800이다. 이 폰트에 한해서는 표를 외우면 파일명 해독이 된다.
이렇게 표준을 완벽히 따르는 폰트가 오히려 예외적인 케이스다. 제조사가 표준 이름을 그대로 쓰는 경우에만 이름-숫자 대응이 맞아떨어지고, 조금이라도 비표준 이름을 쓰거나 단계 수를 줄이면 그 즉시 추측이 위험해진다.
웹폰트로 쓸 때는 9단계 전부를 @font-face로 선언할 필요가 없다. 디자인에 실제로 쓰는 굵기 2~3개만 선별해서 로드하는 게 페이지 성능에 훨씬 유리하다.
Noto Sans KR처럼 과거 배포판에 비표준 값 DemiLight(350)이 끼어 있었던 경우
Noto Sans KR은 현재(2025년 기준) Google Fonts에서 Thin(100)부터 Black(900)까지 100단위 9단계를 모두 제공하고 SemiBold(600)도 포함되어 있다. 그런데 예전에 Adobe나 Noto CJK OTF 방식으로 배포되던 시절 파일엔 이야기가 달랐다.
과거 OTF 배포판 기준으로는 100 단위를 벗어난 비표준 값 DemiLight(350)가 존재했다.
Thin(100) / Light(300) / DemiLight(350) / Regular(400) / Medium(500) / Bold(700) / Black(900)
DemiLight가 350이라, 100단위 표준 체계 밖이다. 오래된 파일을 받아서 직접 @font-face를 선언할 때는 font-weight: 350을 명시하지 않으면 이 굵기에 접근이 안 된다. 지금 Google Fonts CDN으로 불러온다면 SemiBold(600)를 비롯한 9단계가 다 있지만, 과거 로컬 파일 기반 프로젝트라면 한 번쯤 파일이 어느 배포판 기준인지 확인해볼 만하다.
어떤 배포판이든 공통된 교훈은 하나다. 파일명에서 이름을 추측하지 말고 실제 @font-face 코드 안의 숫자를 읽어라. 왜 이 원칙이 중요한지는 다음 섹션에서 설명한다.
나눔고딕처럼 단 2단계만 제공하는 경우
나눔고딕은 Regular(400)와 Bold(700), 딱 두 개뿐이다. font-weight: 300을 써도 Light 파일 자체가 없으니 브라우저가 대체 굵기를 선택한다. "Light로 쓰려 했는데 왜 Regular처럼 나오지?" 싶었다면 이게 원인이다.
단계가 적은 폰트는 폴백 동작에 특히 주의해야 한다. 선언한 weight 값이 파일에 실제로 없으면 브라우저는 에러를 내지 않고 조용히 다른 굵기로 대체하기 때문에, 개발 중엔 의도한 대로 나온다고 착각하기 쉽다.
없는 굵기를 요청했을 때 브라우저가 내리는 판단
브라우저는 단순히 "가장 가까운 숫자"를 고르지 않는다. 지정값이 어느 구간이냐에 따라 탐색 방향을 먼저 결정하고, 그 방향으로 순서대로 찾아 나간다. MDN 문서 기준으로 세 구간으로 나뉜다.
400~500 사이를 지정했을 때 — 지정값과 500 사이에서 오름차순으로 먼저 찾고, 없으면 지정값보다 가벼운 방향으로 내려간다. 그래도 없으면 500 초과 방향으로 올라간다.
500 초과를 지정했을 때 — 지정값보다 무거운 방향으로 오름차순 탐색이 먼저다. 없으면 가벼운 방향으로 내려간다. 예를 들어 400과 700만 있는 구형 폰트에 font-weight: 600을 쓰면, 600은 500 초과이므로 더 무거운 700을 먼저 집어 든다. SemiBold를 의도했는데 Bold가 나오는 당황스러운 상황의 정체가 바로 이것이다.
400 미만을 지정했을 때 — 지정값보다 가벼운 방향으로 내림차순 탐색이 먼저다. 없으면 무거운 방향으로 올라간다. 나눔고딕에 font-weight: 300을 쓰면 더 가벼운 방향(200, 100)을 먼저 뒤진다. 없으니까 400을 고른다. Light를 의도했는데 Regular가 나오는 것이다.
이 폴백 규칙에서 핵심은 하나다. 해당 폰트가 실제로 제공하는 weight 숫자 안에서만 CSS를 써야 한다. 폰트가 600을 지원하지 않는데 600을 쓰면, 브라우저는 틀린 게 아니라 규칙대로 움직이는 거고 틀린 건 CSS를 작성한 쪽이다.
파일명 대신 @font-face 코드에서 숫자를 읽는 방법
이름-숫자 대응이 폰트마다 다르다면, 추측 말고 실제 숫자를 확인해야 한다.
폰트비 상세 페이지에 들어가면 굵기별 @font-face 웹폰트 코드를 바로 복사할 수 있다. 이 코드 안에 font-weight 값이 이미 명시되어 있어서, Bold 굵기 코드엔 font-weight: 700, DemiLight 굵기 코드엔 font-weight: 350이 그대로 들어 있다. 파일명에서 추측하는 게 아니라 제조사가 실제로 배정한 숫자를 읽는 거다. 코드를 복사해 붙여넣으면 src 선언과 font-weight 값이 함께 들어오니 따로 수치를 외울 필요도 없다.
폰트비는 배포처가 아니라 디렉터리다. 실제 파일은 제조사 공식 배포처에서 받고, 폰트비에서는 weight 정보와 사용 범위를 참조하면 된다.
수동으로 파일을 받아서 @font-face를 직접 작성하는 경우엔 특히 이 확인이 중요하다. font-weight를 틀리게 쓰면 브라우저가 폰트 자체를 못 찾아 시스템 폰트로 폴백하거나, 앞서 설명한 대체 굵기 선택 동작이 조용히 일어난다. 코드를 복사해서 쓰면 그 실수를 원천 차단할 수 있다.
가변 폰트에서는 숫자가 끊기지 않는다
최근에는 가변 폰트(variable font) 형식도 많이 쓰인다. 정적 폰트는 100·200·300처럼 단계가 끊기지만, 가변 폰트는 단일 파일 안에 weight 축을 연속으로 담고 있어서 font-weight: 450처럼 중간값도 그대로 렌더한다.
Pretendard는 정적 파일과 가변 파일 모두 제공한다. 가변 파일을 쓰면 @font-face에서 font-weight: 100 900처럼 범위를 선언하고, 본문에서 원하는 숫자를 자유롭게 지정할 수 있다. 9가지 정적 파일을 각각 선언하는 것보다 관리도 단순하고, 중간 굵기를 정밀하게 조정해야 할 때도 유리하다.
가변 폰트를 쓰면 Noto Sans KR의 DemiLight(350) 문제도 자연스럽게 해소된다. 범위 선언이 담긴 단일 파일을 불러오면 350이든 어느 중간값이든 CSS에서 바로 지정할 수 있다.
가변 폰트 역시 폰트비 상세 페이지에서 @font-face 코드를 확인할 수 있다. 범위 선언이 포함된 코드가 그대로 제공된다.
파일명은 읽기 편하게 붙인 이름이지 CSS 숫자의 보증서가 아니다. 폰트 굵기 표기 이름(Thin, SemiBold, Black 등)은 제조사 관례일 뿐, 실제 배정 숫자는 @font-face 선언 안의 font-weight 값을 봐야 한다. 쓰려는 폰트가 실제로 어떤 weight 값을 제공하는지 먼저 확인하고 CSS를 쓰면, 브라우저가 뜬금없는 굵기를 고르는 당황스러운 상황도 그만큼 줄어든다. 폰트비 상세 페이지에서 @font-face 코드를 복사해 붙여넣는 걸 추천하는 이유가 그거다.
참고자료
이 글이 도움이 됐나요?
여러분의 반응이 다음 글의 방향이 돼요


