CSS Typography

Variable Fonts: The Complete CSS Guide

Đã cập nhật Tháng 2 24, 2026
Variable fonts pack an entire family into one file. Learn the CSS for weight, width, slant, and custom axes — with interactive examples.

Variable Fonts: The Complete CSS Guide

Variable fonts are one of the most significant advances in web typography in decades. Instead of loading a separate file for each weight and style — Regular, Bold, Italic, SemiBold — a single variable font file contains the entire design space. You can dial in not just predefined weights but any weight along a continuous spectrum, and do the same for width, slant, optical size, and custom axes defined by the type designer.

This guide covers everything you need to use variable fonts effectively in production: what they are, how to access their axes in CSS, the performance case for using them, and which variable fonts from Google Fonts are worth reaching for.


What Are Variable Fonts?

A traditional ("static") font file contains a single master — one specific combination of weight, width, and style. If you need four weights plus their italic counterparts, you load eight files.

A variable font contains multiple masters and a set of mathematical descriptions of how to interpolate between them. The type designer defines axes — dimensions of variation — and the extremes of each. Within those extremes, any value is valid and rendered correctly.

The OpenType specification formalizes this as OpenType Font Variations, and it's supported natively in all modern browsers.

/* With static fonts — 4 files */
@font-face { font-family: 'Roboto'; src: url('roboto-regular.woff2'); font-weight: 400; }
@font-face { font-family: 'Roboto'; src: url('roboto-medium.woff2'); font-weight: 500; }
@font-face { font-family: 'Roboto'; src: url('roboto-bold.woff2'); font-weight: 700; }
@font-face { font-family: 'Roboto'; src: url('roboto-black.woff2'); font-weight: 900; }

/* With a variable font — 1 file, full range */
@font-face {
  font-family: 'Roboto';
  src: url('roboto-variable.woff2') format('woff2 supports variations');
  font-weight: 100 900; /* Range declaration */
  font-style: normal;
  font-display: swap;
}

Notice the font-weight: 100 900 in the variable font declaration. This range syntax tells the browser that this single file handles any weight from 100 to 900.

You can also use the format hint woff2 supports variations to indicate it's a variable font, though browsers today support it regardless of the hint.


Registered Axes: wght, wdth, slnt, ital, opsz

The OpenType specification defines five "registered" axes — standardized axes with defined CSS mappings. These are the common dimensions of variation that type designers opted into a standard system for.

wght — Weight

The weight axis (wght) corresponds to font-weight. When a variable font includes this axis, you can specify any numeric value within its range.

@font-face {
  font-family: 'Inter';
  src: url('/fonts/Inter-Variable.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap;
}

/* Use any weight value, not just multiples of 100 */
.body-text {
  font-family: 'Inter', sans-serif;
  font-weight: 400;
}

.slightly-bolder {
  font-weight: 450; /* Only possible with a variable font */
}

.ui-label {
  font-weight: 550; /* Between medium and semibold */
}

.heading {
  font-weight: 720; /* Between bold and extrabold */
}

This granularity is useful in several real-world scenarios: - Matching optical weight across different font sizes (larger text looks heavier at the same numeric weight) - Creating subtle emphasis without jumping to a full bold - Dark mode adjustments where you slightly reduce weight to account for halation (light blooming on dark backgrounds)

wdth — Width

The width axis (wdth) corresponds to font-stretch. Values represent a percentage of normal width.

@font-face {
  font-family: 'Roboto Flex';
  src: url('/fonts/RobotoFlex-Variable.woff2') format('woff2');
  font-weight: 100 900;
  font-stretch: 75% 125%;
  font-display: swap;
}

/* Condensed heading */
.section-header {
  font-family: 'Roboto Flex', sans-serif;
  font-weight: 700;
  font-stretch: 80%;
}

/* Wide display text */
.hero-word {
  font-stretch: 115%;
  font-weight: 900;
}

/* Normal width body */
.body {
  font-stretch: 100%;
}

Width variation is excellent for responsive design: you can switch to a condensed width on small screens to fit more text without reducing font-size.

slnt — Slant

The slant axis (slnt) defines how much the letterforms are slanted. It differs from italic in that it applies a mechanical slant without changing the letterform designs.

@font-face {
  font-family: 'Recursive';
  src: url('/fonts/Recursive-Variable.woff2') format('woff2');
  font-weight: 300 1000;
  font-style: oblique -15deg 0deg;
}

/* Activate slant via font-style: oblique */
.slanted {
  font-family: 'Recursive', sans-serif;
  font-style: oblique -10deg;
}

/* Or via font-variation-settings directly */
.slanted-direct {
  font-variation-settings: 'slnt' -10;
}

ital — Italic

The italic axis (ital) is a binary axis (0 or 1) that switches between upright and italic letterforms. Unlike slnt, switching to 1 triggers the full italic design if the font has one.

.italic {
  font-style: italic;
  /* Browser maps font-style: italic to ital=1 automatically */
}

/* Direct control */
.forced-italic {
  font-variation-settings: 'ital' 1;
}

.forced-upright {
  font-variation-settings: 'ital' 0;
}

opsz — Optical Size

Optical sizing is a feature where the font subtly adjusts its design to look best at a given size. At small sizes, letterforms are opened up, spacing is increased, and strokes are less differentiated. At display sizes, letterforms can be more refined and tightly spaced.

@font-face {
  font-family: 'Source Serif 4';
  src: url('/fonts/SourceSerif4-Variable.woff2') format('woff2');
  font-weight: 200 900;
  font-style: normal;
  font-optical-sizing: auto; /* Let the browser handle it */
}

/* font-optical-sizing: auto maps the optical size axis to the font-size automatically */
body {
  font-optical-sizing: auto;
}

/* Manual control via font-variation-settings */
.display-heading {
  font-size: 4rem;
  font-variation-settings: 'opsz' 72; /* Optimized for 72pt display size */
}

.footnote {
  font-size: 0.75rem;
  font-variation-settings: 'opsz' 8; /* Optimized for 8pt small size */
}

The font-optical-sizing: auto property (the default) tells the browser to automatically apply appropriate optical sizing based on the rendered font-size. This is a subtle but meaningful quality-of-life improvement that many readers notice unconsciously.


CSS: font-variation-settings vs. Standard Properties

You have two ways to set variable font axes in CSS:

  1. Standard CSS propertiesfont-weight, font-style, font-stretch, font-optical-sizing
  2. Low-level font-variation-settings — direct axis control

For registered axes, always prefer the standard CSS properties. They participate in the cascade correctly, work with inheritance, and compose with transitions and animations in a predictable way.

.component {
  font-weight: 600;
  font-stretch: 90%;
  font-style: oblique 8deg;
  font-optical-sizing: auto;
}

/* Transitions work naturally */
.button {
  font-weight: 500;
  transition: font-weight 200ms ease;
}

.button:hover {
  font-weight: 600;
}

font-variation-settings

font-variation-settings gives you direct access to any axis, including custom ones. However, it is a lowest-level override — it does not inherit partially, it does not merge with other font-variation-settings declarations, and it requires that you specify every axis you want to control in a single declaration.

/* Direct axis control */
.text {
  font-variation-settings: 'wght' 450, 'wdth' 90, 'slnt' -5;
}

/* You CANNOT do this — second declaration wins entirely, wght is lost */
.bad-example {
  font-variation-settings: 'wght' 600;  /* Overridden completely */
  font-variation-settings: 'wdth' 90;   /* Only this applies */
}

/* The correct approach — combine in one declaration */
.good-example {
  font-variation-settings: 'wght' 600, 'wdth' 90;
}

The inheritance problem with font-variation-settings is the main practical footgun. If a parent sets font-variation-settings: 'wght' 700, 'wdth' 90 and a child sets font-variation-settings: 'wdth' 80, the child's wght reverts to its default — not the parent's 700. To work around this, you can use CSS custom properties:

/* Workaround using CSS custom properties */
:root {
  --font-wght: 400;
  --font-wdth: 100;
  --font-slnt: 0;
}

.text {
  font-variation-settings:
    'wght' var(--font-wght),
    'wdth' var(--font-wdth),
    'slnt' var(--font-slnt);
}

.bold-text {
  --font-wght: 700; /* Only overrides weight, others inherit via the variable */
}

This pattern lets individual components override only the axes they care about while inheriting the rest.


Custom Axes and Creative Possibilities

Beyond registered axes, type designers can define their own custom axes. These are identified by four-letter uppercase tags and can control anything — the grade of the stroke, the extension of ascenders, the casual-to-formal spectrum, and more.

Recursive is an excellent example of a font with rich custom axes:

@font-face {
  font-family: 'Recursive';
  src: url('/fonts/Recursive-Variable.woff2') format('woff2');
  font-weight: 300 1000;
  font-style: oblique -15deg 0deg;
}

/* MONO axis: 0 = proportional, 1 = monospaced */
.code-snippet {
  font-variation-settings:
    'MONO' 1,
    'CASL' 0,
    'wght' 450,
    'slnt' 0;
}

/* CASL axis: 0 = Linear (formal), 1 = Casual (expressive) */
.fun-callout {
  font-variation-settings:
    'MONO' 0,
    'CASL' 1,
    'wght' 700,
    'slnt' -10;
}

Fraunces has a WONK axis that controls wonky letterform alternates:

.expressive-heading {
  font-family: 'Fraunces', serif;
  font-variation-settings:
    'opsz' 72,
    'SOFT' 100,
    'WONK' 1,
    'wght' 900;
}

Animating Variable Font Axes

One of the most powerful (and fun) capabilities of variable fonts is animating axes. CSS transitions and animations work seamlessly:

.animated-weight {
  font-weight: 100;
  transition: font-weight 600ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.animated-weight:hover {
  font-weight: 900;
}

/* Keyframe animation */
@keyframes pulse-weight {
  0%, 100% { font-weight: 300; }
  50%       { font-weight: 800; }
}

.pulsing-text {
  animation: pulse-weight 2s ease-in-out infinite;
}

/* Animate custom axis */
@keyframes grow-casual {
  from { font-variation-settings: 'CASL' 0, 'wght' 400; }
  to   { font-variation-settings: 'CASL' 1, 'wght' 700; }
}

.casual-reveal {
  animation: grow-casual 0.4s ease-out forwards;
}

Use these effects thoughtfully — they're powerful but can become distracting. Interactive hover states and subtle loading animations are the most justified use cases.


Performance Benefits: One File, All Weights

The performance case for variable fonts is straightforward: one file replaces many.

Consider loading four weights of a sans-serif for a typical site:

Static fonts:
  roboto-300.woff2   —  16 KB
  roboto-400.woff2   —  17 KB
  roboto-500.woff2   —  16 KB
  roboto-700.woff2   —  17 KB
  Total:               66 KB  (4 HTTP requests)

Variable font:
  roboto-flex.woff2  —  45 KB
  Total:               45 KB  (1 HTTP request)

The savings grow with the number of weights you need. For a project requiring six weights, static fonts might total 100+ KB across six requests. The single variable font stays ~45-55 KB.

Beyond file count, there is a meaningful request overhead reduction. Each HTTP request has latency — the connection overhead alone adds time. On a page that loads six font requests, eliminating five of them has a measurable impact on Time to First Render.

/* Efficient: one file for the entire range */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/Inter-Variable.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* Include italic if you need it */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/Inter-Variable-Italic.woff2') format('woff2');
  font-weight: 100 900;
  font-style: italic;
  font-display: swap;
}

/* And that's it — use any combination of weight + style */
body        { font-weight: 400; }
strong      { font-weight: 600; }
h1          { font-weight: 750; }
.ui-label   { font-weight: 500; font-style: normal; }
.quote      { font-weight: 300; font-style: italic; }

The trade-off is that a variable font with many axes may be larger than a single static weight file. If your design only ever uses one weight of a font, a static file is more efficient. Variable fonts earn their keep when you use three or more weights.

Preloading

As with any font, preload your variable font file if it's used above the fold:

<link
  rel="preload"
  href="/fonts/Inter-Variable.woff2"
  as="font"
  type="font/woff2"
  crossorigin
>

Best Variable Fonts on Google Fonts

Google Fonts has an excellent and growing collection of variable fonts. The Google Fonts API automatically serves the variable version when you request a font that has one.

Inter — The UI Workhorse

Inter is a variable font with a wght axis from 100 to 900. Designed specifically for computer screens, it has excellent legibility at small sizes and a clean, neutral aesthetic.

@import url('https://fonts.googleapis.com/css2?family=Inter:[email protected]&display=swap');

body {
  font-family: 'Inter', sans-serif;
  font-weight: 400;
}

.ui-label {
  font-weight: 550;
  letter-spacing: 0.01em;
}

Roboto Flex — Google's Most Parametric Font

Roboto Flex is perhaps the most feature-rich variable font on Google Fonts, with axes for weight, width, grade, optical size, and several custom axes.

@import url('https://fonts.googleapis.com/css2?family=Roboto+Flex:opsz,[email protected],100..900&display=swap');

body {
  font-family: 'Roboto Flex', sans-serif;
  font-optical-sizing: auto;
}

Fraunces — Expressive Display Serif

Fraunces is a variable serif with optical size, weight, and the custom SOFT and WONK axes. It's ideal for editorial headlines that need personality.

@import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,100..900;1,9..144,100..900&display=swap');

h1 {
  font-family: 'Fraunces', serif;
  font-weight: 900;
  font-optical-sizing: auto;
}

Recursive — The Coding + Casual Sans

Recursive covers a remarkable design space: it can be a proportional sans-serif, a monospaced coding font, or anything in between, with a casual/formal axis on top.

@import url('https://fonts.googleapis.com/css2?family=Recursive:slnt,wght,CASL,CRSV,[email protected],300..1000,0..1,0..1,0..1&display=swap');

code {
  font-family: 'Recursive', monospace;
  font-variation-settings: 'MONO' 1, 'CASL' 0;
  font-weight: 400;
}

Raleway — Display Sans with Elegant Thin Weights

Raleway is a display typeface with a beautiful ultra-thin weight — perfect for large headlines where geometric elegance is the goal.

@import url('https://fonts.googleapis.com/css2?family=Raleway:[email protected]&display=swap');

.hero-headline {
  font-family: 'Raleway', sans-serif;
  font-weight: 100;
  letter-spacing: 0.15em;
  text-transform: uppercase;
}

Source Code Pro / Source Serif 4 — Adobe's Variable Releases

Adobe has released variable versions of the entire Source family through Google Fonts. Source Serif 4 in particular has excellent optical sizing and covers the full weight range.

@import url('https://fonts.googleapis.com/css2?family=Source+Serif+4:ital,opsz,wght@0,8..60,200..900;1,8..60,200..900&display=swap');

.article-body {
  font-family: 'Source Serif 4', serif;
  font-optical-sizing: auto;
  font-weight: 400;
}

.article-body h2 {
  font-weight: 700;
  font-optical-sizing: auto;
}

Variable fonts represent a genuine step forward in web typography: better control, better performance, and new creative possibilities that static fonts simply cannot match. The ecosystem has matured to the point where there is no good reason not to use them — especially on Google Fonts, where the variable versions are automatically served when available.

CSS Typography Deep Dive

Thuật ngữ typography

Thử các công cụ này

Font được đề cập

Roboto Sans Serif #1

Được Christian Robertson thiết kế cho hệ sinh thái Material Design của Google, đây là phông chữ sans-serif neo-grotesque được sử dụng rộng rãi nhất trên web và Android. Thiết kế mang tính kép cân bằng giữa độ chính xác cơ học và nhịp đọc tự nhiên, phù hợp cho cả nhãn giao diện lẫn văn bản dài. Phông chữ biến thể hỗ trợ các trục chiều rộng và trọng lượng cùng các chữ viết Cyrillic, Hy Lạp và Latin mở rộng.

The quick brown fox jumps over the lazy dog
Inter Sans Serif #5

Rasmus Andersson đã dành nhiều năm tinh chỉnh phông chữ neo-grotesque này đặc biệt cho màn hình máy tính, tối ưu hóa khoảng cách chữ, chiều cao x và độ tương phản nét vẽ để đạt khả năng đọc cao ở kích thước nhỏ trên màn hình kỹ thuật số. Trục kích thước quang học (opsz) cho phép phông chữ tự động điều chỉnh thiết kế cho chú thích và tiêu đề, trong khi trục trọng lượng bao gồm toàn bộ phạm vi từ thin đến black. Nó đã trở thành lựa chọn tiêu chuẩn thực tế cho bảng điều khiển, trang tài liệu và công cụ dành cho nhà phát triển trên toàn thế giới.

The quick brown fox jumps over the lazy dog
Montserrat Sans Serif #6

Được truyền cảm hứng từ biển hiệu hình học và các cửa hàng trong khu phố Montserrat ở Buenos Aires, Julieta Ulanovsky đã tạo ra phông chữ này để nắm bắt tinh thần của nghệ thuật chữ viết đô thị đầu thế kỷ 20. Các hình tròn gọn gàng và tỷ lệ hình học mạnh mẽ tạo ra sự hiện diện quyết đoán, lý tưởng cho tiêu đề, thương hiệu và trang đích. Trục trọng lượng biến thể bao gồm phạm vi rộng, với các chữ viết Cyrillic và tiếng Việt được bao gồm.

The quick brown fox jumps over the lazy dog
Oswald Sans Serif #12

Vernon Adams đã tái tưởng tượng thể loại grotesque condensed cổ điển cho web, lấy cảm hứng từ các kiểu chữ Gothic Mỹ đầu thế kỷ và chữ in báo condensed. Tỷ lệ cao và hẹp của nó thu hút sự chú ý trong tiêu đề, áp phích và bối cảnh hiển thị nơi nhịp điệu dọc chặt chẽ. Trục trọng lượng biến đổi và hỗ trợ chữ Kirin mở rộng tính hữu dụng của phông vượt ra ngoài các ứng dụng tiếng Anh.

The quick brown fox jumps over the lazy dog
Raleway Sans Serif #14

Ban đầu được hình thành như một typeface display đơn trọng lượng vào năm 2010, Raleway đã được nhiều cộng tác viên mở rộng thành một bộ phông đầy đủ nổi tiếng với tính cách thanh lịch và hơi mang phong cách Art Deco. Những nét đặc trưng — như chữ hoa W được tạo từ hai hình V chồng nhau — tạo nên cá tính tinh tế phù hợp cho portfolio, thương hiệu thời trang và tiêu đề biên tập cao cấp. Trục trọng lượng biến đổi và hỗ trợ chữ Kirin hoàn thiện một bộ phông vượt trội về độ tinh tế thị giác.

The quick brown fox jumps over the lazy dog

Bài viết liên quan