DEV Community

Muhammad Afsar Khan
Muhammad Afsar Khan

Posted on

Zero CLS, APCA Contrast, and AI Font Pairing: Building a Professional Font Comparison Tool.

Professional Font Comparison Tool

When it comes to web performance and accessibility, typography is often an afterthought. We pick nice fonts, maybe check contrast with a browser extension, and call it a day. But modern web development demands more.

I recently built a professional font comparison tool that tackles three of the toughest challenges in web typography:

1.Eliminating CLS with font metric overrides

2.Implementing APCA contrast for WCAG 3 readiness

  1. AI-powered font pairing based on morphological characteristics

Let me walk you through the technical implementation.

Part 1: Zero CLS with Font Metric Overrides
Cumulative Layout Shift (CLS) occurs when a web font loads and swaps with the system fallback, changing the size of text elements. The fix? Use size-adjust, ascent-override, and descent-override in your @font-face rules.

The Challenge: Every font requires different override values. Inter needs different adjustments than Roboto.

The Solution: Build a database of fallback metrics.

const SYSTEM_FALLBACKS = {
  'Inter': {
    fallbacks: ['system-ui', '-apple-system', 'Segoe UI', 'sans-serif'],
    sizeAdjust: '102.5%',
    ascentOverride: '92%',
    descentOverride: '22%',
    lineGapOverride: '0%'
  },
  'Roboto': {
    fallbacks: ['system-ui', 'Segoe UI', 'Arial', 'sans-serif'],
    sizeAdjust: '101.2%',
    ascentOverride: '94%',
    descentOverride: '24%',
    lineGapOverride: '0%'
  }
  // ... 20+ more fonts with AI-matched metrics
};
Enter fullscreen mode Exit fullscreen mode

CLS Ghosting Visualization: To help users understand the impact, I added a "ghost layer" that shows where the fallback font would render:

function showGhostLayers() {
  // Clone the preview text styles
  const styles = window.getComputedStyle(previewText);
  ghostDiv.style.fontSize = styles.fontSize;
  ghostDiv.style.fontWeight = styles.fontWeight;

  // Apply fallback font
  ghostDiv.style.fontFamily = fallbackFont;

  // Calculate shift percentage
  const shift = Math.abs(parseFloat(sizeAdjust) - 100);
  showToast(`CLS Impact: ${shift.toFixed(1)}%`);
}
Enter fullscreen mode Exit fullscreen mode

When activated, users see exactly how much the text would shift—visual feedback that drives home the importance of proper fallbacks.

Part 2: Implementing APCA Contrast
WCAG 2.1 contrast ratios are simple but flawed. They don't account for font weight, spatial frequency, or perceptual brightness. APCA (Accessible Perceptual Contrast Algorithm) fixes this.

The APCA Calculation:

function getAPCAcontrast(textColor, bgColor) {
  // Convert to linear RGB
  const yText = sRGBtoY(rgbText);
  const yBg = sRGBtoY(rgbBg);

  // Determine which is lighter
  const textIsLighter = yText > yBg;
  const yHigh = Math.max(yText, yBg);
  const yLow = Math.min(yText, yBg);

  // APCA formula (simplified)
  let contrast;
  if (textIsLighter) {
    contrast = (yHigh ** 0.56 - yLow ** 0.57) * 1.14 * 100;
  } else {
    contrast = (yHigh ** 0.65 - yLow ** 0.62) * 1.24 * 100;
  }

  return Math.round(contrast * 10) / 10;
}
Enter fullscreen mode Exit fullscreen mode

Performance Optimization: APCA calculations can be expensive, especially when updating in real-time. I used a Web Worker to offload the work:

// Worker thread
self.onmessage = function(e) {
  const { textColor, bgColor, side } = e.data;
  const lc = calculateAPCA(textColor, bgColor);
  self.postMessage({ lc, side });
};

// Main thread
apcaWorker.onmessage = function(e) {
  updateWcagIndicatorsWithLC(e.data.side, e.data.lc);
};
Enter fullscreen mode Exit fullscreen mode

Part 3: AI Morphology Pairing
Font pairing is part art, part science. I wanted to build an algorithm that could suggest pairings based on morphological characteristics:

function smartMorphologyPair(sourcePanel) {
  const sourceFont = getSelectedFont(sourcePanel);
  const sourceChars = getFontCharacteristics(sourceFont);

  let bestMatch = null;
  let bestScore = -1;

  // Score potential matches
  options.forEach(opt => {
    const targetChars = getFontCharacteristics(opt.value);
    let score = 0;

    // Contrasting families often work well
    if (targetChars.familyType !== sourceChars.familyType) score += 35;

    // Matching x-heights creates harmony
    if (targetChars.xHeight === sourceChars.xHeight) score += 25;

    // Similar style adds safety
    if (targetChars.style === sourceChars.style) score += 15;

    if (score > bestScore) {
      bestScore = score;
      bestMatch = opt.value;
    }
  });

  return { font: bestMatch, score: bestScore };
}
Enter fullscreen mode Exit fullscreen mode

This creates pairings like:

Playfair Display (serif) → Source Sans Pro (sans-serif) — 35 points for contrast

Montserrat (geometric) → Open Sans (humanist) — complementary styles

Part 4: Lazy Loading 1000+ Fonts
Displaying every Google Font would crash the browser. The solution? Lazy loading with an Intersection Observer:

function setupLazyLoadingForSelect(selectId) {
  const select = document.getElementById(selectId);

  select.addEventListener('scroll', function() {
    // Check if near bottom
    const scrollPosition = this.scrollTop + this.clientHeight;
    const scrollThreshold = this.scrollHeight - 30;

    if (scrollPosition >= scrollThreshold) {
      loadMoreFonts(this);
    }
  });
}

function loadMoreFonts(select) {
  // Show loading indicator
  // Fetch next batch of 50 fonts
  // Append to select options
  // Update lazy loading index
}
Enter fullscreen mode Exit fullscreen mode

Part 5: The Production Bundle Generator
The final piece: generating production-ready CSS that combines everything:

function generateProductionBundle() {
  const leftFont = getLeftFont();
  const rightFont = getRightFont();
  const leftFallback = FALLBACKS[leftFont];
  const rightFallback = FALLBACKS[rightFont];

  return `
/* Google Fonts Import */
@import url('https://fonts.googleapis.com/css2?family=${leftFont}&display=swap');

/* Zero-CLS Fallbacks */
@font-face {
  font-family: '${leftFont} Fallback';
  src: local('${leftFallback.fallbacks[0]}');
  size-adjust: ${leftFallback.sizeAdjust};
  ascent-override: ${leftFallback.ascentOverride};
  descent-override: ${leftFallback.descentOverride};
}

/* Font Stacks */
:root {
  --font-heading: '${leftFont}', '${leftFont} Fallback', ${leftFallback.fallbacks.join(', ')};
  --font-body: '${rightFont}', ${rightFallback.fallbacks.join(', ')};
}
  `;
}
Enter fullscreen mode Exit fullscreen mode

The Result
The tool now helps developers
:

Eliminate CLS with one-click fallback generation

Meet APCA contrast standards (future-proofing for WCAG 3)

Find perfect font pairings using AI

Test fonts in a live sandbox with real HTML/CSS

If you're building a typography tool or just want to understand these concepts better, the full source approach is available in the tool. Check it out and let me know what you'd add!

Explore the Pro Font Lab → FontPreview

Top comments (0)