Type something to search...
What are optical sizes in fonts?

What are optical sizes in fonts?

Optical sizes are versions of a typeface designed for specific sizes of text, so that small type stays legible and large type stays refined. Designs for small sizes, often called caption or text cuts, have a larger x-height, sturdier strokes, lower contrast between thick and thin parts, wider letters and looser spacing. Designs for large sizes, often called display cuts, have finer details, higher contrast and tighter spacing, because at headline size those details are visible and generous spacing looks loose. In a variable font, optical size is a continuous axis with the tag opsz, and browsers adjust it automatically to match the font size when font-optical-sizing is auto, which is the default. With static fonts, optical sizes come as separate families such as "Text" and "Display" that you choose yourself.

Optical sizing matters because one design can't be perfect at every size. A delicate serif that looks elegant in a 64-pixel headline can turn faint and hard to read in a 13-pixel caption, while a sturdy text design blown up to headline size can look heavy and clumsy. In this article you'll learn where optical sizes come from, what changes between sizes, how the opsz axis and font-optical-sizing work in CSS, which fonts support them, how to load them, and how to override the automatic behaviour when you need to.

Where Optical Sizes Come From

In metal type, every size was a separate set of physical letters. A punchcutter cutting a font at 6 point couldn't simply shrink the 24 point design. Fine hairlines would be too thin to print, counters would fill with ink, and letters would crowd together. So small sizes were cut with heavier strokes, larger counters and more space, while large sizes were cut with more delicacy.

Phototypesetting and early digital type largely lost this. One master design was scaled to every size, which was cheaper but meant compromises. Small text often looked weak and large headlines looked coarse. Some digital families brought optical sizes back as separate fonts, such as caption, regular, subhead and display styles. Variable fonts have since made it practical to ship the whole range in one file, with the browser picking the right point automatically.

What Changes Between Sizes

Compare the small and large ends of a family with an optical size axis and you'll typically see these differences:

FeatureSmall optical sizeLarge optical size
x-heightLarger relative to capsSmaller, more classical
Stroke contrastLower, thin strokes are thickerHigher, hairlines are finer
Letter widthWiderNarrower
SpacingLooserTighter
DetailsSimplified, open aperturesSharper serifs and terminals
Ink trapsMore pronounced where presentReduced or absent

None of these changes is dramatic on its own. Together they keep text comfortable to read at small sizes and polished at large ones. The effect is most obvious in high-contrast serif families, where the thin strokes are the first thing to suffer at small sizes.

How Optical Size Works in Variable Fonts

The opsz axis is one of the five registered OpenType variation axes. Its values are intended to correspond to the text size the design is meant for. Each font defines its own range. A few examples from widely used families:

  • Roboto Flex: 8 to 144
  • Fraunces: 9 to 144
  • Literata: 7 to 72
  • Newsreader: 6 to 72
  • Source Serif 4: 8 to 60
  • Inter (version 4): 14 to 32

The ranges tell you where the design changes. Inter's axis, for example, moves from its text design at 14 to its display design at 32, and values outside the range are clamped to the nearest end.

font-optical-sizing

In CSS, the font-optical-sizing property controls whether the browser sets the opsz axis automatically:

body {
  font-optical-sizing: auto;
}

auto is the initial value, so you don't need to write it. When it's active and the font has an opsz axis, the browser sets opsz to match the computed font size in CSS pixels. A paragraph at 16px gets opsz 16, and a heading at 48px gets opsz 48.

To turn automatic optical sizing off:

.no-optical-sizing {
  font-optical-sizing: none;
}

With none, the font is drawn at its default optical size, whatever the text size.

Loading Fonts With Optical Size

To get optical sizing, the file you load must contain the opsz axis.

From Google Fonts

Request the axis explicitly in the CSS2 API URL. Axes are listed alphabetically, so opsz comes before wght:

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300..800&display=swap">

If you leave out opsz, Google serves the font fixed at its default optical size, and font-optical-sizing has nothing to adjust.

Self-Hosted

Declare the font as usual. You don't need a descriptor for optical size in @font-face; the browser reads the axis from the file:

@font-face {
  font-family: "Literata";
  src: url("/fonts/literata-var.woff2") format("woff2");
  font-weight: 200 900;
  font-style: normal;
  font-display: swap;
}

body {
  font-family: "Literata", Georgia, serif;
  font-size: 1.125rem;
}

h1 {
  font-size: 3rem;
}

figcaption {
  font-size: 0.8125rem;
}

With this setup, the heading, body text and captions each get an appropriate optical size with no extra CSS.

If you subset the font, make sure the tool keeps the variation axes. pyftsubset keeps them by default. To trim the range and reduce file size, use the fontTools instancer:

pip install fonttools brotli

fonttools varLib.instancer literata-var.ttf opsz=12:48 -o literata-opsz-12-48.ttf

This keeps optical sizes between 12 and 48, which may be all a particular site needs.

Overriding the Automatic Value

Sometimes the automatic value isn't what you want. Common reasons include:

  • Text on low-resolution or very bright screens: A slightly smaller optical size than the font size gives sturdier strokes.
  • Light text on dark backgrounds: Thin hairlines can break up, so a lower opsz can help.
  • Stylistic choice: A designer may want the sharper, high-contrast display style on a smaller pull quote.

To set a specific value, turn off automatic sizing and use font-variation-settings:

.pull-quote {
  font-size: 1.5rem;
  font-optical-sizing: none;
  font-variation-settings: "opsz" 72;
}

Strictly speaking, an explicit opsz in font-variation-settings takes precedence over the automatic value anyway, but setting font-optical-sizing: none alongside it makes your intent clear to anyone reading the stylesheet.

Adjusting Relative to the Font Size

If you want text to always use a slightly smaller optical size than its actual size, you can calculate it with a custom property. Because font-variation-settings needs a plain number, keep the size unitless:

:root {
  --opsz-offset: 0.85;
}

.caption {
  --size: 13;
  font-size: calc(var(--size) * 1px);
  font-optical-sizing: none;
  font-variation-settings: "opsz" calc(var(--size) * var(--opsz-offset));
}

This keeps the caption at 13px while using an optical size of about 11, which gives slightly sturdier strokes. Remember that font-variation-settings replaces the whole list, so include any custom axes you rely on in the same declaration.

Optical Sizes in Static Families

Many professional families still ship optical sizes as separate static fonts. You'll see names such as:

  • Caption for very small text
  • Text or Regular for body sizes
  • Subhead for intermediate sizes
  • Display or Deck for headlines

Apple's San Francisco system font also has distinct text and display designs, which the operating system switches between automatically based on size. When you use system-ui or -apple-system on Apple devices, you get this behaviour for free.

With static families, you assign the right family to each role:

@font-face {
  font-family: "Brand Text";
  src: url("/fonts/brand-text-regular.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}

@font-face {
  font-family: "Brand Display";
  src: url("/fonts/brand-display-bold.woff2") format("woff2");
  font-weight: 700;
  font-display: swap;
}

body {
  font-family: "Brand Text", Georgia, serif;
}

h1,
h2 {
  font-family: "Brand Display", "Brand Text", Georgia, serif;
}

The family names are placeholders for whatever your foundry provides. The downside is that each optical size is another file to download, so you might limit yourself to text and display cuts.

Responsive Headings

One case catches people out with static families: a heading that is large on desktop but much smaller on mobile. A display cut at 24px on a phone may look too delicate. With a variable font and auto optical sizing, this adjusts itself. With static fonts, switch families in a media query if it looks weak:

@media (max-width: 40rem) {
  h2 {
    font-family: "Brand Text", Georgia, serif;
    font-weight: 700;
  }
}

Checking That Optical Sizing Works

  1. Use Firefox's Fonts panel: Inspect a heading, open the Fonts tab, and look at the optical size slider. It should match the font size.
  2. Compare with it turned off: Toggle font-optical-sizing: none in DevTools and watch the hairlines and spacing change.
  3. Look at the extremes: Check your smallest captions and largest headings, where the difference is greatest.
  4. Confirm the file has the axis: If nothing changes, the loaded file may not include opsz. Check the font URL or the axes in the file.

You can read the axes from a font file with fontTools:

from fontTools.ttLib import TTFont

font = TTFont("literata-var.ttf")
axes = {a.axisTag: (a.minValue, a.defaultValue, a.maxValue) for a in font["fvar"].axes}
print(axes.get("opsz", "No optical size axis"))

If you're new to variable axes in general, what are variable fonts covers the basics.


FAQ: Optical Sizes

Optical size is the practice of adjusting a typeface's design for the size it will be used at. Small sizes get sturdier strokes, larger x-heights and looser spacing, while large sizes get finer details and tighter spacing.

It tells the browser to set a variable font's opsz axis to match the text's font size in CSS pixels. It's the default value, so it works automatically when the font has an optical size axis.

The font file probably has no opsz axis. Many fonts don't, and font services may serve a version without it unless you request the axis explicitly in the URL.

Examples include Roboto Flex, Fraunces, Literata, Newsreader, Source Serif 4 and Inter version 4. Each defines its own range of optical sizes.

Yes. Set font-optical-sizing to none and use font-variation-settings with the opsz tag and a number. Include any other custom axes in the same declaration, because it replaces the whole list.

No. Weight changes how bold the letters are. Optical size changes proportions, contrast, spacing and detail to suit a particular size, while keeping a similar visual weight.


Conclusion

Optical sizes exist because type that's perfect at one size is rarely perfect at another. Small text needs open shapes, sturdy strokes and generous spacing, while large text can show off fine hairlines and tight spacing. Metal type handled this by cutting each size separately, and variable fonts now do it with a continuous opsz axis.

On the web, the hard work is mostly done for you. Choose a family with an optical size axis, make sure the file you load actually includes it, and let font-optical-sizing: auto match the design to each font size. Override it with font-variation-settings only when you have a clear reason, and with static families, pair text and display cuts deliberately so headings and body text each get the design meant for them.

Share :

Related Posts

How to make typography accessible?

How to make typography accessible?

You make typography accessible by choosing clear typefaces, setting text in relative units so it scales with user preferences, giving it enough colou

Dive Deeper
What are the parts of a letterform?

What are the parts of a letterform?

A letterform is the shape of a single letter, and typographers break it down into named parts. The main ones are the stem (the main vertical

Dive Deeper
What are ascenders, descenders and the baseline?

What are ascenders, descenders and the baseline?

The baseline is the invisible line that letters sit on. Ascenders are the parts of lowercase letters that rise above the x-height, such as th

Dive Deeper