UI Components Easy
Font Size Control
Accessible font size adjuster with decrease/reset/increase buttons, live preview, and localStorage persistence. Vanilla JS.
Open in Lab
MCP
vanilla-js css
Targets: JS HTML
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--base-size: 16px;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-size: var(--base-size);
background: #0d1117;
color: #e6edf3;
min-height: 100vh;
padding: 32px 16px;
display: flex;
justify-content: center;
transition: font-size 0.2s;
}
.demo {
width: 100%;
max-width: 680px;
display: flex;
flex-direction: column;
gap: 20px;
}
/* Control bar */
.fsc-bar {
display: flex;
align-items: center;
gap: 12px;
background: #161b22;
border: 1px solid #21262d;
border-radius: 12px;
padding: 12px 18px;
flex-wrap: wrap;
}
.fsc-label {
font-size: 13px;
font-weight: 700;
color: #8b949e;
}
.fsc-controls {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
}
.fsc-btn {
background: #21262d;
border: 1px solid #30363d;
color: #8b949e;
font-size: 13px;
font-weight: 700;
padding: 6px 12px;
border-radius: 7px;
cursor: pointer;
transition: all 0.15s;
letter-spacing: -0.02em;
}
.fsc-btn:hover {
color: #e6edf3;
border-color: #484f58;
background: #30363d;
}
.fsc-btn:disabled {
opacity: 0.3;
cursor: not-allowed;
}
.fsc-btn:focus-visible {
outline: 2px solid #6366f1;
outline-offset: 2px;
}
.fsc-size-steps {
display: flex;
background: #0d1117;
border-radius: 8px;
padding: 2px;
gap: 2px;
}
.fsc-step {
background: none;
border: none;
color: #6c7086;
font-size: 12px;
font-weight: 700;
padding: 4px 12px;
border-radius: 6px;
cursor: pointer;
transition: all 0.15s;
}
.fsc-step.active {
background: #6366f1;
color: #fff;
}
.fsc-step:not(.active):hover {
color: #e6edf3;
background: rgba(255, 255, 255, 0.05);
}
.fsc-step:focus-visible {
outline: 2px solid #6366f1;
outline-offset: 2px;
}
.fsc-reset {
background: none;
border: none;
color: #4a555f;
font-size: 16px;
cursor: pointer;
padding: 4px;
border-radius: 6px;
transition: color 0.15s;
line-height: 1;
}
.fsc-reset:hover {
color: #e6edf3;
}
.fsc-current {
font-size: 12px;
color: #6c7086;
font-family: Menlo, monospace;
min-width: 36px;
text-align: right;
}
/* Article */
.preview-article {
background: #161b22;
border: 1px solid #21262d;
border-radius: 14px;
padding: 28px 28px 24px;
display: flex;
flex-direction: column;
gap: 1em;
transition: all 0.2s;
}
.pa-heading {
font-size: 1.6em;
font-weight: 800;
color: #e6edf3;
line-height: 1.3;
}
.pa-lead {
font-size: 1.1em;
color: #8b949e;
line-height: 1.65;
font-weight: 500;
}
.pa-body {
font-size: 1em;
color: #cdd6f4;
line-height: 1.75;
}
.pa-body code {
background: #21262d;
border-radius: 4px;
padding: 1px 6px;
font-size: 0.875em;
font-family: Menlo, monospace;
color: #79c0ff;
}
.pa-quote {
border-left: 3px solid #6366f1;
padding: 8px 16px;
margin: 0;
font-style: italic;
color: #8b949e;
font-size: 1em;
background: rgba(99, 102, 241, 0.05);
border-radius: 0 8px 8px 0;
}
.pa-meta {
font-size: 0.8em;
color: #4a555f;
}const SIZES = [
{ key: "small", px: 13, pct: "81%" },
{ key: "default", px: 16, pct: "100%" },
{ key: "large", px: 20, pct: "125%" },
{ key: "xlarge", px: 24, pct: "150%" },
];
let currentIdx = 1; // default
function apply(idx) {
currentIdx = Math.max(0, Math.min(SIZES.length - 1, idx));
const size = SIZES[currentIdx];
document.documentElement.style.setProperty("--base-size", size.px + "px");
document.getElementById("fscCurrent").textContent = size.pct;
document.querySelectorAll(".fsc-step").forEach((btn, i) => {
btn.classList.toggle("active", i === currentIdx);
});
document.getElementById("fscDecrease").disabled = currentIdx === 0;
document.getElementById("fscIncrease").disabled = currentIdx === SIZES.length - 1;
// Persist
localStorage.setItem("fsc-size", size.key);
// Announce to screen readers
const announcer = document.getElementById("fscCurrent");
announcer.textContent = `${size.pct} font size`;
}
document.getElementById("fscDecrease").addEventListener("click", () => apply(currentIdx - 1));
document.getElementById("fscIncrease").addEventListener("click", () => apply(currentIdx + 1));
document.getElementById("fscReset").addEventListener("click", () => apply(1));
document.querySelectorAll(".fsc-step").forEach((btn, i) => {
btn.addEventListener("click", () => apply(i));
});
// Restore from storage
const saved = localStorage.getItem("fsc-size");
if (saved) {
const idx = SIZES.findIndex((s) => s.key === saved);
if (idx !== -1) apply(idx);
} else {
apply(1);
}<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Font Size Control</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="demo">
<div class="fsc-bar">
<span class="fsc-label">Text Size</span>
<div class="fsc-controls">
<button class="fsc-btn" id="fscDecrease" aria-label="Decrease font size">A−</button>
<div class="fsc-size-steps" id="fscSteps" role="group" aria-label="Font size">
<button class="fsc-step" data-size="small">S</button>
<button class="fsc-step active" data-size="default">M</button>
<button class="fsc-step" data-size="large">L</button>
<button class="fsc-step" data-size="xlarge">XL</button>
</div>
<button class="fsc-btn" id="fscIncrease" aria-label="Increase font size">A+</button>
<button class="fsc-reset" id="fscReset" title="Reset to default">↺</button>
</div>
<span class="fsc-current" id="fscCurrent" aria-live="polite">100%</span>
</div>
<article class="preview-article">
<h1 class="pa-heading">The Importance of Accessible Typography</h1>
<p class="pa-lead">Typography directly impacts readability and usability for people with visual impairments, dyslexia, and low vision.</p>
<p class="pa-body">Allowing users to adjust text size is a core accessibility requirement. A minimum base font size of 16px is recommended, and all elements should scale proportionally using <code>em</code> or <code>rem</code> units.</p>
<blockquote class="pa-quote">
"Good typography is invisible. Bad typography is everywhere." — Beatrice Ward
</blockquote>
<p class="pa-body">When implementing font size controls, ensure the layout remains usable at all sizes and that text never overflows its container.</p>
<div class="pa-meta">Last updated March 7, 2026 · 3 min read</div>
</article>
</div>
<script src="script.js"></script>
</body>
</html>Font size control widget with A−/A/A+ buttons, a step slider, and a live text preview area. Sets font-size on :root via CSS custom property. Persists the user’s choice to localStorage.