---
title: 'Typeface Selection'
source: 'https://academia.sh/en/courses/interface-fundamentals/typeface-selection'
course: 'Fundamentals of Interface Design'
language: en
updated: '2026-08-17T18:11:57+00:00'
license: 'CC BY-SA 4.0'
---

# Typeface Selection

The distinction between legibility and readability, what each typeface class promises, computing apparent size from x-height, and auditing a typeface candidate for alphabet coverage and character distinguishability.

The previous lesson completed the layout skeleton: columns, gutters, and grid margins
took their place according to screen width, the minimum size of touch targets was set,
and pointer and touch were shown to expect different things from the same interface. The
skeleton stands; the text that will fill it has not been chosen yet. Nearly all the
weight the catalog interface carries is text: record titles, author names, shelf codes,
ISBN numbers, dates, the label on the borrow button.

This lesson answers a single question: by what criteria, rather than taste, is a
typeface chosen? The criteria fall into four groups, and three of them are computable —
apparent size, alphabet coverage, and character distinguishability. The fourth, the
hierarchy load a family can carry, is a direct consequence of the two-channel rule from
the first lesson.

## Legibility and Readability Are Separate Things

The two criteria are often called by a single word and confused with each other.

**Legibility** is the degree to which a single character can be distinguished from its
neighbors. Whether the letter `I` in a shelf code is distinguishable from the digit `1`
is a legibility question. It is measured at the character level and is a property of the
typeface itself.

**Readability** is the degree to which a block of text can be read fluently. Line length,
leading, alignment, and contrast ratio all factor into this criterion. It is measured at
the block level and is a property not of the typeface but of the design.

The practical consequence of the distinction is this: a legibility problem is solved by
changing the typeface, a readability problem by changing the layout. In the catalog
interface, a shelf code being misread falls into the first class; a search results list
that tires the eye falls into the second. Neither improves with the other's solution.

The catalog interface has two separate masses of text, and their criteria differ. The
results list and the metadata fields consist of short, scanned strings that resemble
each other; there, legibility is decisive. The summary paragraph in the record detail, by
contrast, is read continuously; there, readability is decisive.

## Classes and What They Promise

Typefaces are grouped into classes by the formal features they carry. A class makes it
possible to talk about a typeface without naming a product, and more importantly, it
says what each class promises.

- **Serif.** Carries finishing strokes at the letter ends. These strokes produce a
  horizontal flow cue that helps in long continuous text. At small sizes the finishing
  strokes thin out and can disappear at low resolution.
- **Sans-serif.** Has no finishing strokes. It stays steadier at small sizes and in
  interface labels; because letterforms are simplified, the distinction between
  similar-looking characters can weaken.
- **Monospace.** Every character occupies the same advance width. Wherever column
  alignment is needed — shelf code, ISBN, date — it gives alignment for free. In
  continuous text it slows reading down because word shapes are distorted.
- **Variable font.** Carries a continuous weight axis in a single file; a separate file
  for each weight is not needed. What it provides is not a new form but a cheaper weight
  channel.

On the CSS side, these classes correspond to generic family keywords: `serif`,
`sans-serif`, `monospace`. These were introduced in the Visual Presentation with CSS
course as the last item in a family list; on the design side, they are a direct class
decision.

The catalog interface's decision follows from this distinction: a single sans-serif
family for body text and headings, a monospace family for the shelf code and ISBN
fields. Two families, because there are two different jobs; a third family cannot be
justified, because no third job can be named.

## Nominal Size Is Not Apparent Size

Two typefaces set at the same size do not look the same size. Nominal size is the
measure of the em box; what the eye perceives as size is the body height of the
lowercase letters, that is, the **x-height** value. A font file declares this measure in
its own metadata in em units, so it can be computed.

```js
// xheight.mjs — computing apparent size from a font's declared metrics

// Every font file declares its metrics in em units (here em = 1000 units).
// Below are the metrics declared by three candidate families.
const candidates = [
  { name: "candidate-1", xHeight: 480, capHeight: 700, ascent: 750, descent: 250 },
  { name: "candidate-2", xHeight: 520, capHeight: 715, ascent: 800, descent: 230 },
  { name: "candidate-3", xHeight: 548, capHeight: 730, ascent: 776, descent: 275 },
];

const EM = 1000;
const BASE_PX = 16; // nominal size chosen for body text

console.log("candidate     x/em    x-height  cap height  x/cap height");
for (const c of candidates) {
  const xRatio = c.xHeight / EM;
  const xPx = xRatio * BASE_PX;
  const capHeightPx = (c.capHeight / EM) * BASE_PX;
  console.log(
    `${c.name.padEnd(13)} ${xRatio.toFixed(3)}   ${xPx.toFixed(2).padStart(9)} px ${capHeightPx.toFixed(2).padStart(8)} px  ${(c.xHeight / c.capHeight).toFixed(3)}`
  );
}

// Nominal size required to hit the same apparent size.
// The target is the x-height candidate-3 produces at 16 pixels.
const targetX = (548 / EM) * BASE_PX;
console.log(`\ntarget x-height: ${targetX.toFixed(2)} px (candidate-3, 16px)`);
console.log("candidate     equivalent nominal size   diff");
for (const c of candidates) {
  const required = targetX / (c.xHeight / EM);
  const diff = required - BASE_PX;
  console.log(
    `${c.name.padEnd(13)} ${required.toFixed(2).padStart(18)} px ${(diff >= 0 ? "+" : "") + diff.toFixed(2)} px`
  );
}

// Vertical measure carried by the line box: ascent + descent.
console.log("\ncandidate     ascent+descent (em)   at equivalent size (px)");
for (const c of candidates) {
  const total = (c.ascent + c.descent) / EM;
  const required = targetX / (c.xHeight / EM);
  console.log(
    `${c.name.padEnd(13)} ${total.toFixed(3).padStart(16)} ${(total * required).toFixed(2).padStart(21)}`
  );
}
```

```
candidate     x/em    x-height  cap height  x/cap height
candidate-1   0.480        7.68 px    11.20 px  0.686
candidate-2   0.520        8.32 px    11.44 px  0.727
candidate-3   0.548        8.77 px    11.68 px  0.751

target x-height: 8.77 px (candidate-3, 16px)
candidate     equivalent nominal size   diff
candidate-1                18.27 px +2.27 px
candidate-2                16.86 px +0.86 px
candidate-3                16.00 px +0.00 px

candidate     ascent+descent (em)   at equivalent size (px)
candidate-1              1.000                 18.27
candidate-2              1.030                 17.37
candidate-3              1.051                 16.82
```

The output's second block shows that "16 pixels" says nothing by itself. To hit the same
apparent size, candidate-1 has to go up to 18.27 pixels; the difference is 2.27 pixels,
that is, fourteen percent of the base size. This is why swapping a typeface requires the
whole size scale to be readjusted. The scale is the subject of the next lesson, and its
input is this computation.

The third block gives a second result. The sum of ascent and descent sets the base
height of the line box. Because candidate-3 operates at a smaller nominal size for the
same apparent size, its line box comes out shorter too: 16.82 pixels against 18.27. Of
two text blocks set at the same leading multiplier, one will sit tighter even though
both are at the same apparent size.

The x-height ratioed to cap height (the x/cap height column) gives a second signal. As
this ratio rises, lowercase letters approach the size of capitals; the leading bump of
words that start with a capital weakens. Where word shape speeds up scanning — record
titles in search results — this is the cost of too high a ratio.

## Distinguishing Characters From Each Other

Legibility has two auditable faces: whether the needed characters exist in the
typeface, and whether the ones that exist are not confused with each other. Both are
tested against the text that actually appears in the interface, not against a sample
sentence.

```js
// coverage.mjs — auditing catalog text for typeface coverage and distinguishability

// Fields that actually appear in the catalog interface.
const records = [
  { title: "Işık ve Gölge: Bir Optik Tarihi", author: "Öztürk, Şükran", year: 1998, shelfCode: "QC353.I8", isbn: "9789750718533" },
  { title: "Çağdaş Türk Şiirinde İmge", author: "Ilgaz, İbrahim", year: 2004, shelfCode: "PL248.C1", isbn: "9789944882101" },
  { title: "Ağ Protokolleri El Kitabı", author: "Gündoğdu, Elif", year: 2011, shelfCode: "TK5105.5", isbn: "9786055829476" },
];

// 1) Coverage check: the code-point ranges a candidate font declares.
const ranges = {
  "latin-basic-only": [[0x0020, 0x007e]],
  "latin-1-supplement": [[0x0020, 0x007e], [0x00a0, 0x00ff]],
  "latin-extended-a": [[0x0020, 0x007e], [0x00a0, 0x00ff], [0x0100, 0x017f], [0x2010, 0x2027]],
};

function covers(ranges, code) {
  return ranges.some(([low, high]) => code >= low && code <= high);
}

const text = records.map((r) => `${r.title} ${r.author} ${r.year} ${r.shelfCode} ${r.isbn}`).join(" ");
const used = [...new Set([...text])].sort((a, b) => a.codePointAt(0) - b.codePointAt(0));

console.log(`distinct characters across catalog text: ${used.length}`);
for (const [name, r] of Object.entries(ranges)) {
  const missing = used.filter((c) => !covers(r, c.codePointAt(0)));
  const written = missing
    .map((c) => `${c}(U+${c.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")})`)
    .join(" ");
  console.log(`${name.padEnd(24)} missing ${String(missing.length).padStart(2)}: ${written || "-"}`);
}

// 2) Distinguishability: character sets that can be confused with each other.
const confusable = [
  ["I", "l", "1", "|"],
  ["0", "O", "o"],
  ["5", "S", "s"],
  ["8", "B"],
  ["I", "İ", "i", "ı"],
];

console.log("\nfield      confusable set members present together");
const fields = [
  ["shelfCode", records.map((r) => r.shelfCode)],
  ["isbn", records.map((r) => String(r.isbn))],
  ["author", records.map((r) => r.author)],
];
for (const [fieldName, values] of fields) {
  const letters = new Set(values.join("").split(""));
  const overlapping = confusable
    .map((set) => set.filter((c) => letters.has(c)))
    .filter((v) => v.length >= 2)
    .map((v) => v.join("/"));
  console.log(`${fieldName.padEnd(10)} ${overlapping.length ? overlapping.join("  ") : "-"}`);
}
```

```
distinct characters across catalog text: 54
latin-basic-only         missing  9: Ç(U+00C7) Ö(U+00D6) ö(U+00F6) ü(U+00FC) ğ(U+011F) İ(U+0130) ı(U+0131) Ş(U+015E) ş(U+015F)
latin-1-supplement       missing  5: ğ(U+011F) İ(U+0130) ı(U+0131) Ş(U+015E) ş(U+015F)
latin-extended-a         missing  0: -

field      confusable set members present together
shelfCode  I/1
isbn       -
author     I/l  I/İ/i
```

The first block shows why a coverage problem cannot be caught by eye. Latin-1 Supplement
covers most of Turkish's letters; the five code points still missing are exactly the
dotted and dotless `i` pair, `ğ`, and `ş` in the Latin Extended-A block. A typeface looks
flawless for as long as these five letters do not appear in the sample text. Characters
that fall back to a substitute family are drawn with a different x-height, and two
separate typefaces appear within the same word. Code points were defined in the How
Computers Work course; the audit here is a direct application of them.

The second block tells which field carries which confusion. In the shelf code field, `I`
and `1` occur together; this field is used by being read aloud or copied by hand in a
library, so the cost of confusion is concrete. The ISBN field carries only digits, so it
does not collide with any set in the list. The author field, however, has a confusion
specific to Turkish: capital `I`, lowercase `l`, and dotted `İ` occur in the same field.

This finding has two separate solutions, and they touch different places. Giving the
shelf code field a monospace family separates letters and digits into two distinct
drawing traditions — this is a typeface decision. The `I`/`İ` confusion in the author
field is not solved by the typeface, because both are genuinely distinct letters that
are needed; what is needed there is to check which form — serifed base or hooked — the
candidate draws its capital `I` with.

## The Hierarchy Load a Family Can Carry

The Visual Hierarchy lesson established as a rule that critical distinctions are
expressed in at least two channels. One of the channels was weight. The number of
weights a typeface family offers directly limits how many places this rule can be
applied.

The catalog interface needs at least three weight levels: bold for the record title,
regular for body text, and regular again but at lower contrast for suppressed metadata.
A family that offers two weights covers this. A family that offers a single weight does
not; in that case the entire load of the hierarchy falls on the size and contrast
channels, producing the one-legged distinction case computed in the first lesson.

Figure style is a second constraint. A family's digits can be drawn as **proportional**
or **tabular** figures. In proportional figures, the `1` sits narrower than the others;
in publication years stacked one under another, the column drifts. If the catalog
results list aligns years in a column, tabular figures are required. A monospace family
already provides this; a sans-serif family needs it declared as a separate feature.

The third constraint is whether a genuine italic cut exists. If the family has no
italic cut, the browser produces an imitation by slanting the roman letters; this
imitation distorts letter ends and reduces legibility at small sizes. In the catalog
interface, italics are needed in only one place — the citation in the record detail —
and even that single place constrains the family choice.

When these three constraints come together, the selection criterion simplifies: does the
family actually offer the channels the interface needs? If it does not, the missing
channel's load transfers to other channels, and those channels are already full.

## Summary

- Legibility is measured at the character level, readability at the block level; the
  first is solved by a typeface change, the second by a layout change.
- Typeface classes make it possible to decide without naming a product; each class makes
  one job cheaper and another more expensive.
- Nominal size does not give apparent size; apparent size is computed from the x-height
  ratio, and hitting the same measure requires a different nominal size in different
  families.
- Alphabet coverage is audited against the code points that actually occur in the
  interface; missing coverage is invisible in the sample text but shows itself through
  characters that fall back to a substitute family.
- Character confusion is audited field by field; which confusable set actually
  co-occurs in which field is a computable question.
- The number of weights a family offers, its digit width, and whether it has a genuine
  italic cut directly limit how many channels the hierarchy can be built on.

## Next Step

This lesson fixed a single measure — the base size of body text — and showed that this
measure changes across families. But the interface does not have a single size: page
title, record title, author name, metadata, and button text sit at separate steps. If
these steps are each chosen individually, the crowd of numbers grows, and the
distinction thresholds measured in the first lesson are hit by chance or missed
entirely. The next lesson derives the steps from a base and a ratio, rounds the
resulting scale to the pixel grid, and computes whether neighboring steps actually
clear the distinction threshold.
