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 type family is IBM Plex, plus STIX Two Math for notation. Each face has a job.
Body text — paragraphs, author names, abstracts, UI.
font-family: var(--ds-font-sans);
Labels — metadata labels, table headers, uppercase section labels.
font-family: var(--ds-font-condensed);
Code & identifiers — arXiv IDs, DOIs, BibTeX, code blocks.
font-family: var(--ds-font-mono);
<code> and <pre> already use it; see Code blocks below.Annotation voice — footnote text and figure alt-text in the reader margins. “The editor speaking quietly next to the body text.”
<p class="ds-annotation">Figure 2: the scalaron potential.</p>
--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-annotation.Gives an editorial flavor to headlines and pull quotes. Currently used only on the blog.
font-family: var(--ds-font-serif);
Mathematical notation.
font-family: "STIX Two Math", "Cambria Math", math;
font-display: auto per DESIGN-POLICIES.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.
inline code follows the size of whatever it sits inside, a heading included.<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>
--ds-text-muted.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>Gauge-independent approach to inflation</h1>
<h2>Submission history</h2>
<h3>Gauge-invariant perturbations</h3>
<h4>Boundary conditions</h4>
--ds-space-4.--ds-space-8, below --ds-space-3.--ds-space-8, below --ds-space-2.--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 <a href="/abs/2604.22725">an inline link</a> in it.
.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.:focus-visible only.A list of author links
Adrian Palomares, Mei Lin Chen, Kwame Mensah, Abhay Ashtekar
<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>
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
<p>Weak-<mark>Lensing</mark> Shear Response for Photometric Redshift-Based Tomographic Binning</p>
.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.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>
<!-- Write the block the way you already do -->
<pre><code><button class="ds-btn">Save</button></code></pre>
<!-- And include the script once, at the foot of the page -->
<script src="copy-code.js" defer></script>
<, > and & inside it. No wrapper and no button of your own.<pre> on the page and adds the control.<pre>, opts that block out — for a block that is an illustration of output rather than something to take away.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.
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.
<p class="ds-panel-label">Paper information</p>
--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..ds-acc-body is this label, and has no class.<h1> per page, then
<h2>, <h3> and <h4> in order, without
skipping a level. Choose the level from the outline, not from the size you want; the scale gives
each level its size. A screen reader user moves through the page by these levels.em, so the box grows with its label.overflow: hidden unless there is another way
to read all of it. At 400% page zoom a 1280px screen is a 320px screen, and the page must not
scroll sideways there.alt text or next to it..ds-panel-label makes it so. Type it in sentence case, so
copy-and-paste, search and screen readers get the real words.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 · weights | Internal tools | Public site | Outreach |
|---|---|---|---|
| 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 italic | annotation 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 | — |
800 either
falls back or gets faux-bolded, differently per browser. Ask for the weight that exists.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.
font-size inherits the browser default bold, which is how several pages ended
up with an h3 heavier than the h2 above it.clamp() between
rem bounds rather than a fixed 2rem — see "Fluid where titles meet phones" below.--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.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.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.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.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.<pre data-no-copy> — for a
block that is an illustration of output rather than something to take away.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.
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).
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).
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.
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.
--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.
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.
| Weight | Use it for |
|---|---|
| 400 Regular | Body copy, and anything that is read rather than scanned. |
| 500 Medium | Navigation, table headers, the quieter emphasis. |
| 600 SemiBold | Headings, button labels, and the label half of a label/value pair. |
| 700 Bold | The h1, and nothing else — see the heading scale. |
| 100, 200, 300, 450 | Lighter 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.
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-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.| Surface | Body | Labels | Code | Math |
|---|---|---|---|---|
| Abstract pages | Plex Sans | Plex Sans Condensed | Plex Mono | — |
| HTML paper pages | Plex Sans | Plex Sans Condensed | Plex Mono | STIX Two Math |
| Internal tools | Plex Sans | Plex Sans Condensed | Plex Mono | — |
| Homepage, listings, search | Plex Sans | Plex Sans Condensed | Plex Mono | — |
The sizes the stylesheet sets, in rem, with the pixel size at the browser's default base size of 16px.
| Use | Size | Weight | Face |
|---|---|---|---|
| Page heading (h1) | 2rem · 32px | 700 | Plex Sans |
| Section heading (h2) | 1.5rem · 24px | 600 | Plex Sans |
| Subsection heading (h3) | 1.25rem · 20px | 600 | Plex Sans |
| h4; body text | 1rem · 16px | 600; 400 | Plex Sans |
| Secondary text, captions | 0.875rem · 14px | 400 | Plex Sans |
| Code blocks | 0.875rem · 14px | 400 | Plex Mono |
| Inline code, identifiers | 0.9em of the text around it | 400 | Plex Mono |
| Annotation (margin footnote, alt text) | 0.8125rem · 13px, italic | 400 | Plex Serif |
| Header links | 0.8125rem · 13px | 500 | Plex Sans |
| Panel labels, table headers | 0.75rem · 12px, uppercase | 600 | Plex Sans Condensed |