How to Change Font With JavaScript: A Complete Guide

Master dynamic typography in modern web development with practical techniques for user preferences, accessibility, and theme systems.

Why Change Fonts Dynamically With JavaScript

Dynamic font manipulation has become an essential technique in modern web development. Whether you're building accessibility features, implementing theme systems, or creating personalized user experiences, understanding how to change fonts with JavaScript gives you precise control over typography.

Common Use Cases for Dynamic Font Changes

  • User Preference Features: Dark mode typography, reading mode, and customizable text sizes
  • Accessibility Accommodations: Dyslexia-friendly fonts, high-contrast modes, and size preferences
  • A/B Testing: Testing different typography to measure engagement and readability
  • Theme Systems: Coordinating font changes with overall design themes
  • Content Localization: Different fonts for different languages and regions

Modern web applications require typography that adapts to user needs while maintaining performance and visual consistency. When implementing font systems at scale, consider partnering with web development experts who understand the nuances of dynamic typography implementation.

Methods for Changing Fonts With JavaScript

Choose the right approach for your use case

Direct Style Manipulation

Set element.style.fontFamily directly for quick, one-off changes. Best for simple use cases where you need immediate control.

CSS Class Switching

Toggle predefined CSS classes for maintainable, scalable font management. Recommended for most production applications.

CSS Custom Properties

Use CSS variables for dynamic, theme-aware typography. The modern standard for design systems and theming.

Font Loading API

Programmatically load and manage fonts with JavaScript. Essential for custom font loading strategies.

Direct Font Manipulation With JavaScript

The most straightforward approach to changing fonts is setting CSS properties directly on DOM elements using JavaScript. This method gives you immediate control but requires careful consideration for maintainability.

Targeting Specific Elements

JavaScript provides several methods to select elements for font manipulation:

// Target specific element by ID
const header = document.getElementById('main-header');
header.style.fontFamily = 'Inter, sans-serif';

// Target multiple elements with querySelectorAll
document.querySelectorAll('.typography-content').forEach(el => {
 el.style.fontFamily = 'Georgia, serif';
});

// Scope selections within a container
const article = document.querySelector('article');
article.querySelectorAll('p').forEach(p => {
 p.style.fontFamily = 'Merriweather, serif';
});

Setting Font Properties

Beyond fontFamily, JavaScript can manipulate all typography-related properties:

// Change font family
element.style.fontFamily = '"Open Sans", sans-serif';

// Adjust font size dynamically
element.style.fontSize = '1.25rem';

// Modify font weight
element.style.fontWeight = '600';

// Toggle italic style
element.style.fontStyle = 'italic';

// Control line height
element.style.lineHeight = '1.6';

For production applications, consider how this approach scales. Our web development services team can help you implement maintainable typography systems that grow with your application.

The CSS Class Approach: Better Maintainability

For production applications, using CSS classes to manage fonts is strongly recommended. This approach separates presentation from behavior, making your code more maintainable and easier to extend.

Creating Font Classes

Define your font classes in CSS with a clear naming convention:

/* Base typography */
body {
 font-family: system-ui, sans-serif;
}

/* Font theme classes */
.font-serif {
 font-family: Georgia, 'Times New Roman', serif;
}

.font-sans {
 font-family: 'Inter', system-ui, sans-serif;
}

.font-monospace {
 font-family: 'JetBrains Mono', monospace;
}

/* Accessibility-focused fonts */
.font-dyslexia {
 font-family: 'OpenDyslexic', sans-serif;
 letter-spacing: 0.05em;
 word-spacing: 0.1em;
}

Switching Classes With JavaScript

Manage class transitions cleanly:

function setFontTheme(fontClass) {
 // Remove existing font classes
 document.body.classList.remove('font-serif', 'font-sans', 'font-monospace');
 // Add the new font class
 document.body.classList.add(fontClass);
}

// Toggle between font options
function toggleFont() {
 document.body.classList.toggle('font-serif');
}

Benefits of Class-Based Approach

  • Separation of concerns: CSS handles presentation, JavaScript handles behavior
  • Maintainability: Easy to modify fonts in CSS without touching JavaScript
  • Performance: Browsers optimize class changes efficiently
  • Preprocessor support: Works with Sass, Less, and PostCSS
  • Transitions: Easy to animate between font states

This approach integrates seamlessly with modern design systems and theme implementations.

CSS Custom Properties for Dynamic Typography

CSS custom properties (variables) represent the modern standard for dynamic typography. They enable powerful theming systems while maintaining excellent performance.

Defining Font Custom Properties

Set up a comprehensive typography system using CSS variables:

:root {
 /* Primary font families */
 --font-primary: 'Inter', system-ui, sans-serif;
 --font-secondary: 'Merriweather', serif;
 --font-code: 'JetBrains Mono', monospace;

 /* Font sizes with fluid scaling */
 --text-xs: clamp(0.75rem, 0.7vw, 0.875rem);
 --text-sm: clamp(0.875rem, 0.8vw, 1rem);
 --text-base: clamp(1rem, 0.9vw, 1.125rem);
 --text-lg: clamp(1.125rem, 1.1vw, 1.25rem);
 --text-xl: clamp(1.25rem, 1.3vw, 1.5rem);

 /* Font weights */
 --font-light: 300;
 --font-normal: 400;
 --font-medium: 500;
 --font-bold: 700;
}

Updating Properties With JavaScript

Modify CSS variables dynamically:

// Change primary font via custom property
function setPrimaryFont(fontName) {
 document.documentElement.style.setProperty('--font-primary', fontName);
}

// Create a font size preference system
function setTextSize(preference) {
 const sizes = {
 small: '0.875rem',
 normal: '1rem',
 large: '1.125rem',
 xlarge: '1.25rem'
 };
 document.documentElement.style.setProperty('--text-base', sizes[preference]);
}

Complete Theme Switching System

const fontThemes = {
 default: {
 '--font-primary': "'Inter', system-ui, sans-serif",
 '--font-secondary': "'Merriweather', serif"
 },
 classic: {
 '--font-primary': "'Georgia', serif",
 '--font-secondary': "'Times New Roman', serif"
 },
 modern: {
 '--font-primary': "'Space Grotesk', sans-serif",
 '--font-secondary': "'Inter', sans-serif"
 }
};

function applyFontTheme(themeName) {
 const theme = fontThemes[themeName];
 if (!theme) return;

 Object.entries(theme).forEach(([property, value]) => {
 document.documentElement.style.setProperty(property, value);
 });
}

The Font Loading API

The CSS Font Loading API provides JavaScript-level control over font loading, enabling you to preload fonts, detect when fonts are ready, and handle font loading states programmatically.

Loading Fonts On Demand

Check if a font is loaded before using it:

async function ensureFontLoaded(fontFamily, fallback = 'sans-serif') {
 try {
 await document.fonts.load(`16px "${fontFamily}"`);
 return true;
 } catch (error) {
 console.warn(`Font ${fontFamily} failed to load, using ${fallback}`);
 return false;
 }
}

// Use a custom font when ready
async function applyCustomFont() {
 const fontLoaded = await ensureFontLoaded('Custom Font', 'sans-serif');
 if (fontLoaded) {
 document.body.style.fontFamily = 'Custom Font, sans-serif';
 }
}

FontFace API for Dynamic Loading

Load fonts programmatically without CSS:

async function loadCustomFont(fontName, fontUrl) {
 const fontFace = new FontFace(fontName, `url(${fontUrl})`);

 try {
 const loadedFont = await fontFace.load();
 document.fonts.add(loadedFont);
 document.body.style.fontFamily = fontName;
 return true;
 } catch (error) {
 console.error('Failed to load font:', error);
 return false;
 }
}

// Usage
loadCustomFont('MyFont', '/fonts/myfont.woff2');

Handling Font Loading Events

// Wait for all fonts to load
document.fonts.ready.then(() => {
 console.log('All fonts have loaded');
 // Trigger any font-dependent rendering
});

Performance Best Practices

Font changes can impact page performance and user experience. Following these best practices ensures smooth typography transitions without layout issues.

Avoiding Layout Shifts

Cumulative Layout Shift (CLS) from font changes frustrates users and impacts your SEO performance. Prevent it with:

@font-face {
 font-family: 'CustomFont';
 src: url('/fonts/custom.woff2') format('woff2');
 font-display: swap;
}

/* Better fallback matching with size-adjust */
@font-face {
 font-family: 'CustomFont';
 src: url('/fonts/custom.woff2') format('woff2');
 size-adjust: 95%;
}

Efficient DOM Updates

function applyFontThemeOptimized(theme) {
 // Use requestAnimationFrame for smooth updates
 requestAnimationFrame(() => {
 Object.entries(theme).forEach(([property, value]) => {
 document.documentElement.style.setProperty(property, value);
 });
 });
}

// Use CSS classes for bulk changes instead of multiple style updates
function applyFontClass(themeName) {
 document.body.className = document.body.className
 .replace(/theme-\w+/g, '')
 .trim();
 document.body.classList.add(`theme-${themeName}`);
}

Minimizing Repaints

Different font properties trigger different browser operations:

  • fontFamily: Repaint only (fast)
  • fontSize: Reflow + repaint (slower)

For animations, use CSS transforms instead of font properties.

Next.js Specific Approaches

Next.js provides built-in font optimization through next/font, which automatically handles font loading, caching, and performance optimization.

Using next/font for Optimization

// app/layout.js
import { Inter, Merriweather } from 'next/font/google';

const inter = Inter({
 subsets: ['latin'],
 display: 'swap',
 variable: '--font-inter'
});

const merriweather = Merriweather({
 weight: ['400', '700'],
 subsets: ['latin'],
 display: 'swap',
 variable: '--font-merriweather'
});

export default function Layout({ children }) {
 return (
 <html lang="en" className={`${inter.variable} ${merriweather.variable}`}>
 <body>{children}</body>
 </html>
 );
}

Dynamic Font Switching in React

'use client';

import { useState, useEffect } from 'react';

const fonts = [
 { id: 'inter', name: 'Inter', class: 'font-inter' },
 { id: 'merriweather', name: 'Merriweather', class: 'font-serif' },
 { id: 'mono', name: 'JetBrains Mono', class: 'font-mono' }
];

export default function FontSwitcher() {
 const [selectedFont, setSelectedFont] = useState('inter');

 useEffect(() => {
 document.body.className = fonts.find(f => f.id === selectedFont)?.class || '';
 localStorage.setItem('preferred-font', selectedFont);
 }, [selectedFont]);

 useEffect(() => {
 const saved = localStorage.getItem('preferred-font');
 if (saved && fonts.find(f => f.id === saved)) {
 setSelectedFont(saved);
 }
 }, []);

 return (
 <select value={selectedFont} onChange={(e) => setSelectedFont(e.target.value)}>
 {fonts.map(font => (
 <option key={font.id} value={font.id}>{font.name}</option>
 ))}
 </select>
 );
}

Our web development services team specializes in Next.js implementations with optimized typography systems.

Accessibility Considerations

When implementing font changes, accessibility should be a primary concern. Always respect user preferences and provide clear controls.

Respecting User System Preferences

// Check for user's motion preference
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

// Check for user's contrast preferences
const prefersLargeText = window.matchMedia('(prefers-contrast: more)').matches;

// Respect user's system font settings
function applySystemFont() {
 const userAgent = navigator.userAgent;
 if (userAgent.includes('Mac')) {
 return '-apple-system, BlinkMacSystemFont, sans-serif';
 }
 if (userAgent.includes('Windows')) {
 return 'Segoe UI, Tahoma, sans-serif';
 }
 return 'system-ui, sans-serif';
}

Best Practices for Accessible Font Controls

  1. Provide Options, Not Defaults: Never auto-change fonts without user consent
  2. Remember Choices: Store preferences in localStorage for consistency
  3. Clear Controls: Use obvious labels and visual feedback
  4. Respect OS Settings: Honor system font and accessibility preferences
  5. Offer Reset: Always provide a way to return to defaults

Implementing accessible typography is a key component of creating inclusive web experiences.

Complete Implementation Examples

Reading Mode Toggle

A complete implementation for a reading mode that optimizes typography for long-form content:

class ReadingMode {
 constructor() {
 this.isActive = false;
 this.init();
 }

 init() {
 const toggle = document.getElementById('reading-mode-toggle');
 toggle?.addEventListener('click', () => this.toggle());
 }

 toggle() {
 this.isActive ? this.disable() : this.enable();
 }

 enable() {
 this.isActive = true;
 document.body.classList.add('reading-mode');
 document.body.style.setProperty('--font-primary', '"Georgia", serif');
 document.body.style.setProperty('--text-base', '1.25rem');
 document.body.style.setProperty('--line-height', '1.8');
 }

 disable() {
 this.isActive = false;
 document.body.classList.remove('reading-mode');
 document.body.style.removeProperty('--font-primary');
 document.body.style.removeProperty('--text-base');
 document.body.style.removeProperty('--line-height');
 }
}

Font Size Adjuster

An accessibility-focused font size control:

class FontSizeAdjuster {
 constructor() {
 this.sizes = ['xs', 'sm', 'base', 'lg', 'xl'];
 this.currentIndex = 2;
 this.init();
 }

 init() {
 const saved = localStorage.getItem('font-size');
 if (saved && this.sizes.includes(saved)) {
 this.currentIndex = this.sizes.indexOf(saved);
 }
 this.applySize();
 }

 increase() {
 if (this.currentIndex < this.sizes.length - 1) {
 this.currentIndex++;
 this.applySize();
 }
 }

 decrease() {
 if (this.currentIndex > 0) {
 this.currentIndex--;
 this.applySize();
 }
 }

 applySize() {
 const size = this.sizes[this.currentIndex];
 const baseSizes = {
 xs: '0.875rem', sm: '1rem', base: '1.125rem',
 lg: '1.25rem', xl: '1.5rem'
 };
 document.documentElement.style.setProperty('--text-base', baseSizes[size]);
 localStorage.setItem('font-size', size);
 }
}

Summary

Changing fonts with JavaScript is a powerful technique for creating dynamic, user-centric web experiences. Here's how to approach each method:

Key Recommendations

  1. Use CSS Custom Properties for the most maintainable and performant approach to dynamic typography. They integrate seamlessly with design systems and theme implementations.

  2. Consider the Font Loading API when you need programmatic control over font loading states, especially for custom fonts that may take time to load.

  3. Always consider performance by using font-display: swap, preloading critical fonts, and minimizing layout shifts.

  4. In Next.js applications, leverage next/font for automatic optimization, including font subsetting and caching.

  5. Prioritize accessibility by respecting user preferences, providing clear controls, and remembering choices across sessions.

Quick Reference

MethodBest ForComplexity
Direct style manipulationQuick prototypes, one-off changesLow
CSS class switchingProduction apps, maintainable codeMedium
CSS custom propertiesDesign systems, themingMedium
Font Loading APICustom font loading controlHigh

By choosing the right approach for your specific use case, you can implement robust font manipulation that enhances user experience while maintaining excellent performance.

Frequently Asked Questions

What's the best way to change fonts with JavaScript?

CSS custom properties (variables) are generally the best approach for dynamic font changes. They provide excellent performance, work seamlessly with CSS preprocessors, and integrate naturally with design systems. Use CSS classes for simpler cases where you don't need theming capabilities.

How do I prevent layout shifts when changing fonts?

Use font-display: swap in your @font-face declarations to prevent invisible text. For better fallback matching, use size-adjust to make fallback fonts more closely match your custom font's metrics. Preload critical fonts and define fallback fonts that match the x-height of your primary font.

Can I change fonts dynamically in Next.js?

Yes, Next.js provides the next/font package for optimized font loading. For dynamic font switching, use CSS custom properties combined with React state. Store user preferences in localStorage and apply them in a useEffect hook to ensure consistent font choices across sessions.

How do I make font changes accessible?

Always respect user preferences like prefers-reduced-motion and system font settings. Provide clear, accessible controls for font changes. Remember user choices and provide a way to reset to defaults. Never auto-change fonts without explicit user consent.

Ready to Build Modern Web Experiences?

Our team specializes in custom web development with Next.js, implementing best practices for typography, performance, and accessibility.