Building Beautiful JS UIs with Inter & Outfit
Learn how to use Inter and Outfit fonts to build polished JavaScript user interfaces. Master font loading, CSS integration, and typography best practices for professional web apps.
Typography sets the tone for your entire application. The right font pairing makes your JavaScript UI look professional and polished, while poor typography makes even well-built features feel amateurish. Inter and Outfit are two of the best free fonts for web applications: Inter excels at body text and UI components, while Outfit brings personality to headings and display text. This guide shows you how to load, configure, and pair these fonts in your JavaScript projects.
Why Inter and Outfit?
Inter: The Modern UI Font
Inter was designed specifically for computer screens by Rasmus Andersson. It features tall x-height, open apertures, and carefully tuned spacing that stays readable at small sizes.
| Feature | Detail |
|---|---|
| Designer | Rasmus Andersson |
| Category | Sans-serif |
| Weights | 100 (Thin) to 900 (Black) |
| Variable font | Yes (weight and optical size axes) |
| Best for | Body text, UI labels, buttons, forms, navigation |
| License | SIL Open Font License (free for all uses) |
Outfit: The Geometric Display Font
Outfit is a geometric sans-serif that works beautifully for headings and display text. Its clean, rounded shapes pair perfectly with Inter's neutral body text.
| Feature | Detail |
|---|---|
| Designer | Rodrigo Fuenzalida |
| Category | Geometric sans-serif |
| Weights | 100 (Thin) to 900 (Black) |
| Variable font | Yes (weight axis) |
| Best for | Headings, hero text, feature titles, branding |
| License | SIL Open Font License (free for all uses) |
Loading Fonts from Google Fonts
The simplest way to add Inter and Outfit is through Google Fonts. Add this to your HTML <head>:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Preconnect for faster font loading -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Load Inter and Outfit variable fonts -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<title>My App</title>
</head>
<body>
<!-- Your content -->
</body>
</html>Performance Optimization with Font Display
The display=swap parameter in the Google Fonts URL tells the browser to show text immediately with a fallback font, then swap to Inter/Outfit once they load. This prevents invisible text (FOIT).
/* The Google Fonts link already includes display:swap,
but if you self-host, set it explicitly: */
@font-face {
font-family: "Inter";
src: url("/fonts/Inter-Variable.woff2") format("woff2");
font-weight: 100 900;
font-display: swap; /* Prevents invisible text during loading */
}
@font-face {
font-family: "Outfit";
src: url("/fonts/Outfit-Variable.woff2") format("woff2");
font-weight: 100 900;
font-display: swap;
}Self-Hosting Fonts for Better Performance
Self-hosting eliminates the external network request to Google Fonts, improving load times and privacy:
// Download fonts and place in your project
// public/fonts/Inter-Variable.woff2
// public/fonts/Outfit-Variable.woff2/* fonts.css */
@font-face {
font-family: "Inter";
src: url("/fonts/Inter-Variable.woff2") format("woff2");
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Outfit";
src: url("/fonts/Outfit-Variable.woff2") format("woff2");
font-weight: 100 900;
font-style: normal;
font-display: swap;
}Preloading Critical Fonts
Add preload hints so the browser starts downloading fonts immediately:
<head>
<!-- Preload the most critical font (body text) -->
<link rel="preload" href="/fonts/Inter-Variable.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/fonts/Outfit-Variable.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="/css/fonts.css">
</head>Setting Up the CSS Typography System
Create a complete typography system that pairs Inter and Outfit:
/* Base typography */
:root {
--font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--font-heading: "Outfit", "Inter", sans-serif;
/* Font sizes using a modular scale (1.25 ratio) */
--text-xs: 0.75rem; /* 12px */
--text-sm: 0.875rem; /* 14px */
--text-base: 1rem; /* 16px */
--text-lg: 1.125rem; /* 18px */
--text-xl: 1.25rem; /* 20px */
--text-2xl: 1.563rem; /* 25px */
--text-3xl: 1.953rem; /* 31px */
--text-4xl: 2.441rem; /* 39px */
--text-5xl: 3.052rem; /* 49px */
/* Line heights */
--leading-tight: 1.2;
--leading-normal: 1.5;
--leading-relaxed: 1.75;
/* Font weights */
--weight-normal: 400;
--weight-medium: 500;
--weight-semibold: 600;
--weight-bold: 700;
--weight-extrabold: 800;
}
/* Apply to body */
body {
font-family: var(--font-body);
font-size: var(--text-base);
line-height: var(--leading-normal);
font-weight: var(--weight-normal);
color: #1a1a2e;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Headings use Outfit */
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-heading);
line-height: var(--leading-tight);
font-weight: var(--weight-bold);
color: #0f0f23;
}
h1 { font-size: var(--text-5xl); font-weight: var(--weight-extrabold); }
h2 { font-size: var(--text-4xl); }
h3 { font-size: var(--text-3xl); }
h4 { font-size: var(--text-2xl); }
h5 { font-size: var(--text-xl); }
h6 { font-size: var(--text-lg); font-weight: var(--weight-semibold); }Building UI Components with the Font Pairing
Navigation Bar
<nav class="navbar">
<a href="/" class="navbar-brand">RuneApp</a>
<div class="navbar-links">
<a href="/dashboard">Dashboard</a>
<a href="/projects">Projects</a>
<a href="/settings">Settings</a>
</div>
</nav>.navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
height: 64px;
background: white;
border-bottom: 1px solid #e5e7eb;
}
.navbar-brand {
font-family: var(--font-heading);
font-size: var(--text-xl);
font-weight: var(--weight-bold);
color: #3b82f6;
text-decoration: none;
}
.navbar-links a {
font-family: var(--font-body);
font-size: var(--text-sm);
font-weight: var(--weight-medium);
color: #6b7280;
text-decoration: none;
margin-left: 24px;
transition: color 0.2s;
}
.navbar-links a:hover {
color: #1f2937;
}Card Component
.card {
background: white;
border-radius: 12px;
padding: 24px;
border: 1px solid #e5e7eb;
transition: box-shadow 0.2s ease;
}
.card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.card-title {
font-family: var(--font-heading);
font-size: var(--text-xl);
font-weight: var(--weight-semibold);
color: #111827;
margin-bottom: 8px;
}
.card-description {
font-family: var(--font-body);
font-size: var(--text-sm);
line-height: var(--leading-relaxed);
color: #6b7280;
}
.card-badge {
font-family: var(--font-body);
font-size: var(--text-xs);
font-weight: var(--weight-semibold);
color: #3b82f6;
background: #eff6ff;
padding: 2px 8px;
border-radius: 4px;
}Buttons with Proper Typography
.btn {
font-family: var(--font-body);
font-weight: var(--weight-medium);
font-size: var(--text-sm);
line-height: 1;
padding: 10px 20px;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
letter-spacing: -0.01em;
}
.btn-primary {
background: #3b82f6;
color: white;
}
.btn-primary:hover {
background: #2563eb;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}
.btn-secondary {
background: #f3f4f6;
color: #374151;
}
.btn-lg {
font-size: var(--text-base);
padding: 14px 28px;
}Dynamic Font Loading with JavaScript
Control font loading programmatically using the Font Loading API:
// Check if specific fonts are loaded
document.fonts.ready.then(() => {
console.log("All fonts loaded!");
document.body.classList.add("fonts-loaded");
});
// Load specific fonts on demand
async function loadFonts() {
const inter = new FontFace(
"Inter",
"url(/fonts/Inter-Variable.woff2)",
{ weight: "100 900", style: "normal" }
);
const outfit = new FontFace(
"Outfit",
"url(/fonts/Outfit-Variable.woff2)",
{ weight: "100 900", style: "normal" }
);
try {
const [loadedInter, loadedOutfit] = await Promise.all([
inter.load(),
outfit.load()
]);
document.fonts.add(loadedInter);
document.fonts.add(loadedOutfit);
document.body.classList.add("fonts-loaded");
console.log("Fonts loaded successfully");
} catch (error) {
console.error("Font loading failed:", error);
// Fallback fonts are already applied via font stack
}
}
loadFonts();/* Progressive enhancement: optimize display after fonts load */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
body.fonts-loaded {
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
body.fonts-loaded h1,
body.fonts-loaded h2,
body.fonts-loaded h3 {
font-family: "Outfit", "Inter", sans-serif;
}Using Inter and Outfit in JavaScript Frameworks
React / Next.js
// In Next.js with next/font
import { Inter, Outfit } from "next/font/google";
const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
display: "swap"
});
const outfit = Outfit({
subsets: ["latin"],
variable: "--font-outfit",
display: "swap"
});
export default function Layout({ children }) {
return (
<html className={`${inter.variable} ${outfit.variable}`}>
<body>{children}</body>
</html>
);
}/* globals.css */
body {
font-family: var(--font-inter), sans-serif;
}
h1, h2, h3 {
font-family: var(--font-outfit), sans-serif;
}Vanilla JavaScript SPA
// app.js - Font setup for a vanilla JS app
function initializeTypography() {
// Create and inject font styles
const style = document.createElement("style");
style.textContent = `
:root {
--font-body: "Inter", system-ui, sans-serif;
--font-heading: "Outfit", "Inter", sans-serif;
}
body { font-family: var(--font-body); }
h1, h2, h3, h4, h5, h6 { font-family: var(--font-heading); }
`;
document.head.appendChild(style);
// Load fonts
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@500;600;700;800&display=swap";
document.head.appendChild(link);
}
document.addEventListener("DOMContentLoaded", initializeTypography);Typography Best Practices
Optimal Font Sizes by Context
| Context | Font | Size | Weight | Line Height |
|---|---|---|---|---|
| Page title (h1) | Outfit | 2.5-3rem | 800 | 1.1-1.2 |
| Section heading (h2) | Outfit | 1.75-2rem | 700 | 1.2 |
| Sub-heading (h3) | Outfit | 1.25-1.5rem | 600 | 1.3 |
| Body text | Inter | 1rem (16px) | 400 | 1.5-1.6 |
| Small text / captions | Inter | 0.875rem | 400 | 1.4 |
| Buttons | Inter | 0.875rem | 500 | 1 |
| Navigation links | Inter | 0.875rem | 500 | 1 |
| Code / monospace | JetBrains Mono | 0.875rem | 400 | 1.6 |
Responsive Typography with Clamp
/* Fluid font sizes that scale with viewport */
h1 {
font-family: var(--font-heading);
font-size: clamp(2rem, 5vw, 3.5rem);
font-weight: 800;
}
h2 {
font-family: var(--font-heading);
font-size: clamp(1.5rem, 3vw, 2.25rem);
font-weight: 700;
}
body {
font-family: var(--font-body);
font-size: clamp(0.938rem, 1vw + 0.5rem, 1.125rem);
}Common Mistakes to Avoid
Mistake 1: Loading Too Many Weights
<!-- WRONG: Loading every weight increases page size significantly -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap" rel="stylesheet">
<!-- CORRECT: Load only the weights you actually use -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">Mistake 2: Not Setting Fallback Fonts
/* WRONG: No fallbacks, text may be invisible or look wrong during loading */
body { font-family: "Inter"; }
/* CORRECT: System font fallbacks maintain readability */
body { font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }Mistake 3: Using Headings Font for Body Text
/* WRONG: Outfit is geometric, harder to read in long paragraphs */
body { font-family: "Outfit", sans-serif; }
/* CORRECT: Inter is designed for extended reading */
body { font-family: "Inter", sans-serif; }
h1, h2, h3 { font-family: "Outfit", sans-serif; }Real-World Example: Dashboard Layout
function createDashboardCard(title, value, change, trend) {
const card = document.createElement("div");
card.className = "dashboard-card";
const header = document.createElement("h3");
header.className = "dashboard-card-title";
header.textContent = title;
const metric = document.createElement("div");
metric.className = "dashboard-card-value";
metric.textContent = value;
const changeEl = document.createElement("span");
changeEl.className = `dashboard-card-change ${trend}`;
changeEl.textContent = `${trend === "up" ? "+" : ""}${change}%`;
card.appendChild(header);
card.appendChild(metric);
card.appendChild(changeEl);
return card;
}
// Build dashboard
const grid = document.getElementById("dashboard-grid");
const metrics = [
{ title: "Total Users", value: "12,847", change: 12.5, trend: "up" },
{ title: "Revenue", value: "$48,290", change: 8.3, trend: "up" },
{ title: "Bounce Rate", value: "32.1%", change: -4.2, trend: "down" },
{ title: "Avg. Session", value: "4m 32s", change: 2.1, trend: "up" }
];
metrics.forEach(m => {
grid.appendChild(createDashboardCard(m.title, m.value, m.change, m.trend));
});.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 20px;
}
.dashboard-card {
background: white;
border-radius: 12px;
padding: 24px;
border: 1px solid #e5e7eb;
}
.dashboard-card-title {
font-family: var(--font-body);
font-size: var(--text-sm);
font-weight: var(--weight-medium);
color: #6b7280;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 8px;
}
.dashboard-card-value {
font-family: var(--font-heading);
font-size: var(--text-3xl);
font-weight: var(--weight-bold);
color: #111827;
margin-bottom: 4px;
}
.dashboard-card-change {
font-family: var(--font-body);
font-size: var(--text-sm);
font-weight: var(--weight-semibold);
}
.dashboard-card-change.up { color: #22c55e; }
.dashboard-card-change.down { color: #ef4444; }Rune AI
Key Insights
- Font roles: Use Inter for body text and UI elements; use Outfit for headings and display text
- Performance: Self-host variable font woff2 files, preload critical fonts, and always use
font-display: swap - CSS variables: Build a typography system with custom properties so font sizes, weights, and families stay consistent
- System fallbacks: Always include system font fallbacks in your font stack to prevent invisible text during loading
- Keep it minimal: Load only the font weights you actually use; four to five weights per font is sufficient for most applications
Frequently Asked Questions
Are Inter and Outfit free to use in commercial projects?
How much do Inter and Outfit add to page load time?
Can I mix Inter and Outfit with other fonts?
Should I use the variable font or individual weight files?
How do I handle font flashing (FOUT/FOIT)?
Conclusion
The Inter and Outfit font pairing gives your JavaScript applications a professional, modern appearance with minimal effort. Inter handles body text, UI labels, and buttons with exceptional screen readability. Outfit brings visual weight and personality to headings and display text. Self-host both fonts as variable woff2 files for the best performance, set up a CSS custom properties system for consistent typography, and use the Font Loading API when you need programmatic control. The fonts are free, well-maintained, and optimized for the web.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.