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
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
ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 ąćęłńóśźż ÄÖÜß €$£ → ← ✓ ⌘ ⌥ ⇧
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
ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 ąćęłńóśźż ÄÖÜß €$£ → ← ✓ ⌘ ⌥ ⇧
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.
-0.032em
Interfaces that feel inevitable
Hero headlines on marketing surfaces.
-0.03em
Interfaces that feel inevitable
Page heroes.
-0.028em
Interfaces that feel inevitable
Section openers.
-0.025em
Interfaces that feel inevitable
Feature titles.
-0.022em
Interfaces that feel inevitable
Documentation page titles.
-0.021em
Quarterly revenue report
Top-level app page titles.
-0.019em
Quarterly revenue report
Section titles in product UI.
-0.016em
Quarterly revenue report
Card and dialog titles.
-0.011em
Quarterly revenue report
Group and panel titles.
-0.006em
Quarterly revenue report
Dense list and table headings.
-0.014em
prfct turns decisions into defaults, so teams ship interfaces that are consistent, accessible and calm.
Lead paragraphs, introductions.
-0.011em
prfct turns decisions into defaults, so teams ship interfaces that are consistent, accessible and calm.
Long-form reading.
-0.006em
prfct turns decisions into defaults, so teams ship interfaces that are consistent, accessible and calm.
Default product UI copy.
-0.003em
prfct turns decisions into defaults, so teams ship interfaces that are consistent, accessible and calm.
Secondary copy in dense UI.
-0.011em
Save changes
Large controls.
-0.006em
Save changes
Buttons, inputs, menu items.
-0.003em
Save changes
Compact controls, badges.
0em
Save changes
Micro labels, counters.
+0.002em
Updated 2 minutes ago
Captions, timestamps, helper text.
+0.08em
Foundations
Eyebrows above headings. Uppercase.
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.
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.
| Frankfurt | 1,204,118 | 81.4 ms |
| Warsaw | 987,450 | 74.9 ms |
| Virginia | 11,009,871 | 112.0 ms |
| Frankfurt | 1,204,118 | 81.4 ms |
| Warsaw | 987,450 | 74.9 ms |
| Virginia | 11,009,871 | 112.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-mediumword insidebody-smis enough. - Keep reading width between 45 and 75 characters. Use
max-w-proseormax-w-2xlfor 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.
Billing Email
Invoices Are Sent To finance@acme.co.
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.