Typography

arXiv sets all text in the IBM Plex family with STIX Two Math for notation. All fonts are self-hosted and open source with no external font services.

The font stack

The type family is IBM Plex, plus STIX Two Math for notation. Each face has a job.

IBM Plex Sans

Body text — paragraphs, author names, abstracts, UI.

Gauge-independent approach to inflation in quadratic gravity
We explore inflation driven by the Starobinsky-like R + R² term using a manifestly gauge-independent approach to cosmological perturbation theory.
Regular 400Medium 500Semibold 600
Relevant code
font-family: var(--ds-font-sans);
--ds-font-sans
The default: where no other face applies, this is the answer. Weights 400 / 500 / 600.

IBM Plex Sans Condensed

Labels — metadata labels, table headers, uppercase section labels.

SUBJECTS · DOI · LICENSE · ANCILLARY FILES
ArXiv identifierJournal DOIWho cites this paper
Relevant code
font-family: var(--ds-font-condensed);
--ds-font-condensed
Labels, table headers and metadata, usually small and uppercase. Weights 500 / 600.
.ds-panel-label
The ready-made label in this face; see Panel label below.

IBM Plex Mono

Code & identifiers — arXiv IDs, DOIs, BibTeX, code blocks.

arXiv:2604.22725 [gr-qc]
@article{palomares2026gauge, doi={10.48550/arXiv.2604.22725}}
Relevant code
font-family: var(--ds-font-mono);
--ds-font-mono
Identifiers and code. Weights 400 / 500. Inline <code> and <pre> already use it; see Code blocks below.

IBM Plex Serif italic

Annotation voice — footnote text and figure alt-text in the reader margins. “The editor speaking quietly next to the body text.”

Starobinsky's 1980 model adds only the R² term; the resulting scalaron drives inflation while leaving the gravitational sector otherwise unmodified.
Relevant code
<p class="ds-annotation">Figure 2: the scalaron potential.</p>
.ds-annotation
The annotation voice: --ds-font-serif, italic, 400, 0.8125rem, --ds-text-muted. It is a voice, not a layout role; where it sits is the consumer's decision.
--ds-font-serif
The serif stack. On the public site and in internal tools it is used only through .ds-annotation.

IBM Plex Serif

Gives an editorial flavor to headlines and pull quotes. Currently used only on the blog.

Updates on all things arXiv
I am excited and honored to join arXiv at such a significant time for the organization.
Relevant code
font-family: var(--ds-font-serif);
--ds-font-serif
Upright, weights 400 and 600. Outreach properties only; see outreach sites.

STIX Two Math

Mathematical notation.

𝑑𝑠² = −𝑑𝑡² + 𝑎²(𝑡) δ𝑖𝑗 𝑑𝑥𝑖𝑑𝑥𝑗
Relevant code
font-family: "STIX Two Math", "Cambria Math", math;
<math>
No font token exists for math; use the stack directly on the notation. Loads with font-display: auto per DESIGN-POLICIES.

The body text scale

The text sizes are set in rem so they move with the reader's base size. Browsers set the base size to 16px unless the reader changes it, so 1rem is 16px by default; the pixel sizes on this page assume that default.

1rem · 16px
Body text: paragraphs, abstracts, interface copy, on a 1.5 line-height.
0.875rem · 14px
Small: secondary text, captions, and the code block.
0.8125rem · 13px
Annotation: a margin footnote, figure alt text.
0.75rem · 12px
Panel label
0.9em
inline code follows the size of whatever it sits inside, a heading included.
Relevant code
<p>Body text.</p>
<small>Secondary text.</small>
<p class="ds-annotation">A margin footnote.</p>
<p class="ds-panel-label">Paper information</p>
<p>An identifier: <code>arXiv:2604.22725</code></p>
<p>
1rem, the reader's base size. Nothing sets it; the browser does.
<small>
0.875rem in --ds-text-muted.
<pre>
0.875rem, line-height 1.6, on the dark chrome ground.
.ds-annotation
0.8125rem, serif italic, line-height 1.45.
.ds-panel-label
0.75rem, condensed, uppercase.
<code>
0.9em, so it follows the size of its parent.

The heading scale

Four levels, each level is a quarter larger than the one below it, which puts h1 at exactly twice body size. Sizes are in rem so they follow the user's base setting. That base is 16px unless the reader has changed the browser's default, so the pixel sizes below assume 16px.

h1 · 2rem · 700
Gauge-independent approach to inflation
h2 · 1.5rem · 600
Submission history
h3 · 1.25rem · 600
Gauge-invariant perturbations
h4 · 1rem · 600
Boundary conditions
Relevant code
<h1>Gauge-independent approach to inflation</h1>
  <h2>Submission history</h2>
    <h3>Gauge-invariant perturbations</h3>
      <h4>Boundary conditions</h4>
<h1>
The page's one subject: what the page is about, stated once — a paper title, a form name, a console view. A page that seems to need two h1s is two pages. 2rem (32px) · 700 · line-height 1.25 · space above 0, below --ds-space-4.
<h2>
A major section: a top-level division of that subject — Abstract, Submission history, References, each step of a submission form. 1.5rem (24px) · 600 · line-height 1.25 · space above --ds-space-8, below --ds-space-3.
<h3>
A division inside one section: a subsection of a paper, or a card heading inside a panel. It only exists where an h2 already does. 1.25rem (20px) · 600 · line-height 1.25 · space above --ds-space-8, below --ds-space-2.
<h4>
The smallest named step: a named paragraph in a paper, a field group in a form. This is the last level the scale defines; needing an h5 is a sign the content wants restructuring, not a fifth size. 1rem (16px) · 600 · line-height 1.25 · space above --ds-space-4, below --ds-space-2.

Links are typography too. Inline links in body text are always underlined (to pass WCAG 1.4.1). Standalone navigation links and groups of links that are not part of body text (headers, footers, author list) drop the underline to reduce noise.

Full state-by-state references (rest / hover / visited / focus / disabled, light and dark): Links. Link color tokens with ratios: Colors.

Reading text with an inline link in its resting state — Link Blue, underlined, offset so the descenders stay readable. Hover darkens it and increases the thickness of the underline. A visited link turns Visited Purple; keyboard focus draws the standard 3px ring.
Relevant code
Reading text with <a href="/abs/2604.22725">an inline link</a> in it.
.ds-link
The inline text link. Both surfaces use .ds-link and the --ds-link-* tokens: the same grammar, the same names and the same values. Inside a .ds-page a bare <a> gets the same treatment without the class.
--ds-link, --ds-link-hover, --ds-link-visited
Rest, hover and visited colours. The underline does not change between states.
--ds-focus-ring
The 3px keyboard focus ring, drawn on :focus-visible only.

A list of author links

Relevant code
<p class="ds-link-list">
  <a href="/a/palomares_a_1">Adrian Palomares</a>,
  <a href="/a/chen_m_1">Mei Lin Chen</a>,
  <a href="/a/mensah_k_1">Kwame Mensah</a>
</p>
.ds-link-list
On the element that holds a list in which every item is a link, such as an author list or a column of footer links. The links drop the underline at rest and keep everything else: Link Blue, the underline on hover, the focus ring and the visited colour. There is no ordinary text beside them for the underline to separate them from, so colour alone is enough here.

Search-matched words

The words that matched a search are marked with <mark>, in the accent wash and a heavier weight.

Weak-Lensing Shear Response for Photometric Redshift-Based Tomographic Binning

Relevant code
<p>Weak-<mark>Lensing</mark> Shear Response for Photometric Redshift-Based Tomographic Binning</p>
<mark>
One matched word or phrase. No class: the element is styled inside .ds-page. The weight marks it as well as the colour, so it is never colour alone. In internal tools the wash is Access Lime.

Code blocks and the copy button

A code block uses Plex Mono on Repository Brown, and always includes a copy button.

<button class="ds-btn ds-btn-primary" type="button">Save</button>
Relevant code
<!-- Write the block the way you already do -->
<pre><code>&lt;button class="ds-btn"&gt;Save&lt;/button&gt;</code></pre>

<!-- And include the script once, at the foot of the page -->
<script src="copy-code.js" defer></script>
<pre><code>
The block. Escape <, > and & inside it. No wrapper and no button of your own.
<script src="copy-code.js" defer>
Once per page, at the foot. It finds every <pre> on the page and adds the control.
.ds-code, .ds-code-copy
The wrapper and the button. The script writes both; never hand-build them.
[data-no-copy]
On a <pre>, opts that block out — for a block that is an illustration of output rather than something to take away.

Panel label

The small uppercase Condensed label that names a panel, a column or a group. It is not a heading level and stays out of the document outline — if a reader needs to navigate to it, it is an h3 or an h4 at the sizes above.

The test: if a paragraph follows it, or it covers several examples, it is a heading. Otherwise it is a panel label, naming the one example directly below it. A panel label is a few words, never a sentence. Public and internal versions of one example take panel labels.

Note

Do not reach for it as a heading substitute. A label that a reader needs to navigate to is a heading, and belongs in the outline.

Paper information

The label sits closer to what it introduces than to what precedes it, so it bonds downward. That relationship is intrinsic to the thing, not something its context supplies — which matters because half the instances in these pages are paragraphs rather than headings.

Relevant code
<p class="ds-panel-label">Paper information</p>
.ds-panel-label
One class for the whole system: 0.75rem · 600 · Condensed · uppercase · 0.04em · --ds-text-muted. Use it wherever this treatment appears — panel headings, column labels, the label above a group of controls — on whichever element is semantically right. A component that owns its internal spacing takes the margin back.
<dt>
A definition term inside .ds-acc-body is this label, and has no class.

Accessibility essentials

Rules

Which faces where — the three contexts

One family, three contexts. Internal tools and the public site share the working set; outreach properties add the upright serif and the heaviest sans. A face without a mark is not used in that context.

Face · weightsInternal toolsPublic siteOutreach
IBM Plex Sans · 400 / 500 / 600✓ all text✓ all text✓ all text
IBM Plex Sans · 700 + 700 italic✓ h1 only✓ h1 only✓ the one heading that must not read as an article
IBM Plex Sans Condensed · 500 / 600✓ labels, table headers✓ labels✓ labels
IBM Plex Mono · 400 / 500✓ IDs, code✓ arXiv IDs, code✓ code, plus a display register (wordmark, standfirst)
IBM Plex Serif · 400 italicannotation voice only (.ds-annotation)annotation voice only (.ds-annotation)✓ emphasis, bylines, prev/next
IBM Plex Serif · 400 / 600 upright——✓ headlines to display size, pull quotes, drop caps
STIX Two Math · 400—✓ math in HTML papers—
  • 700 is the ceiling. IBM Plex ships no heavier weight, so a CSS 800 either falls back or gets faux-bolded, differently per browser. Ask for the weight that exists.
  • Two serif jobs, kept apart. Upright serif is the editorial voice — headlines, quotes, drop caps. Italic serif stays the annotation voice everywhere. A heading that must not read as "another article" uses the heavy sans instead, which is why 700 exists in this context at all.
  • Mono as display type is an outreach liberty: the typewriter register echoes the main site's identifiers. On the public site, mono still means "this is an identifier or code."

Headings

Space is given as spacing tokens, not pixels, because that is what design-system.css implements — --ds-space-8 is 2rem, --ds-space-6 is 1.5rem, and so on down the scale.

  • The ratio is 1.25 — a major third — rounded to quarter-rem steps: 1, 1.25, 1.5, 2rem. A strict geometric run would put h2 at 1.5625rem; a quarter of a rem is the number a person can hold in their head and type without checking. The rounding is what makes h1 land on exactly double the body size.
  • 700 appears once, at h1. IBM Plex ships nothing heavier, so 700 is the ceiling and it is spent on the one heading that names the page. Everything below is 600. Always state the weight: a rule that sets only font-size inherits the browser default bold, which is how several pages ended up with an h3 heavier than the h2 above it.
  • Space above is at least twice the space below. A heading belongs to the text it introduces, not the text it follows, and the gap has to say so. The values sit on the 4px rhythm in Spacing.
  • Long titles clamp. An h1 that can run to seven words uses clamp() between rem bounds rather than a fixed 2rem — see "Fluid where titles meet phones" below.
  • The 11px uppercase Condensed label is not a heading level. Set in --ds-font-condensed and --ds-text-muted, it marks a panel or a column and stays out of the document outline. If a reader needs to navigate to it, it is an h3 or an h4 at the size above, not a label.

The copy button

  • There is no markup for the button, and that is the design. copy-code.js finds every <pre> on the page, wraps it in .ds-code and inserts the control, which is .ds-code-copy. A block added next year is covered without anyone remembering, which a per-block wrapper would not be. Every page under docs/ already loads the script.
  • The button sits over the top corner of the code, on a solid ground. The block keeps even padding, and a long line that scrolls under the button is hidden behind it rather than showing through.
  • It is an icon, and it still says “Copy”. The two overlapping sheets are the glyph people already know, so the control needs no label taking up room over the code. Both SVGs are aria-hidden and the button has aria-label="Copy code", which is what a screen reader announces. The title is the pointer affordance only — assistive technology takes the aria-label and ignores it.
  • Success is announced, not just shown. The icon becomes a green tick, and a visually hidden role="status" region says “Copied to clipboard”. Both are needed: a tick is invisible to a screen reader, and the button’s own name does not change because its action did not change — it is still the copy button. Both reset after two seconds.
  • The name stays put while the icon moves. Relabelling a control to report a result is the same mistake as bolding a switch label on toggle: the name of a control is what it does, not what just happened. The result belongs in the live region.
  • It works from disk. The asynchronous Clipboard API needs a secure context, which file:// is not, and it rejects when the window is not focused. Both cases fall back to execCommand, and if that fails too the status region says so rather than the button quietly doing nothing.
  • Without JavaScript there is no button and the code is still selectable. The button is a convenience; it is never the only way to get the text.
  • Opt a block out with <pre data-no-copy> — for a block that is an illustration of output rather than something to take away.

Sizes are rem, and a box that holds text scales with its text

A browser font-size setting has to reach arXiv content (accessibility-priorities.md, cognitive/dyslexia section), so every type size is rem. But a label that grows inside a box that does not is no better than a label that never grew: set a control's padding and minimum width in em, against its own label, and the whole control follows the reader. Absolute floors stay absolute — min-height: 24px is a WCAG target size, not a type size. Body text never scales fluidly — it follows the user's base size exactly.

Self-hosted, with honest fallbacks

No Google Fonts, no Typekit, no CDN fonts (DESIGN-POLICIES.md). Every font-family declaration includes system fallbacks; text fonts load with font-display: swap (readability first), STIX Two Math with font-display: auto (math must render in the correct face).

Do not fight the reader

No fixed line-heights, no pinned letter-spacing, no !important on font-family in content areas. User stylesheets, reader-mode extensions, and OS settings win — "HTML gives you freedom: you can use software to change colors or typefaces" (research, dyslexia section).

Spec

Everything above is the design. This is what an implementer needs: the tokens, the files, the @font-face rules and the fallbacks. Placed last because it is reference, not reading.

Why IBM Plex

Open source under the SIL Open Font License, so there is no licensing cost and no vendor dependency; self-hostable as woff2 from arXiv's own assets; one designer's family covering Sans, Condensed, Mono and Serif so nothing is mixed; broad language coverage including Latin Extended, Cyrillic, Greek, Arabic, Hebrew, Devanagari and Thai; and it pairs with STIX Two Math, which is built for the same technical context. The family was re-evaluated in June 2026 against Atkinson Hyperlegible Next, Source Sans 3, Inter and Public Sans, and kept — that comparison lives in whiteboard/planning/proposals, not here.

Tokens

--ds-font-sans:      "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--ds-font-condensed: "IBM Plex Sans Condensed", "IBM Plex Sans", sans-serif;
--ds-font-mono:      "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;
--ds-font-serif:     "IBM Plex Serif", Georgia, "STIX Two Text", "Times New Roman", serif;

Every stack includes system fallbacks so text stays readable if the web fonts fail: sans falls to the OS system font, mono to the platform monospace. There is no math token — STIX Two Math is applied by the paper renderer on HTML paper pages, not by the design system, so the stylesheet does not declare one.

Available weights, and sanctioned ones

Every weight and style IBM Plex ships is self-hosted — Thin 100, ExtraLight 200, Light 300, Regular 400, Text 450, Medium 500, SemiBold 600 and Bold 700, each with an italic, across Sans, Sans Condensed, Mono and Serif. That is 64 files and about 4 MB in the repository, and it costs a reader nothing: a @font-face rule downloads only when a page uses that face.

Available is not the same as approved. Only the faces the design system uses are declared in fonts.css; the rest are present but commented out, so you can see what exists without going to look for it. Uncommenting one is how you turn it on — and it is a decision, not a formality.

WeightUse it for
400 RegularBody copy, and anything that is read rather than scanned.
500 MediumNavigation, table headers, the quieter emphasis.
600 SemiBoldHeadings, button labels, and the label half of a label/value pair.
700 BoldThe h1, and nothing else — see the heading scale.
100, 200, 300, 450Lighter weights are present, but unused and commented out. See warning below.

Light weights are an accessibility problem that contrast ratios do not catch

Thin, ExtraLight and Light are now reachable. At interface sizes their strokes are too fine for many readers to resolve even when the colour passes AA — the ratio measures the hue, not whether the letterform survives. Body and interface text use 400 or heavier. Anything lighter belongs at display sizes, if anywhere, and is worth testing before it ships.

The files

Self-hosted woff2, from arXiv's static assets. No external font service — see DESIGN-POLICIES for why that one is a hard constraint.

/static/fonts/
  IBMPlexSans-*.woff2            8 weights x roman/italic  = 16 files
  IBMPlexSansCondensed-*.woff2   8 weights x roman/italic  = 16 files
  IBMPlexMono-*.woff2            8 weights x roman/italic  = 16 files
  IBMPlexSerif-*.woff2           8 weights x roman/italic  = 16 files
  STIXTwoMath-Regular.woff2      ← HTML paper pages only

This repo's pattern pages serve the same files from docs/fonts/, declared in fonts.css, which every page links ahead of the stylesheet. The @font-face rules live in that separate file rather than in the design system itself, because consumers bundle the design system with their own font paths. Files come from IBM/plex (OFL).

@font-face

@font-face {
  font-family: "IBM Plex Sans";
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  src: url("/static/fonts/IBMPlexSans-Regular.woff2") format("woff2");
}
/* …one per weight and style… */

@font-face {
  font-family: "STIX Two Math";
  font-weight: 400;
  font-display: auto;
  src: local("STIX Two Math"),
       url("/static/fonts/STIXTwoMath-Regular.woff2") format("woff2");
}
  • font-display: swap for text: the fallback shows immediately and swaps when the real font arrives. Readability beats visual stability.
  • font-display: auto for STIX Two Math: maths in a fallback font can be actively wrong, so it is better to wait briefly.
  • local() for STIX Two Math skips the ~300KB download if the reader already has it.
  • Total: about 135KB for the Plex woff2 files, plus STIX only on paper pages.

Which faces each surface uses

SurfaceBodyLabelsCodeMath
Abstract pagesPlex SansPlex Sans CondensedPlex Mono—
HTML paper pagesPlex SansPlex Sans CondensedPlex MonoSTIX Two Math
Internal toolsPlex SansPlex Sans CondensedPlex Mono—
Homepage, listings, searchPlex SansPlex Sans CondensedPlex Mono—

Sizes in use

The sizes the stylesheet sets, in rem, with the pixel size at the browser's default base size of 16px.

UseSizeWeightFace
Page heading (h1)2rem · 32px700Plex Sans
Section heading (h2)1.5rem · 24px600Plex Sans
Subsection heading (h3)1.25rem · 20px600Plex Sans
h4; body text1rem · 16px600; 400Plex Sans
Secondary text, captions0.875rem · 14px400Plex Sans
Code blocks0.875rem · 14px400Plex Mono
Inline code, identifiers0.9em of the text around it400Plex Mono
Annotation (margin footnote, alt text)0.8125rem · 13px, italic400Plex Serif
Header links0.8125rem · 13px500Plex Sans
Panel labels, table headers0.75rem · 12px, uppercase600Plex Sans Condensed