안녕하세요! 오늘 함께 살펴볼 MDN 공식 문서는 웹 타이포그래피의 지평을 넓힌 혁신적인 기술, 바로 가변 폰트 (Variable fonts)입니다.
과거에는 글꼴의 굵기(Weight)나 기울기(Italic)를 표현하기 위해 수많은 폰트 파일을 일일이 다운로드해야 했습니다. 하지만 가변 폰트를 사용하면 단 하나의 파일로 수천 가지의 굵기와 스타일을 자유자재로 표현할 수 있습니다. 이는 웹사이트의 로딩 속도를 획기적으로 줄여줄 뿐만 아니라, 인터랙티브한 타이포그래피 애니메이션까지 가능하게 해줍니다. 프론트엔드 최적화(Optimization) 면접에서도 아주 훌륭한 답변 소재가 될 수 있으니 함께 자세히 알아보겠습니다!
가변 폰트(Variable fonts)는 OpenType 폰트 명세의 진화된 형태로, 서체(typeface)의 다양한 변형(variations)을 너비, 굵기, 스타일마다 각각 별도의 폰트 파일로 갖는 대신 단일 파일(single file)에 통합할 수 있게 해줍니다. 이 기술을 사용하면 CSS와 단 하나의 @font-face 참조만을 통해 해당 폰트 파일에 포함된 모든 변형에 접근할 수 있습니다. 이 문서는 가변 폰트 사용을 시작하는 데 필요한 모든 것을 알려드립니다.
💡 노트 (Note):
운영체제에서 가변 폰트를 사용하려면 시스템이 최신 상태인지 확인해야 합니다. 예를 들어, 리눅스(Linux) OS는 최신 버전의 Linux FreeType이 필요하며, macOS는 High Sierra (10.13) 이전 버전에서는 가변 폰트를 지원하지 않습니다. 운영체제가 구형이라면 웹 페이지나 파이어폭스 개발자 도구(Firefox Developer Tools)에서 가변 폰트를 제대로 확인할 수 없습니다.
가변 폰트가 무엇이 다른지 제대로 이해하려면, 먼저 가변 폰트가 아닌 기존의 폰트들이 어땠는지, 그리고 이 둘을 어떻게 비교할 수 있는지 되짚어 보는 것이 좋습니다.
과거에는 하나의 서체(typeface)가 여러 개의 개별 폰트들로 생산되었고, 각 폰트 파일은 특정한 너비/굵기/스타일의 '조합' 단 하나만을 나타냈습니다. 그래서 여러분은 'Roboto Regular', 'Roboto Bold', 'Roboto Bold Italic'을 위한 별도의 파일들을 각각 가지고 있어야 했습니다. 즉, 하나의 온전한 서체 제품군을 표현하기 위해 20개에서 30개에 달하는 다양한 폰트 파일을 다 가지고 있어야 할 수도 있다는 뜻입니다 (다양한 너비까지 지원하는 대규모 서체라면 그 몇 배가 될 수도 있습니다).
이런 상황에서 본문(body copy)에 서체를 적용하는 일반적인 경우를 생각해 보면, 최소한 레귤러(regular), 이탤릭(italic), 볼드(bold), 볼드 이탤릭(bold italic) 이렇게 4개의 파일이 필요했습니다. 만약 캡션(caption)용으로 더 얇은 굵기를 원하거나, 특별히 강조하고 싶은 부분을 위해 더 두꺼운 굵기를 추가하고 싶다면 여러 개의 파일을 추가로 불러와야만 했습니다. 이는 곧 더 많은 HTTP 요청(requests)과 더 많은 데이터 다운로드를 유발합니다 (일반적으로 파일 하나당 약 20KB 이상의 용량을 차지합니다).
가변 폰트를 사용하면, 이 모든 조합(permutations)을 단일 파일(single file)에 담을 수 있습니다. 물론 그 파일 하나의 용량은 단일 정적 폰트 파일 하나보다는 크겠지만, 대부분의 경우 본문 텍스트를 위해 로드해야 했던 4개의 폰트 파일을 합친 용량보다 작거나 비슷한 수준입니다.
가변 폰트를 선택했을 때 얻는 가장 큰 이점은, 이전에 개별적으로 로드해야만 했던 몇 가지 굵기만 쓸 수 있었던 제약에서 벗어나, 해당 폰트가 제공하는 굵기, 너비, 스타일의 '전체 범위(range)'를 자유롭게 사용할 수 있다는 것입니다.
이를 통해 각 크기별로 최적의 가독성을 제공하기 위해 각기 다른 크기의 제목(heading)들에 미세하게 다른 굵기를 적용하거나, 데이터가 빽빽한 디스플레이 화면에서는 글자 너비를 살짝 좁게 사용하는 등 흔히 쓰이는 훌륭한 타이포그래피 기법들을 웹에서도 구현할 수 있습니다.
브라우저가 폰트 모양을 강제로 합성(synthesize)해서 만들어내는 것에 의존하지 않고, 왜 굵기와 스타일마다(예: bold, italic, bold italic) 특정한 폰트 파일들을 명시적으로 불러오라고 이야기하는지 궁금하실 수 있습니다.
그 이유는 대부분의 서체들이 더 굵은 굵기나 이탤릭체를 위해 아주 특별한 디자인을 따로 가지고 있기 때문입니다 (예를 들어, 소문자 'a'와 'g'는 이탤릭체에서 그 모양이 완전히 달라지는 경우가 많습니다). 따라서 서체 디자이너의 의도를 가장 정확하게 반영하고, 브라우저들이 각기 다른 스타일을 강제로 합성하는 방식이나 그 결과물이 브라우저마다 다르게 나타나는 문제를 피하려면, 정적 폰트를 사용할 때는 필요한 폰트 파일을 정확히 지정해서 로드하는 것이 훨씬 안전하고 정확합니다.
또한, 어떤 가변 폰트들은 두 개의 파일로 나뉘어 제공되기도 합니다. 하나는 직립(upright, 똑바로 선) 폰트와 그 모든 변형을 담고 있고, 다른 하나는 이탤릭체(italic) 변형들만 담고 있는 식입니다. 이는 이탤릭체를 전혀 사용하지 않는 웹사이트에서 불필요한 전체 파일 용량을 줄이기 위해 종종 사용되는 방식입니다. 이 경우에도, 공통된 font-family 이름으로 두 파일을 연결하여 동일한 font-family 이름과 적절한 font-style을 사용해 호출하는 것이 얼마든지 가능합니다.
이 새로운 가변 폰트 포맷의 심장부에는 서체 디자인의 특정 측면이 변할 수 있는 허용 범위(range)를 설명하는 '변형 축(axis of variation)'이라는 개념이 있습니다.
예를 들어 '굵기 축(weight axis)'은 글자의 형태가 얼마나 얇아지고 두꺼워질 수 있는지를 설명하고, '너비 축(width axis)'은 글자가 얼마나 좁아지고 넓어질 수 있는지를 설명합니다. '이탤릭 축(italic axis)'은 이탤릭 형태의 글자가 존재하는지 여부를 설명하며 필요에 따라 켜거나(on) 끌(off) 수 있습니다. 축은 어떤 값의 범위(range)가 될 수도 있고 이진 선택(binary choice)이 될 수도 있음을 유의하세요. 굵기(Weight)는 1부터 999까지의 범위를 가질 수 있지만, 이탤릭(Italic)은 0 또는 1 (꺼짐 또는 켜짐)의 값을 가질 수 있습니다.
명세서에 정의된 바에 따르면, 축(axes)에는 두 가지 종류가 있습니다: 등록된 축(registered axes)과 커스텀 축(custom axes)입니다.
💡 강사의 실무 최적화 팁!
"구글 웹 폰트(Google Fonts)에서 Noto Sans KR을 불러올 때 로딩 속도가 너무 느려요!"라고 고민하시는 분들이 많습니다. 기존에는 100, 300, 400, 500, 700, 900 등 필요한 굵기 폰트를 다 따로 불러왔기 때문이죠. 이때 구글 폰트에서 'Variable' 버전을 체크해서 불러오면 단 하나의 폰트 파일 요청으로 100부터 900까지 모든 굵기를font-weight: 450처럼 자유롭게 숫자로 조절하면서도 렌더링 속도까지 잡을 수 있습니다!
이 섹션에서는 예제 및 그에 상응하는 CSS와 함께 앞서 정의된 5개의 등록된 축(registered axes)을 시연해 보겠습니다. 가능한 경우 표준 문법(standard syntax)과 하위 수준 문법(lower-level syntax)을 모두 포함했습니다.
하위 수준 문법인 font-variation-settings는 가변 폰트 지원의 초기 구현을 테스트하기 위해 가장 먼저 만들어진 메커니즘이었으며, 앞서 말한 5개의 등록된 축을 넘어서는 새롭거나 커스텀된 축을 사용하려면 이 속성이 반드시 필요합니다. 하지만 W3C의 원래 의도는 다른 표준 속성(예: font-weight)이 이미 존재한다면 가급적 이 하위 수준 문법은 쓰지 않도록 하는 것입니다. 따라서 가능한 한 항상 적절한 표준 속성을 사용해야 하며, font-variation-settings라는 하위 수준 문법은 다른 방식으로는 접근할 수 없는 커스텀 값이나 축을 설정할 때만 사용해야 합니다.
font-variation-settings를 사용할 때 명심해야 할 점은 축의 이름(태그)이 대소문자를 엄격히 구분한다는 것입니다. 등록된 축의 이름은 반드시 소문자(lower case)로 작성해야 하고, 커스텀 축은 반드시 대문자(upper case)로 작성해야 합니다. 예를 들어:font-variation-settings:
"wght" 375, /* 소문자 wght는 등록된 축(굵기)입니다 */
"GRAD" 88; /* 대문자 GRAD는 커스텀 축(등급)입니다 */
font-variation-settings를 사용해서 여러 값을 한 번에 설정해 두었는데, 그중 '하나의 값'만 나중에 변경하고 싶다면 (마치 OpenType 폰트 기능을 설정하는 font-feature-settings에서처럼) 모든 값을 처음부터 다시 전부 선언해 주어야 합니다. 이 한계점은 개별 값들을 CSS 커스텀 속성 (CSS 변수)로 설정하고, 변경하고 싶은 개별 커스텀 속성의 값만 수정하는 방식으로 똑똑하게 우회할 수 있습니다. 가이드 맨 마지막에 이를 응용한 예제 코드가 있습니다.굵기(Weight, wght 태그로 표시됨)는 글자 형태(letterforms)의 획(strokes)이 얼마나 얇거나 두꺼울 수 있는지(일반적인 타이포그래피 용어로는 light or heavy)를 정의하는 디자인 축입니다. CSS에서는 오랫동안 font-weight 속성을 통해 이를 지정하는 기능이 존재해 왔습니다. 이 속성은 100부터 900까지 100단위로 증가하는 숫자 값이나, 이 숫자 값(400과 700)에 해당하는 normal 또는 bold 같은 키워드를 취합니다.
이러한 방식은 가변 폰트를 다룰 때나 비가변 폰트를 다룰 때 모두 여전히 적용됩니다. 하지만 가변 폰트를 사용하면 이제 1부터 1000 사이의 어떤 숫자든 유효한 값으로 사용할 수 있습니다. (font-weight: 375; 같은 값이 가능해집니다!)
참고로 현시점에서는 @font-face 선언 안에서 가변 폰트 변형 축의 특정 숫자 지점을 bold (또는 다른 어떤 키워드)라는 키워드로 완벽하게 매핑하는 방법은 없습니다. 이는 일반적으로 CSS를 작성할 때 숫자를 직접 적어줌으로써 아주 쉽게 해결할 수 있습니다.
font-weight: 375; /* 이 표준 문법을 권장합니다 */
font-variation-settings: "wght" 375; /* 하위 수준 문법 */
(아래 라이브 예제에서 텍스트 축을 조절하여 글씨의 굵기가 어떻게 부드럽게 변하는지 확인해 보세요.)
/* weight 범위는 300에서 900까지입니다 */
.p1 {
font-weight: 625;
}
/* 하위 수준 문법 */
.p2 {
font-variation-settings: "wght" 625;
}
/* 슬라이더와 CSS 변수를 이용한 동적 조절 */
.p3 {
font-variation-settings: "wght" var(--text-axis);
}
너비(Width, wdth 태그로 표시됨)는 글자 형태가 얼마나 좁아지거나 넓어질 수 있는지(타이포그래피 용어로는 condensed or extended)를 정의하는 디자인 축입니다. CSS에서는 일반적으로 font-stretch 속성을 사용하여 이를 설정하며, 값은 'normal'(100%)을 기준으로 높거나 낮은 퍼센트(%)로 표현됩니다.
기술적으로 0보다 큰 어떤 숫자든 유효하지만, 실제 폰트 디자인에서는 75% ~ 125%처럼 100% 마크에 가까운 범위 내에서 조절되는 경우가 훨씬 많습니다. 만약 여러분이 폰트 파일 자체에 인코딩된 허용 범위를 벗어나는 숫자 값을 입력한다면, 브라우저는 해당 폰트가 허용하는 가장 가까운 값으로 렌더링해야 합니다.
💡 노트 (Note):
font-variation-settings(하위 수준 문법)를 사용하여 너비를 조절할 때는 % 기호를 쓰지 않고 숫자만 입력합니다!
font-stretch: 115%; /* 이 표준 문법을 권장합니다 */
font-variation-settings: "wdth" 115; /* 하위 수준 문법 */
(아래 라이브 예제에서 너비를 직접 조절해 보세요. 텍스트가 가로로 좁아지거나 넓어지는 것을 볼 수 있습니다.)
/* 너비 범위는 55%에서 100%까지입니다 */
.p1 {
font-stretch: 60%;
}
/* 하위 수준 문법에서는 55에서 100 사이의 정수를 사용합니다 */
.p2 {
font-variation-settings: "wdth" 60;
}
/* 슬라이더와 CSS 변수를 이용한 동적 조절 */
.p3 {
font-variation-settings: "wdth" var(--text-axis);
}
이탤릭(Italic, ital 태그로 표시됨) 축은 [0-1]의 범위로 설정될 수 있습니다. 0은 "이탤릭 아님", 0.5는 "반쯤 이탤릭", 그리고 1은 "완전한 이탤릭"을 지정합니다. (하지만 대부분의 폰트에서는 이탤릭이 켜지고 꺼지는 0과 1의 토글 형태로 많이 구현됩니다.)
이탤릭 디자인은 단순히 글씨를 기울이는 것이 아니라 똑바로 선 원본 글자와는 극적으로 다른 글자 형태를 가지는 경우가 많습니다. 그래서 똑바로 선 글자에서 이탤릭체로 전환될 때, 글리프(또는 캐릭터) 자체가 완전히 다른 모양으로 교체(substitution)되는 일이 흔히 발생합니다. 이탤릭(Italic)과 오블리크(Oblique)라는 용어는 종종 비슷한 의미로 혼용되곤 하지만, 사실 이 둘은 완전히 다릅니다. 오블리크는 이 문맥에서 slant(기울기)라는 용어로 정의되며 (아래 섹션 참조), 보통 하나의 서체는 둘 중 하나(이탤릭 혹은 오블리크)만을 가지고 있지 둘 다를 가지는 경우는 드뭅니다.
CSS에서 이탤릭체와 오블리크체는 모두 font-style 속성을 사용하여 텍스트에 적용됩니다. 또한 font-synthesis: none;의 도입을 눈여겨보세요. 이것은 브라우저가 실수로 변형 축을 강제로 적용하거나 없는 이탤릭체를 억지로 합성(synthesize)해서 렌더링하는 것을 막아주는 안전장치 역할을 합니다. 이는 강제로 글씨를 굵게 만드는 가짜 볼드(faux-bolding) 현상을 막는 데도 사용될 수 있습니다.
font-style: italic;
font-variation-settings: "ital" 1;
font-synthesis: none; /* 브라우저의 강제 합성을 막습니다 */
기울기(Slant, slnt 태그로 표시됨)는 종종 '오블리크(oblique)'라고 불립니다. 이것이 진짜 이탤릭체와 다른 점은, 글리프 자체의 형태를 바꾸지 않고 그저 글자 형태의 각도(angle)만 옆으로 기울인다는 것입니다. 기울기 역시 숫자 범위로 표현되는 가변적인 축입니다. 이로 인해 폰트는 허용된 기울기 축 범위를 따라 원하는 만큼 자유롭게 기울일 수 있습니다. 허용되는 각도 범위는 -90도에서 90도까지입니다.
기울기를 제어할 수 있는 두 가지 속성은 font-style과 font-variation-settings입니다. 다음 두 개의 속성 선언은 완전히 동일하게 작동합니다:
font-style: oblique 14deg;
font-variation-settings: "slnt" -14;
가능하면 font-variation-settings 속성보다는 font-style 속성을 사용하는 것이 좋습니다. font-variation-settings 속성을 사용할 때는 deg(각도 단위) 키워드를 쓰지 않는다는 점을 주의하세요. 또한 font-variation-settings를 쓸 때는 양수(positive) 각도가 시계 반대 방향(왼쪽)으로 기울어지는 것을 의미한다는 점도 헷갈리기 쉽습니다! (표준 문법인 font-style은 우리가 생각하는 대로 오른쪽으로 기울어집니다.)
.font-style {
font-style: oblique 5deg; /* 오른쪽으로 5도 기울어집니다 */
}
.font-variation {
font-variation-settings: "slnt" -5; /* 하위 문법에서는 음수를 써야 오른쪽으로 기울어집니다 */
}
광학적 크기(Optical size, opsz 태그)는 디지털 폰트와 CSS의 세계에서는 다소 새로운 개념이지만, 사실 금속 활자를 디자인하고 만들던 시절부터 수 세기 동안 이어져 온 아주 오래된 기법입니다.
광학적 사이징(Optical sizing)은 글씨가 실제로 렌더링되는 물리적 크기에 맞춰 글자 형태의 전반적인 획 두께를 조절하는 방식을 말합니다. 만약 글씨 크기가 매우 작다면(예: 10px 또는 12px에 해당하는 크기), 작은 크기에서도 형태가 뭉개지지 않고 가독성을 확보하기 위해 전반적으로 획이 두꺼워지고 자잘한 디테일들이 단순화됩니다. 반대로 매우 큰 사이즈(예: 48px 또는 60px)로 사용될 때는, 획의 굵은 부분과 얇은 부분의 대비(대조)가 훨씬 커지면서 서체 디자이너의 원래 의도가 담긴 화려하고 섬세한 디자인이 그대로 드러나게 됩니다.
가변 폰트를 지원하기 위해 CSS에는 font-optical-sizing이라는 새로운 속성이 만들어졌습니다. font-optical-sizing을 사용할 때 허용되는 값은 오직 auto와 none뿐입니다. 즉, 이 속성은 브라우저가 알아서 폰트 크기에 맞게 광학적 사이징을 켜고 끄는 기능만 제공합니다.
하지만 만약 여러분이 하위 수준 문법인 font-variation-settings: 'opsz' <num>을 사용한다면, 원하는 숫자 값을 직접 강제로 입력할 수 있습니다.
font-optical-sizing: auto; /* 브라우저가 알아서 최적화! (권장) */
font-variation-settings: "opsz" 36; /* 내가 직접 36px 크기에 맞는 디테일을 적용하겠다! */
커스텀 축은 말 그대로 서체 디자이너가 상상할 수 있는 모든 형태의 디자인 변형 축을 의미합니다. 시간이 지나면서 특정 커스텀 축이 널리 쓰이게 되어 정식(registered) 축으로 승격될 수도 있겠죠.
등급(Grade, GRAD 태그)은 타이포그래피 디자인에서 유서 깊은 역사를 가지고 있어 앞으로 가장 널리 쓰이는 커스텀 축 중 하나가 될 가능성이 높습니다. '등급(grade)'이라는 용어는 서체 디자인의 상대적인 굵기나 밀도를 의미하지만, 기존의 '굵기(weight)'와는 결정적인 차이가 있습니다.
바로 등급을 변경하더라도 텍스트가 차지하는 물리적 공간(너비)이 변하지 않는다는 것입니다! 따라서 텍스트의 등급(두께감)을 바꾸더라도 주변의 텍스트나 레이아웃이 밀려나며 재배치(reflow)되는 현상이 전혀 일어나지 않습니다. 이 놀라운 특징 덕분에 등급(Grade)은 글씨가 스르륵 두꺼워지는 호버(hover) 애니메이션이나 전환 효과를 줄 때 주변 레이아웃을 깨지 않는 완벽한 변형 축이 됩니다.
font-variation-settings: "GRAD" 88;
가변 폰트를 로드하는 문법은 다른 일반적인 웹 폰트를 불러오는 것과 매우 비슷하지만 몇 가지 눈에 띄는 차이점이 있습니다. 이 차이점들은 최신 브라우저에서 제공되는 기존 @font-face 문법의 업그레이드를 통해 제공됩니다.
기본 문법은 동일하지만 format에서 폰트 기술을 특정할 수 있고, 불러오는 폰트 파일에 맞춰 고정된 이름을 짓는 대신 font-weight나 font-stretch 같은 설명자(descriptors)에 허용되는 범위(ranges)를 제공할 수 있습니다.
@font-face {
font-family: "MyVariableFontName";
src: url("path/to/font/file/my-variable-font.woff2")
format("woff2-variations"); /* 가변 폰트 포맷 명시 */
font-weight: 125 950; /* 125부터 950까지의 굵기를 모두 지원한다는 뜻입니다! */
font-stretch: 75% 125%;
font-style: normal;
}
💡 노트 (Note):
모든 브라우저가 폰트 포맷에 대한 전체 문법(format("woff2-variations"))을 완벽하게 구현한 것은 아닙니다. 하지만 가변 폰트를 지원하는 모든 브라우저는format("woff2-variations")대신 그냥 파일 포맷인format("woff2")로 설정해도 가변 폰트를 정상적으로 렌더링합니다. 그래도 가능하면 제대로 된 최신 문법을 사용하는 것이 좋습니다.
브라우저가 가변 폰트를 지원하는지 여부는 CSS의 기능 쿼리(@supports)를 통해 확인할 수 있습니다. 따라서 실제 운영 환경(production)에서 가변 폰트를 사용할 때는, @supports 블록 안에 가변 폰트를 호출하는 CSS 코드를 넣어둠으로써 구형 브라우저와 최신 브라우저 모두에 안전하게 대응할 수 있습니다.
/* 기본적으로 모든 브라우저에 적용되는 일반 폰트 */
h1 {
font-family: some-non-variable-font-family;
}
/* 가변 폰트를 지원하는 최신 브라우저에만 적용되는 코드 */
@supports (font-variation-settings: "wdth" 115) {
h1 {
font-family: some-variable-font-family; /* 가변 폰트로 교체! */
}
}
💡 강사의 실무 팁! (매우 중요)
아래 CSS를 보시면 정말 스마트한 코딩 기법이 나옵니다.font-variation-settings는 한 번 설정한 속성 중 하나만 바꾸려 해도, 처음부터 모든 값을 다시 다 적어줘야 하는 치명적인 단점이 있습니다.이를 해결하기 위해
font-variation-settings안에 들어가는 숫자들을 모두var(--text-wght)같은 CSS 변수(Custom Properties)로 치환해 둔 것을 볼 수 있습니다. 이렇게 해두면,h2:hover처럼 특정 상황에서 글씨를 두껍게(GRAD) 만들고 싶을 때 복잡한 코드를 다시 쓸 필요 없이 오직--text-GRAD: 130;이라는 변수 값 하나만 쏙 바꿔치기해주면 됩니다! 현업에서 가변 폰트를 다룰 때 무조건 쓰게 될 테크닉입니다.
/* 전역 변수 초기화 */
.demo2 {
--text-wght: 375;
--text-wdth: 100;
--text-opsz: 16;
--text-GRAD: 88;
}
/* CSS 변수들을 넣어둔 가변 폰트 세팅 베이스 */
.container2 > * {
font-size: 5rem;
font-variation-settings:
"wght" var(--text-wght),
"wdth" var(--text-wdth),
"opsz" var(--text-opsz),
"GRAD" var(--text-GRAD);
}
/* 이제 각 태그별로 필요한 '변수 값'만 쏙쏙 덮어씌웁니다! */
.container2 h1 {
--text-wght: 450;
--text-wdth: 85;
--text-opsz: 80;
font-size: 5rem;
}
.container2 h2 {
--text-wght: 575;
--text-wdth: 95;
--text-opsz: 36;
font-size: 2.25rem;
}
/* 호버 시 굵기(GRAD) 변수만 살짝 변경해주면 레이아웃이 깨지지 않고 부드럽게 굵어집니다! */
.container2 h2:hover {
--text-GRAD: 130;
}
이 페이지가 도움이 되셨나요?
기여하는 방법 알아보기 (Learn how to contribute)
이 페이지는 MDN 기여자들에 의해 2026년 2월 27일에 마지막으로 수정되었습니다 (MDN contributors).