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
- 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
};
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)}%`);
}
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;
}
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);
};
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 };
}
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
}
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(', ')};
}
`;
}
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)