Skip to content

Typography

One typeface, one scale of semantic roles, and optical details that make text feel set rather than rendered.

Interfaces are mostly text. prfct treats typography as the primary material of the system: a single, highly legible family, a small set of named roles, and the quiet details — optical sizing, dynamic tracking, tabular figures — that separate a designed interface from a default one.

Release notes

Faster builds, calmer dashboards

v1.4Published September 18 · 4 min read

Incremental builds now reuse compiled modules across branches, cutting median deploy time by 38%. The dashboard was rebuilt on prfct, with denser tables and tabular figures that line up.

What changed

Heading, body and caption roles do the work — no one-off sizes, no stray weights.

Typefaces

Inter

prfct is set in Inter 4.1, a variable family designed for screens. It carries two axes: weight, and optical size — the letterforms subtly redraw themselves with size. Text sizes get open counters and generous spacing; from 32px up, Inter's Display cut takes over with tighter spacing and finer details. You never switch fonts for headlines; the font adapts.

Aa

Inter 4.1Inter 4.1, variable, self-hosted with every OpenType feature. Optical sizing switches to the Display cut from 32px.

ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 ąćęłńóśźż ÄÖÜß €$£ → ← ✓ ⌘ ⌥ ⇧

Ag300
Ag400
Ag500
Ag600
Ag700
Ag800

prfct self-hosts Inter rather than loading it from a font CDN. Hosted builds commonly strip OpenType features to save bytes — including the tabular figures data interfaces depend on. prfct's build keeps every feature and both axes, subsets to the Latin script (with full Polish, Czech, Turkish and other Latin Extended-A coverage), preloads only the upright style, and ships a metric-matched fallback so text doesn't reflow when the font arrives.

Geist Mono

Code, keyboard keys and machine-readable values use Geist Mono: compact, even-width, and designed to pair with neo-grotesque sans serifs.

Aa

Geist MonoGeist Mono for code, keyboard keys and tabular data. Load it with next/font as --font-geist-mono; without it, the platform's monospace is used.

ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 ąćęłńóśźż ÄÖÜß €$£ → ← ✓ ⌘ ⌥ ⇧

Ag300
Ag400
Ag500
Ag600
Ag700
Ag800

Type scale

prfct names type by role, not by size. A role bundles size, line height, tracking and weight into one decision, so text styles can't drift apart one property at a time.

72px / 72px · 620
-0.032em

Interfaces that feel inevitable

Hero headlines on marketing surfaces.

60px / 62px · 620
-0.03em

Interfaces that feel inevitable

Page heroes.

48px / 52px · 620
-0.028em

Interfaces that feel inevitable

Section openers.

40px / 44px · 620
-0.025em

Interfaces that feel inevitable

Feature titles.

32px / 38px · 620
-0.022em

Interfaces that feel inevitable

Documentation page titles.

28px / 36px · 600
-0.021em

Quarterly revenue report

Top-level app page titles.

24px / 32px · 600
-0.019em

Quarterly revenue report

Section titles in product UI.

20px / 28px · 600
-0.016em

Quarterly revenue report

Card and dialog titles.

16px / 24px · 600
-0.011em

Quarterly revenue report

Group and panel titles.

14px / 20px · 600
-0.006em

Quarterly revenue report

Dense list and table headings.

18px / 28px · 400
-0.014em

prfct turns decisions into defaults, so teams ship interfaces that are consistent, accessible and calm.

Lead paragraphs, introductions.

16px / 26px · 400
-0.011em

prfct turns decisions into defaults, so teams ship interfaces that are consistent, accessible and calm.

Long-form reading.

14px / 22px · 400
-0.006em

prfct turns decisions into defaults, so teams ship interfaces that are consistent, accessible and calm.

Default product UI copy.

13px / 20px · 400
-0.003em

prfct turns decisions into defaults, so teams ship interfaces that are consistent, accessible and calm.

Secondary copy in dense UI.

16px / 24px · 500
-0.011em

Save changes

Large controls.

14px / 20px · 500
-0.006em

Save changes

Buttons, inputs, menu items.

13px / 18px · 500
-0.003em

Save changes

Compact controls, badges.

12px / 16px · 500
0em

Save changes

Micro labels, counters.

12px / 16px · 400
+0.002em

Updated 2 minutes ago

Captions, timestamps, helper text.

11px / 16px · 600
+0.08em

Foundations

Eyebrows above headings. Uppercase.

13px / 22px · 400
0em

const theme = resolveTheme({ brand })

Inline and block code (mono).

<h1 className="text-display-sm">Settings</h1>
<h2 className="text-heading-md">Notifications</h2>
<p className="text-body-sm text-muted-foreground">Choose what we email you about.</p>

Tailwind's own size utilities (text-sm, text-lg…) remain available for fine-grained UI work, and they carry prfct's tracking too.

Role familyUse for
DisplayMarketing heroes, page titles on documentation and landing surfaces. Never inside product chrome.
HeadingTitles inside the product: pages, sections, cards, dialogs.
BodyReading text. body-md for long form, body-sm as the UI default.
LabelText on controls: buttons, inputs, menu items, tabs.
Caption / overlineMetadata, timestamps, helper text, eyebrows above headings.

Details that matter

Dynamic tracking

Letter spacing follows Inter's size-dependent metrics: slightly open at 12px, where letters need air, progressively tighter as size grows, where default spacing starts to look loose. Every role and every Tailwind size already includes the right value — don't add tracking-* to normal text.

A voice for headlines

Display roles switch on Inter's single-storey a (cv11) and rounded quotes (ss03). It gives large headlines a softer, more distinctive voice, while body and UI text keep the default double-storey a, which remains more legible at small sizes.

Figures that align

Numbers in tables, stats and timers must line up in columns. prfct tables turn on tabular figures by default; anywhere else, add the numeric utility.

Proportional figures
Frankfurt1,204,11881.4 ms
Warsaw987,45074.9 ms
Virginia11,009,871112.0 ms
Tabular figures (numeric)
Frankfurt1,204,11881.4 ms
Warsaw987,45074.9 ms
Virginia11,009,871112.0 ms
<span className="numeric">1,204,118</span>

Balanced and pretty wrapping

Headings use text-wrap: balance so lines are even; paragraphs use text-wrap: pretty to avoid orphaned last words. Both are set globally.

Guidelines

  • Use roles, not sizes. If a design needs a size that no role provides, it usually needs a different role.
  • One heading per level. Skip levels in neither the markup nor the visual scale.
  • Emphasize with weight, not size. A font-medium word inside body-sm is enough.
  • Keep reading width between 45 and 75 characters. Use max-w-prose or max-w-2xl for long text.
  • Sentence case everywhere — headings, buttons, menu items. Title Case reads as shouting in interfaces and makes names ambiguous.

Billing email

Invoices are sent to finance@acme.co.

Do.Hierarchy from roles: one heading, one body style, one caption.

Billing Email

Invoices Are Sent To finance@acme.co.

Don’t.Mixed sizes, weights and caps compete for attention.

Accessibility

  • Every size is set in rem, so text scales with the reader's browser font size. Layouts are tested at 200% zoom.
  • Body roles use a line height of at least 1.5× for reading text and never set weights below 400 at small sizes.
  • Muted text still meets 4.5:1; see Color.
  • Uppercase is reserved for short overlines, which are tracked out and use case-sensitive punctuation so they remain readable.