# How Hallmark Detects Font Stacks During Its Pre‑Flight Scan

> Learn how Hallmark detects font stacks using CSS custom properties for its pre-flight scan. Optimize your workflow with this efficient font detection method.

- Repository: [Hassan El Mghari/hallmark](https://github.com/Nutlope/hallmark)
- Tags: how-to-guide
- Published: 2026-08-08

---

**Hallmark detects font stacks by reading CSS custom properties (`--font-display`, `--font-body`, `--font-outlier`) from the root stylesheet, normalizing the computed values into arrays, and caching the results in [`.hallmark/preflight.json`](https://github.com/Nutlope/hallmark/blob/main/.hallmark/preflight.json) to avoid redundant processing on subsequent runs.**

The pre-flight scan in Nutlope/hallmark is the first step in ensuring typographic consistency across a project. Before running audits, the tool programmatically discovers the exact font families defined in your CSS, validates them against the "2+1 rule," and stores this metadata for downstream checks.

## CSS Custom Properties as the Source of Truth

Hallmark expects your design system to expose font definitions through three specific CSS variables at the `:root` level. These **CSS custom properties** serve as the canonical source for typography detection.

According to [[`skills/hallmark/references/typography.md`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/references/typography.md)](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/references/typography.md)#L19‑L24, the required variables are:

```css
:root {
  --font-display:  "Fraunces", ui-serif, Georgia, serif;   /* headings, hero */
  --font-body:     "Geist", ui-sans-serif, system-ui, sans;/* prose, UI */
  --font-outlier:  "Geist Mono", ui-monospace, monospace;   /* occasional outlier */
}

```

These definitions typically live in [`site/css/tokens.css`](https://github.com/Nutlope/hallmark/blob/main/site/css/tokens.css) or [`site/css/base.css`](https://github.com/Nutlope/hallmark/blob/main/site/css/base.css), depending on your project structure.

## The Pre‑Flight Detection Process

When Hallmark initiates a scan, it performs the following steps to detect font stacks accurately:

1. **Load stylesheets** – The scanner reads the compiled CSS files (e.g., [`site/css/tokens.css`](https://github.com/Nutlope/hallmark/blob/main/site/css/tokens.css), [`site/css/base.css`](https://github.com/Nutlope/hallmark/blob/main/site/css/base.css)).
2. **Create temporary DOM** – It instantiates a temporary DOM environment, attaches the stylesheets, and queries the computed values of the three custom properties from `:root`.
3. **Normalize values** – The raw CSS strings are split into arrays, preserving the author's specified fallback order.

The extraction logic functions roughly like this:

```javascript
// Illustrative implementation from the pre-flight scanner
function getFontStacks() {
  const root = document.querySelector(':root');
  const styles = getComputedStyle(root);
  
  return {
    display: styles.getPropertyValue('--font-display').trim().split(','),
    body:    styles.getPropertyValue('--font-body').trim().split(','),
    outlier: styles.getPropertyValue('--font-outlier').trim().split(',')
  };
}

```

This process ensures that even complex font stacks with multiple fallbacks are captured correctly.

## Caching Strategy and File Watching

To optimize performance, Hallmark caches detected font stacks rather than parsing CSS on every run. The cache is stored at [`.hallmark/preflight.json`](https://github.com/Nutlope/hallmark/blob/main/.hallmark/preflight.json) and follows an invalidation strategy based on file modification times.

As documented in [[`skills/hallmark/SKILL.md`](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/SKILL.md)](https://github.com/Nutlope/hallmark/blob/main/skills/hallmark/SKILL.md)#L177‑L179, the tool compares the cache timestamp against source files:

```javascript
import { statSync } from 'fs';

const preflightMtime = statSync('.hallmark/preflight.json').mtime;
const pkgMtime = statSync('package.json').mtime;
const tailwindMtime = statSync('tailwind.config.js').mtime;

if (pkgMtime <= preflightMtime && tailwindMtime <= preflightMtime) {
  // Safe to reuse cached font stacks
  const cached = JSON.parse(fs.readFileSync('.hallmark/preflight.json'));
} else {
  // Re-scan required
  const stacks = getFontStacks();
  fs.writeFileSync('.hallmark/preflight.json', JSON.stringify(stacks, null, 2));
}

```

The resulting JSON structure normalizes each stack into a clean array:

```json
{
  "display": ["Fraunces", "ui-serif", "Georgia", "serif"],
  "body":    ["Geist", "ui-sans-serif", "system-ui", "sans"],
  "outlier": ["Geist Mono", "ui-monospace", "monospace"]
}

```

## Tailwind CSS Integration

If your project uses Tailwind CSS, Hallmark merges the `fontFamily` theme definitions from your [`tailwind.config.js`](https://github.com/Nutlope/hallmark/blob/main/tailwind.config.js) with the CSS-derived stacks. This ensures that utility-generated fonts (e.g., `font-sans`, `font-serif`) are accounted for alongside the custom property definitions, providing complete coverage of all potential font families used in the project.

## Validation Against the 2+1 Rule

Once detected, the font stacks feed into Hallmark's **pre-flight audit** to enforce the "2+1 rule"—a maximum of three distinct font families per page. The audit flags any additional, unapproved font families that appear outside the display, body, and outlier definitions captured during the scan.

## Summary

- Hallmark detects font stacks by parsing three specific CSS custom properties (`--font-display`, `--font-body`, `--font-outlier`) from the `:root` selector.
- The scanning process creates a temporary DOM to read computed styles and normalizes values into ordered arrays.
- Results are cached in [`.hallmark/preflight.json`](https://github.com/Nutlope/hallmark/blob/main/.hallmark/preflight.json) and only re-scanned when [`package.json`](https://github.com/Nutlope/hallmark/blob/main/package.json) or [`tailwind.config.js`](https://github.com/Nutlope/hallmark/blob/main/tailwind.config.js) have newer modification timestamps.
- Tailwind CSS `fontFamily` configurations are merged with CSS-derived stacks for complete coverage.
- Detected stacks are validated against the "2+1 rule" to ensure typographic consistency.

## Frequently Asked Questions

### What CSS files does Hallmark scan to detect font stacks?

Hallmark targets compiled stylesheets typically located at [`site/css/tokens.css`](https://github.com/Nutlope/hallmark/blob/main/site/css/tokens.css) and [`site/css/base.css`](https://github.com/Nutlope/hallmark/blob/main/site/css/base.css). These files must contain the `:root` definitions for `--font-display`, `--font-body`, and `--font-outlier` for the detection to succeed.

### How does Hallmark cache detected font stacks between runs?

The tool writes detected stacks to [`.hallmark/preflight.json`](https://github.com/Nutlope/hallmark/blob/main/.hallmark/preflight.json) and implements an invalidation check using file modification times. If neither [`package.json`](https://github.com/Nutlope/hallmark/blob/main/package.json) nor [`tailwind.config.js`](https://github.com/Nutlope/hallmark/blob/main/tailwind.config.js) has been modified since the cache was created, Hallmark reuses the cached data instead of re-parsing the CSS.

### Can Hallmark detect font stacks defined only in Tailwind config?

Yes. While Hallmark primarily reads CSS custom properties, it also merges any `fontFamily` definitions found in your Tailwind configuration. This dual-source approach ensures that both utility-class fonts and CSS variable-based fonts are detected and validated.

### What is the "2+1 rule" mentioned in the font stack validation?

The "2+1 rule" is Hallmark's typographic constraint allowing a maximum of three distinct font families per page: one display font, one body font, and one optional outlier (typically monospace). The pre-flight scan uses the detected font stacks to audit the page and warn if additional, unapproved font families appear.