All posts

Windows 95 fonts for the modern web

3 min read

There’s a font that anyone who touched a computer in the 90s recognizes immediately.

MS Sans Serif. That small, square, pixel-visible font that showed up everywhere in Windows 95: menus, buttons, dialog boxes, title bars.

React95 already had the components and the styles. The fonts, no. In the browser, you’d get the system fonts. I felt like something was missing. React95 was built to be pixel perfect, and using the wrong font broke that.

There are plenty of fonts out there that look like MS Sans Serif. W95FA is one of them, made to remember that era. And they work, from a distance. Up close, they’re not the same. They’re not pixel perfect.

I wanted exactly that one.

The problem

The original Windows 95 fonts live inside .FON files, a Microsoft binary format from 1987, built for bitmap fonts in the DOS era. There’s no direct path from a .FON to a modern @font-face. Nobody’s going to just “import” this in CSS.

So the question became: can it be done?

The extraction

The answer was Python. Not the language I use day to day, but what I needed. With a little help from AI to get unstuck on the parts I didn’t know well, I was able to keep moving.

A library called monobit reads old .FON files and extracts the glyph data. From there, fontTools rebuilds each size as a proper TTF, with correct metrics and the gasp table configured so browsers wouldn’t apply antialiasing and ruin the pixels. Then brotli compresses to WOFF2 and embeds the result as base64 directly in the CSS.

Each font, each size, became a standalone CSS file with a ready-to-use @font-face.

The strange thing about bitmap fonts

Vector fonts scale: you set font-size: 32px and the font adapts. Bitmap fonts don’t work that way. Each size is a fixed grid of pixels — scale it up and the pixels fall apart: blurred, misaligned, wrong. You either render at the right resolution or you don’t.

This means R95 Sans Serif 14pt and R95 Sans Serif 18pt aren’t sizes of the same family. They’re different families. Each has its own name, its own native pixel height, and needs to be used at the right scale.

body {
font-family: 'R95 Sans Serif 14pt';
font-size: 24px; /* native height of 14pt at 96 dpi */
}

It’s counterintuitive until it clicks: each size is a different font because every pixel was drawn by hand for that exact scale.

The package

The result was @react95/fonts: four variants, MS Sans Serif and MS Serif, at 96 dpi and 120 dpi, with six sizes each. Twenty-eight CSS files generated automatically by the Python script, with base64 embedded, no extra network requests.

npm install @react95/fonts

Pick how much you want:

// Everything
import '@react95/fonts';
// One full variant
import '@react95/fonts/sans-serif';
// One specific size
import '@react95/fonts/sans-serif/14pt';

The code is on GitHub. The package is on npm. And if you want to see the fonts before installing, there’s a demo built with React95 itself.

Try it here:

R95 Sans Serif 8pt
R95 Sans Serif 10pt
R95 Sans Serif 12pt
R95 Sans Serif 14pt
R95 Sans Serif 18pt
R95 Sans Serif 24pt
R95 Sans Serif HiRes 8pt
R95 Sans Serif HiRes 10pt
R95 Sans Serif HiRes 12pt
R95 Sans Serif HiRes 14pt
R95 Sans Serif HiRes 18pt
R95 Sans Serif HiRes 24pt
R95 Serif 8pt
R95 Serif 10pt
R95 Serif 12pt
R95 Serif 14pt
R95 Serif 18pt
R95 Serif 24pt
R95 Serif HiRes 8pt
R95 Serif HiRes 10pt
R95 Serif HiRes 12pt
R95 Serif HiRes 14pt
R95 Serif HiRes 18pt
R95 Serif HiRes 24pt
Config

No market case

Nobody needs MS Sans Serif to ship a product. But there’s a difference between “close enough” and “correct,” and that difference bothered me.

React95 exists because I wanted to learn by building something fun. @react95/fonts exists for the same reason: the right font, at the right pixel. Sometimes that turns into a package other people install.