1️⃣ px – Pixels (Absolute Unit)
A fixed size unit.
1 px = 1 pixel on the screen (not responsive).
Doesn’t scale with screen or zoom preferences.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Px Unit Anatomy</title>
<style>
.infographic-pxunit {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
box-sizing: border-box;
margin: 0 auto;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: var(--paper);
color: var(--ink);
border-radius: 14px;
overflow: hidden;
}
.infographic-pxunit[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
}
.ha-inner { padding: 0.5rem; }
.ha-layout { display: flex; flex-direction: column; gap: 1.5rem; }
.ha-code-wrap {
background: #0b0b0b;
border: 1px solid #2a2a2a;
border-radius: 10px;
overflow: hidden;
}
.ha-filetabs {
display: flex;
background: #161616;
border-bottom: 1px solid #2a2a2a;
}
.ha-filetab {
border: none;
background: transparent;
color: #8a8a8a;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 0.78rem;
font-weight: 700;
padding: 0.6rem 1rem;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.ha-filetab:hover { color: #e5e5e5; }
.ha-filetab.ha-tab-on {
color: #ffffff;
border-bottom-color: #5b8def;
background: #0b0b0b;
}
.ha-code {
padding: 1rem 1.8rem 1rem 0.9rem;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 0.8rem;
line-height: 1.85;
overflow-x: auto;
}
.ha-codepane { display: none; }
.ha-codepane.ha-pane-on { display: block; }
.ha-line { display: block; white-space: pre; color: #8a8a8a; padding: 0.05rem 0.4rem; }
.ha-tag { color: #60a5fa; font-weight: 700; }
.ha-sel { color: #f472b6; font-weight: 700; }
.ha-prop { color: #c586c0; }
.ha-val { color: #ce9178; }
.ha-unit-hl { color: #ffd166; font-weight: 800; }
.ha-comment { color: #5c6370; font-style: italic; }
.ha-browser {
border: 1px solid var(--line);
border-radius: 10px;
overflow: hidden;
background: var(--paper);
}
.ha-browser-bar {
background: var(--paper-alt);
padding: 0.5rem 0.7rem;
display: flex;
align-items: center;
gap: 0.5rem;
border-bottom: 1px solid var(--line);
}
.ha-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--line); }
.ha-tab {
background: var(--paper);
border: 1px solid var(--line);
border-bottom: none;
border-radius: 6px 6px 0 0;
font-size: 0.72rem;
color: var(--ink-soft);
padding: 0.25rem 0.6rem;
}
.ha-urlrow {
background: var(--paper-alt);
padding: 0.35rem 0.7rem 0.6rem;
font-size: 0.72rem;
color: var(--ink-soft);
font-family: "SFMono-Regular", Consolas, monospace;
}
.ha-screen {
background: #ffffff;
color: #111;
padding: 1.2rem 1.4rem 1.4rem;
font-family: Arial, sans-serif;
}
.ha-out-h1 {
font-size: 24px;
color: #333;
margin: 0 0 0.5rem;
padding: 0.1rem 0.3rem;
margin-left: -0.3rem;
display: inline-block;
border-radius: 6px;
transition: background 0.2s ease, outline 0.2s ease;
}
.ha-out-p { font-size: 0.9rem; margin: 0; color: #333; }
.ha-out-lit { background: var(--accent-soft); outline: 2px solid var(--accent); }
.ha-slider-row {
display: grid;
grid-template-columns: 90px 1fr 50px;
align-items: center;
gap: 0.6rem;
}
.ha-slider-row label {
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.78rem;
font-weight: 700;
color: var(--ink-soft);
}
.ha-slider-row input[type="range"] { width: 100%; accent-color: var(--accent); }
.ha-slider-row .ha-val {
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.78rem;
color: var(--ink-soft);
text-align: right;
}
</style>
</head>
<body>
<div class="infographic-pxunit" id="ha-root">
<div class="ha-inner">
<div class="ha-layout">
<div class="ha-code-wrap">
<div class="ha-filetabs">
<button class="ha-filetab ha-tab-on" id="tab-html">index.html</button>
<button class="ha-filetab" id="tab-css">style.css</button>
</div>
<div class="ha-code">
<div class="ha-codepane ha-pane-on" id="pane-html">
<span class="ha-line"><<span class="ha-tag">body</span>></span>
<span class="ha-line ha-clickable ha-active" data-part="rule"> <<span class="ha-tag">h1</span>>This is a Heading</<span class="ha-tag">h1</span>></span>
<span class="ha-line"> <<span class="ha-tag">p</span>>This heading uses a font size of 24px as defined in the CSS.</<span class="ha-tag">p</span>></span>
<span class="ha-line"></<span class="ha-tag">body</span>></span>
</div>
<div class="ha-codepane" id="pane-css">
<span class="ha-line ha-clickable ha-active" data-part="rule"><span class="ha-sel">h1</span> {</span>
<span class="ha-line ha-clickable ha-active" data-part="rule"> <span class="ha-prop">font-size</span>: <span class="ha-unit-hl" id="code-fontsize">24px</span>; <span class="ha-comment">/* fixed, absolute size */</span></span>
<span class="ha-line ha-clickable ha-active" data-part="rule"> <span class="ha-prop">color</span>: <span class="ha-val">#333</span>;</span>
<span class="ha-line ha-clickable ha-active" data-part="rule"> <span class="ha-prop">font-family</span>: <span class="ha-val">Arial, sans-serif</span>;</span>
<span class="ha-line ha-clickable ha-active" data-part="rule">}</span>
</div>
</div>
</div>
<div class="ha-browser">
<div class="ha-browser-bar">
<span class="ha-dot"></span><span class="ha-dot"></span><span class="ha-dot"></span>
<div class="ha-tab">🌐 Px Unit Example</div>
</div>
<div class="ha-urlrow">127.0.0.1:5500/basicstructure.html</div>
<div class="ha-screen">
<h1 class="ha-out-h1 ha-out-lit" id="out-h1">This is a Heading</h1>
<p class="ha-out-p">This heading uses a font size of 24px as defined in the CSS.</p>
</div>
</div>
<div class="ha-slider-row">
<label for="s-fontsize">font-size</label>
<input type="range" id="s-fontsize" min="10" max="60" value="24">
<span class="ha-val" id="v-fontsize">24px</span>
</div>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.getElementById('ha-root');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') {
dark = true;
} else if (bodyAttr === 'false') {
dark = false;
} else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
})();
(function () {
const tabHtml = document.getElementById('tab-html');
const tabCss = document.getElementById('tab-css');
const paneHtml = document.getElementById('pane-html');
const paneCss = document.getElementById('pane-css');
tabHtml.addEventListener('click', () => {
tabHtml.classList.add('ha-tab-on');
tabCss.classList.remove('ha-tab-on');
paneHtml.classList.add('ha-pane-on');
paneCss.classList.remove('ha-pane-on');
});
tabCss.addEventListener('click', () => {
tabCss.classList.add('ha-tab-on');
tabHtml.classList.remove('ha-tab-on');
paneCss.classList.add('ha-pane-on');
paneHtml.classList.remove('ha-pane-on');
});
const slider = document.getElementById('s-fontsize');
const vOut = document.getElementById('v-fontsize');
const codeOut = document.getElementById('code-fontsize');
const heading = document.getElementById('out-h1');
slider.addEventListener('input', () => {
const val = slider.value + 'px';
vOut.textContent = val;
codeOut.textContent = val;
heading.style.fontSize = val;
});
})();
</script>
</body>
</html>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});2️⃣ % – Percentage (Relative Unit)
Relative to the parent element.
Often used in widths, heights, padding, and margins.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Percent Unit Anatomy</title>
<style>
.infographic-percentunit {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
box-sizing: border-box;
margin: 0 auto;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: var(--paper);
color: var(--ink);
border-radius: 14px;
overflow: hidden;
}
.infographic-percentunit[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
}
.ha-inner { padding: 0.5rem; }
.ha-layout { display: flex; flex-direction: column; gap: 1.2rem; }
.ha-code-wrap {
background: #0b0b0b;
border: 1px solid #2a2a2a;
border-radius: 10px;
overflow: hidden;
}
.ha-filetabs {
display: flex;
background: #161616;
border-bottom: 1px solid #2a2a2a;
}
.ha-filetab {
border: none;
background: transparent;
color: #8a8a8a;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 0.78rem;
font-weight: 700;
padding: 0.6rem 1rem;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.ha-filetab:hover { color: #e5e5e5; }
.ha-filetab.ha-tab-on {
color: #ffffff;
border-bottom-color: #5b8def;
background: #0b0b0b;
}
.ha-code {
padding: 1rem 1.8rem 1rem 0.9rem;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 0.8rem;
line-height: 1.85;
overflow-x: auto;
}
.ha-codepane { display: none; }
.ha-codepane.ha-pane-on { display: block; }
.ha-line { display: block; white-space: pre; color: #8a8a8a; padding: 0.05rem 0.4rem; }
.ha-tag { color: #60a5fa; font-weight: 700; }
.ha-attr { color: #f2b96b; }
.ha-str { color: #9ece6a; }
.ha-sel { color: #f472b6; font-weight: 700; }
.ha-prop { color: #c586c0; }
.ha-val { color: #ce9178; }
.ha-unit-hl { color: #ffd166; font-weight: 800; }
.ha-comment { color: #5c6370; font-style: italic; }
.ha-slider-row {
display: grid;
grid-template-columns: 130px 1fr 50px;
align-items: center;
gap: 0.6rem;
}
.ha-slider-row label {
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.78rem;
font-weight: 700;
color: var(--ink-soft);
}
.ha-slider-row input[type="range"] { width: 100%; accent-color: var(--accent); }
.ha-slider-row .ha-val {
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.78rem;
color: var(--ink-soft);
text-align: right;
}
.ha-stage {
background: var(--paper-alt);
border: 1px solid var(--line);
border-radius: 10px;
padding: 1rem;
}
#container {
background-color: #f0f0f0;
padding: 20px;
box-sizing: border-box;
transition: width 0.15s ease;
margin: 0 auto;
border: 1px dashed #999;
}
#box {
background-color: #add8e6;
padding: 20px;
box-sizing: border-box;
transition: width 0.15s ease;
}
#box p {
margin: 0;
font-family: Georgia, "Times New Roman", serif;
font-size: 0.85rem;
color: #111;
}
.ha-dims {
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.72rem;
color: var(--ink-soft);
margin-top: 0.6rem;
text-align: center;
}
</style>
</head>
<body>
<div class="infographic-percentunit" id="ha-root">
<div class="ha-inner">
<div class="ha-layout">
<div class="ha-code-wrap">
<div class="ha-filetabs">
<button class="ha-filetab ha-tab-on" id="tab-html">index.html</button>
<button class="ha-filetab" id="tab-css">style.css</button>
</div>
<div class="ha-code">
<div class="ha-codepane ha-pane-on" id="pane-html">
<span class="ha-line"><<span class="ha-tag">div</span> <span class="ha-attr">class</span>=<span class="ha-str">"container"</span>></span>
<span class="ha-line"> <<span class="ha-tag">div</span> <span class="ha-attr">class</span>=<span class="ha-str">"box"</span>></span>
<span class="ha-line"> <<span class="ha-tag">p</span>>This box takes up 50% of the container's width.</<span class="ha-tag">p</span>></span>
<span class="ha-line"> </<span class="ha-tag">div</span>></span>
<span class="ha-line"></<span class="ha-tag">div</span>></span>
</div>
<div class="ha-codepane" id="pane-css">
<span class="ha-line"><span class="ha-sel">.container</span> {</span>
<span class="ha-line"> <span class="ha-prop">width</span>: <span class="ha-unit-hl" id="code-container-width">100%</span>;</span>
<span class="ha-line"> <span class="ha-prop">background-color</span>: <span class="ha-val">#f0f0f0</span>;</span>
<span class="ha-line">}</span>
<span class="ha-line"><span class="ha-sel">.box</span> {</span>
<span class="ha-line"> <span class="ha-prop">width</span>: <span class="ha-unit-hl" id="code-box-width">50%</span>; <span class="ha-comment">/* 50% of the container */</span></span>
<span class="ha-line"> <span class="ha-prop">background-color</span>: <span class="ha-val">#add8e6</span>;</span>
<span class="ha-line">}</span>
</div>
</div>
</div>
<div class="ha-slider-row">
<label>container width</label>
<input type="range" id="s-container" min="150" max="560" value="480">
<span class="ha-val" id="v-container">480px</span>
</div>
<div class="ha-slider-row">
<label>.box width (%)</label>
<input type="range" id="s-box" min="10" max="100" value="50">
<span class="ha-val" id="v-box">50%</span>
</div>
<div class="ha-stage">
<div id="container">
<div id="box">
<p id="box-text">This box takes up 50% of the container's width.</p>
</div>
</div>
<div class="ha-dims" id="dims-out">container: 480px → box: 50% = 240px</div>
</div>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.getElementById('ha-root');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') {
dark = true;
} else if (bodyAttr === 'false') {
dark = false;
} else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
})();
(function () {
const tabHtml = document.getElementById('tab-html');
const tabCss = document.getElementById('tab-css');
const paneHtml = document.getElementById('pane-html');
const paneCss = document.getElementById('pane-css');
tabHtml.addEventListener('click', () => {
tabHtml.classList.add('ha-tab-on');
tabCss.classList.remove('ha-tab-on');
paneHtml.classList.add('ha-pane-on');
paneCss.classList.remove('ha-pane-on');
});
tabCss.addEventListener('click', () => {
tabCss.classList.add('ha-tab-on');
tabHtml.classList.remove('ha-tab-on');
paneCss.classList.add('ha-pane-on');
paneHtml.classList.remove('ha-pane-on');
});
const sContainer = document.getElementById('s-container');
const sBox = document.getElementById('s-box');
const vContainer = document.getElementById('v-container');
const vBox = document.getElementById('v-box');
const codeBoxWidth = document.getElementById('code-box-width');
const container = document.getElementById('container');
const box = document.getElementById('box');
const dimsOut = document.getElementById('dims-out');
const boxText = document.getElementById('box-text');
function render() {
const containerPx = +sContainer.value;
const boxPct = +sBox.value;
vContainer.textContent = containerPx + 'px';
vBox.textContent = boxPct + '%';
codeBoxWidth.textContent = boxPct + '%';
container.style.width = containerPx + 'px';
box.style.width = boxPct + '%';
const computedBoxPx = Math.round(containerPx * boxPct / 100);
dimsOut.textContent = 'container: ' + containerPx + 'px → box: ' + boxPct + '% = ' + computedBoxPx + 'px';
boxText.textContent = 'This box takes up ' + boxPct + '% of the container\u2019s width.';
}
[sContainer, sBox].forEach(el => el.addEventListener('input', render));
render();
})();
</script>
</body>
</html>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});3️⃣ em – Relative to Parent’s Font Size
1 em = current element’s parent font size.
Can cascade if nested (may lead to unexpected scaling).
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Em Unit Anatomy</title>
<style>
.infographic-emunit {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
box-sizing: border-box;
margin: 0 auto;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: var(--paper);
color: var(--ink);
border-radius: 14px;
overflow: hidden;
}
.infographic-emunit[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
}
.ha-inner { padding: 0.5rem; }
.ha-layout { display: flex; flex-direction: column; gap: 1.2rem; }
.ha-code-wrap {
background: #0b0b0b;
border: 1px solid #2a2a2a;
border-radius: 10px;
overflow: hidden;
}
.ha-filetabs {
display: flex;
background: #161616;
border-bottom: 1px solid #2a2a2a;
}
.ha-filetab {
border: none;
background: transparent;
color: #8a8a8a;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 0.78rem;
font-weight: 700;
padding: 0.6rem 1rem;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.ha-filetab:hover { color: #e5e5e5; }
.ha-filetab.ha-tab-on {
color: #ffffff;
border-bottom-color: #5b8def;
background: #0b0b0b;
}
.ha-code {
padding: 1rem 1.8rem 1rem 0.9rem;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 0.8rem;
line-height: 1.85;
overflow-x: auto;
}
.ha-codepane { display: none; }
.ha-codepane.ha-pane-on { display: block; }
.ha-line { display: block; white-space: pre; color: #8a8a8a; padding: 0.05rem 0.4rem; }
.ha-tag { color: #60a5fa; font-weight: 700; }
.ha-sel { color: #f472b6; font-weight: 700; }
.ha-prop { color: #c586c0; }
.ha-val { color: #ce9178; }
.ha-unit-hl { color: #ffd166; font-weight: 800; }
.ha-comment { color: #5c6370; font-style: italic; }
.ha-slider-row {
display: grid;
grid-template-columns: 140px 1fr 50px;
align-items: center;
gap: 0.6rem;
}
.ha-slider-row label {
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.76rem;
font-weight: 700;
color: var(--ink-soft);
}
.ha-slider-row input[type="range"] { width: 100%; accent-color: var(--accent); }
.ha-slider-row .ha-val {
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.76rem;
color: var(--ink-soft);
text-align: right;
}
.ha-stage {
background: var(--paper-alt);
border: 1px solid var(--line);
border-radius: 10px;
padding: 1.1rem;
}
.ha-nest-level {
border: 1px dashed #b7b0a1;
border-radius: 6px;
padding: 0.5rem 0.7rem;
margin: 0.3rem 0;
}
.ha-nest-label {
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.65rem;
color: var(--ink-soft);
margin-bottom: 0.2rem;
}
.ha-nest-text {
font-family: Arial, sans-serif;
color: #333;
}
.ha-note {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-size: 0.78rem;
color: var(--ink-soft);
}
</style>
</head>
<body>
<div class="infographic-emunit" id="ha-root">
<div class="ha-inner">
<div class="ha-layout">
<div class="ha-code-wrap">
<div class="ha-filetabs">
<button class="ha-filetab ha-tab-on" id="tab-html">index.html</button>
<button class="ha-filetab" id="tab-css">style.css</button>
</div>
<div class="ha-code">
<div class="ha-codepane ha-pane-on" id="pane-html">
<span class="ha-line"><<span class="ha-tag">div</span>></span>
<span class="ha-line"> <<span class="ha-tag">p</span>>This paragraph is 2em, so it's 32px (2 × 16px).</<span class="ha-tag">p</span>></span>
<span class="ha-line"></<span class="ha-tag">div</span>></span>
</div>
<div class="ha-codepane" id="pane-css">
<span class="ha-line"><span class="ha-sel">div</span> {</span>
<span class="ha-line"> <span class="ha-prop">font-size</span>: <span class="ha-unit-hl" id="code-base">16px</span>; <span class="ha-comment">/* base font size for the div */</span></span>
<span class="ha-line">}</span>
<span class="ha-line"><span class="ha-sel">p</span> {</span>
<span class="ha-line"> <span class="ha-prop">font-size</span>: <span class="ha-unit-hl" id="code-em">2em</span>; <span class="ha-comment">/* 2 × parent's font size */</span></span>
<span class="ha-line">}</span>
</div>
</div>
</div>
<div class="ha-slider-row">
<label>base font-size</label>
<input type="range" id="s-base" min="8" max="24" value="16">
<span class="ha-val" id="v-base">16px</span>
</div>
<div class="ha-slider-row">
<label>p font-size (em)</label>
<input type="range" id="s-em" min="5" max="30" value="20" step="1">
<span class="ha-val" id="v-em">2em</span>
</div>
<div class="ha-slider-row">
<label>nested wrappers</label>
<input type="range" id="s-nest" min="0" max="4" value="0" step="1">
<span class="ha-val" id="v-nest">0</span>
</div>
<div class="ha-stage" id="stage"></div>
<div class="ha-note" id="note-out">Each em multiplies against its own parent's computed size — nest another div with its own em rule inside, and the sizes compound instead of resetting, which is exactly the "unexpected scaling" this unit warns about.</div>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.getElementById('ha-root');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') {
dark = true;
} else if (bodyAttr === 'false') {
dark = false;
} else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
})();
(function () {
const tabHtml = document.getElementById('tab-html');
const tabCss = document.getElementById('tab-css');
const paneHtml = document.getElementById('pane-html');
const paneCss = document.getElementById('pane-css');
tabHtml.addEventListener('click', () => {
tabHtml.classList.add('ha-tab-on');
tabCss.classList.remove('ha-tab-on');
paneHtml.classList.add('ha-pane-on');
paneCss.classList.remove('ha-pane-on');
});
tabCss.addEventListener('click', () => {
tabCss.classList.add('ha-tab-on');
tabHtml.classList.remove('ha-tab-on');
paneCss.classList.add('ha-pane-on');
paneHtml.classList.remove('ha-pane-on');
});
const sBase = document.getElementById('s-base');
const sEm = document.getElementById('s-em');
const sNest = document.getElementById('s-nest');
const vBase = document.getElementById('v-base');
const vEm = document.getElementById('v-em');
const vNest = document.getElementById('v-nest');
const codeBase = document.getElementById('code-base');
const codeEm = document.getElementById('code-em');
const stage = document.getElementById('stage');
const note = document.getElementById('note-out');
function render() {
const basePx = +sBase.value;
const emMult = (+sEm.value) / 10;
const nestCount = +sNest.value;
vBase.textContent = basePx + 'px';
vEm.textContent = emMult + 'em';
vNest.textContent = nestCount;
codeBase.textContent = basePx + 'px';
codeEm.textContent = emMult + 'em';
// build nested divs, each applying the SAME em rule against its own parent's computed size —
// this is what actually compounds, since every level only knows its immediate parent's size.
stage.innerHTML = '';
let currentPx = basePx;
let container = stage;
for (let level = 0; level <= nestCount; level++) {
if (level > 0) currentPx = currentPx * emMult; // each wrapper div also uses the em rule
const wrap = document.createElement('div');
wrap.className = 'ha-nest-level';
const label = document.createElement('div');
label.className = 'ha-nest-label';
label.textContent = 'div level ' + (level + 1) + ' — font-size: ' + Math.round(currentPx) + 'px';
wrap.appendChild(label);
container.appendChild(wrap);
container = wrap;
}
const p = document.createElement('p');
p.className = 'ha-nest-text';
const finalPx = currentPx * emMult;
p.style.fontSize = finalPx + 'px';
p.style.margin = '0';
p.textContent = 'This paragraph is ' + emMult + 'em, so it\u2019s ' + Math.round(finalPx) + 'px (' + emMult + ' × ' + Math.round(currentPx) + 'px).';
container.appendChild(p);
if (nestCount === 0) {
note.textContent = 'Each em multiplies against its own parent\u2019s computed size — try increasing "nested wrappers" below to see sizes compound instead of resetting.';
} else {
note.textContent = 'With ' + nestCount + ' extra nested wrapper' + (nestCount > 1 ? 's' : '') + ', the ' + emMult + 'em rule keeps applying at every level down to the paragraph — the final size (' + Math.round(finalPx) + 'px) grew far past a simple ' + emMult + ' × ' + basePx + 'px, since each div only passes its own font-size forward, not the original base.';
}
}
[sBase, sEm, sNest].forEach(el => el.addEventListener('input', render));
render();
})();
</script>
</body>
</html>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});4️⃣ rem – Relative to Root Font Size
rem = root em (based on <html> element’s font size).
More predictable than em.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Rem Unit Anatomy</title>
<style>
.infographic-remunit {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
box-sizing: border-box;
margin: 0 auto;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: var(--paper);
color: var(--ink);
border-radius: 14px;
overflow: hidden;
}
.infographic-remunit[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
}
.ha-inner { padding: 0.5rem; }
.ha-layout { display: flex; flex-direction: column; gap: 1.2rem; }
.ha-code-wrap {
background: #0b0b0b;
border: 1px solid #2a2a2a;
border-radius: 10px;
overflow: hidden;
}
.ha-filetabs {
display: flex;
background: #161616;
border-bottom: 1px solid #2a2a2a;
}
.ha-filetab {
border: none;
background: transparent;
color: #8a8a8a;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 0.78rem;
font-weight: 700;
padding: 0.6rem 1rem;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.ha-filetab:hover { color: #e5e5e5; }
.ha-filetab.ha-tab-on {
color: #ffffff;
border-bottom-color: #5b8def;
background: #0b0b0b;
}
.ha-code {
padding: 1rem 1.8rem 1rem 0.9rem;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 0.78rem;
line-height: 1.8;
overflow-x: auto;
}
.ha-codepane { display: none; }
.ha-codepane.ha-pane-on { display: block; }
.ha-line { display: block; white-space: pre; color: #8a8a8a; padding: 0.05rem 0.4rem; }
.ha-tag { color: #60a5fa; font-weight: 700; }
.ha-sel { color: #f472b6; font-weight: 700; }
.ha-prop { color: #c586c0; }
.ha-val { color: #ce9178; }
.ha-unit-hl { color: #ffd166; font-weight: 800; }
.ha-comment { color: #5c6370; font-style: italic; }
.ha-slider-row {
display: grid;
grid-template-columns: 140px 1fr 50px;
align-items: center;
gap: 0.6rem;
}
.ha-slider-row label {
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.76rem;
font-weight: 700;
color: var(--ink-soft);
}
.ha-slider-row input[type="range"] { width: 100%; accent-color: var(--accent); }
.ha-slider-row .ha-val {
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.76rem;
color: var(--ink-soft);
text-align: right;
}
.ha-stage {
background: var(--paper-alt);
border: 1px solid var(--line);
border-radius: 10px;
padding: 1.1rem;
}
.ha-nest-level {
border: 1px dashed #b7b0a1;
border-radius: 6px;
padding: 0.5rem 0.7rem;
margin: 0.3rem 0;
}
.ha-nest-label {
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.65rem;
color: var(--ink-soft);
margin-bottom: 0.3rem;
}
.ha-out-h1 { font-family: Verdana, sans-serif; color: #2c3e50; margin: 0 0 0.3rem; }
.ha-out-p { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: #555; margin: 0; }
.ha-note {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-size: 0.78rem;
color: var(--ink-soft);
}
</style>
</head>
<body>
<div class="infographic-remunit" id="ha-root">
<div class="ha-inner">
<div class="ha-layout">
<div class="ha-code-wrap">
<div class="ha-filetabs">
<button class="ha-filetab ha-tab-on" id="tab-html">index.html</button>
<button class="ha-filetab" id="tab-css">style.css</button>
</div>
<div class="ha-code">
<div class="ha-codepane ha-pane-on" id="pane-html">
<span class="ha-line"><<span class="ha-tag">h1</span>>This heading is 2rem (32px)</<span class="ha-tag">h1</span>></span>
<span class="ha-line"><<span class="ha-tag">p</span>>It scales based on the root font size defined on the <<span class="ha-tag">html</span>> element.</<span class="ha-tag">p</span>></span>
</div>
<div class="ha-codepane" id="pane-css">
<span class="ha-line"><span class="ha-sel">html</span> {</span>
<span class="ha-line"> <span class="ha-prop">font-size</span>: <span class="ha-unit-hl" id="code-root">16px</span>; <span class="ha-comment">/* root font size */</span></span>
<span class="ha-line">}</span>
<span class="ha-line"><span class="ha-sel">h1</span> {</span>
<span class="ha-line"> <span class="ha-prop">font-size</span>: <span class="ha-unit-hl" id="code-h1">2rem</span>; <span class="ha-comment">/* 2 × root */</span></span>
<span class="ha-line">}</span>
<span class="ha-line"><span class="ha-sel">p</span> {</span>
<span class="ha-line"> <span class="ha-prop">font-size</span>: <span class="ha-unit-hl" id="code-p">1rem</span>; <span class="ha-comment">/* equals root size */</span></span>
<span class="ha-line">}</span>
</div>
</div>
</div>
<div class="ha-slider-row">
<label>html font-size (root)</label>
<input type="range" id="s-root" min="8" max="24" value="16">
<span class="ha-val" id="v-root">16px</span>
</div>
<div class="ha-slider-row">
<label>h1 font-size (rem)</label>
<input type="range" id="s-h1" min="5" max="30" value="20">
<span class="ha-val" id="v-h1">2rem</span>
</div>
<div class="ha-slider-row">
<label>nested wrappers</label>
<input type="range" id="s-nest" min="0" max="4" value="0">
<span class="ha-val" id="v-nest">0</span>
</div>
<div class="ha-stage" id="stage"></div>
<div class="ha-note" id="note-out">Try pushing "nested wrappers" up — unlike em, rem always measures from the <html> root, no matter how deep the paragraph is nested. No compounding, ever.</div>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.getElementById('ha-root');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') {
dark = true;
} else if (bodyAttr === 'false') {
dark = false;
} else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
})();
(function () {
const tabHtml = document.getElementById('tab-html');
const tabCss = document.getElementById('tab-css');
const paneHtml = document.getElementById('pane-html');
const paneCss = document.getElementById('pane-css');
tabHtml.addEventListener('click', () => {
tabHtml.classList.add('ha-tab-on');
tabCss.classList.remove('ha-tab-on');
paneHtml.classList.add('ha-pane-on');
paneCss.classList.remove('ha-pane-on');
});
tabCss.addEventListener('click', () => {
tabCss.classList.add('ha-tab-on');
tabHtml.classList.remove('ha-tab-on');
paneCss.classList.add('ha-pane-on');
paneHtml.classList.remove('ha-pane-on');
});
const sRoot = document.getElementById('s-root');
const sH1 = document.getElementById('s-h1');
const sNest = document.getElementById('s-nest');
const vRoot = document.getElementById('v-root');
const vH1 = document.getElementById('v-h1');
const vNest = document.getElementById('v-nest');
const codeRoot = document.getElementById('code-root');
const codeH1 = document.getElementById('code-h1');
const stage = document.getElementById('stage');
const note = document.getElementById('note-out');
function render() {
const rootPx = +sRoot.value;
const h1Rem = (+sH1.value) / 10;
const nestCount = +sNest.value;
const pRem = 1;
vRoot.textContent = rootPx + 'px';
vH1.textContent = h1Rem + 'rem';
vNest.textContent = nestCount;
codeRoot.textContent = rootPx + 'px';
codeH1.textContent = h1Rem + 'rem';
stage.innerHTML = '';
let container = stage;
for (let level = 0; level < nestCount; level++) {
const wrap = document.createElement('div');
wrap.className = 'ha-nest-level';
const label = document.createElement('div');
label.className = 'ha-nest-label';
label.textContent = 'div level ' + (level + 1) + ' (no font-size set — irrelevant to rem)';
wrap.appendChild(label);
container.appendChild(wrap);
container = wrap;
}
const h1 = document.createElement('div');
h1.className = 'ha-out-h1';
h1.style.fontSize = (h1Rem * rootPx) + 'px';
h1.style.fontWeight = '700';
h1.textContent = 'This heading is ' + h1Rem + 'rem (' + Math.round(h1Rem * rootPx) + 'px)';
container.appendChild(h1);
const p = document.createElement('div');
p.className = 'ha-out-p';
p.style.fontSize = (pRem * rootPx) + 'px';
p.textContent = 'It scales based on the root font size (' + Math.round(pRem * rootPx) + 'px), regardless of nesting depth.';
container.appendChild(p);
if (nestCount === 0) {
note.textContent = 'Try pushing "nested wrappers" up — unlike em, rem always measures from the <html> root, no matter how deep the paragraph is nested. No compounding, ever.';
} else {
note.textContent = 'Even ' + nestCount + ' level' + (nestCount > 1 ? 's' : '') + ' deep, the heading is still exactly ' + h1Rem + ' × ' + rootPx + 'px = ' + Math.round(h1Rem * rootPx) + 'px. Nesting has zero effect on rem — it only ever looks at the root.';
}
}
[sRoot, sH1, sNest].forEach(el => el.addEventListener('input', render));
render();
})();
</script>
</body>
</html>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});5️⃣ vw – Viewport Width
1 vw = 1% of the browser's viewport width.
Adjusts with screen resizing.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vw Unit Anatomy</title>
<style>
.infographic-vwunit {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
box-sizing: border-box;
margin: 0 auto;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: var(--paper);
color: var(--ink);
border-radius: 14px;
overflow: hidden;
}
.infographic-vwunit[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
}
.ha-inner { padding: 0.5rem; }
.ha-layout { display: flex; flex-direction: column; gap: 1.1rem; }
.ha-code-wrap {
background: #0b0b0b;
border: 1px solid #2a2a2a;
border-radius: 10px;
overflow: hidden;
}
.ha-filetabs {
display: flex;
background: #161616;
border-bottom: 1px solid #2a2a2a;
}
.ha-filetab {
border: none;
background: transparent;
color: #8a8a8a;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 0.78rem;
font-weight: 700;
padding: 0.6rem 1rem;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.ha-filetab:hover { color: #e5e5e5; }
.ha-filetab.ha-tab-on {
color: #ffffff;
border-bottom-color: #5b8def;
background: #0b0b0b;
}
.ha-code {
padding: 1rem 1.8rem 1rem 0.9rem;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 0.8rem;
line-height: 1.85;
overflow-x: auto;
}
.ha-codepane { display: none; }
.ha-codepane.ha-pane-on { display: block; }
.ha-line { display: block; white-space: pre; color: #8a8a8a; padding: 0.05rem 0.4rem; }
.ha-tag { color: #60a5fa; font-weight: 700; }
.ha-sel { color: #f472b6; font-weight: 700; }
.ha-prop { color: #c586c0; }
.ha-val { color: #ce9178; }
.ha-unit-hl { color: #ffd166; font-weight: 800; }
.ha-comment { color: #5c6370; font-style: italic; }
.ha-slider-row {
display: grid;
grid-template-columns: 130px 1fr 55px;
align-items: center;
gap: 0.6rem;
}
.ha-slider-row label {
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.76rem;
font-weight: 700;
color: var(--ink-soft);
}
.ha-slider-row input[type="range"] { width: 100%; accent-color: var(--accent); }
.ha-slider-row .ha-val {
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.76rem;
color: var(--ink-soft);
text-align: right;
}
.ha-viewport-label {
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.68rem;
color: var(--ink-soft);
text-align: center;
}
.ha-viewport-frame {
border: 1px dashed #999;
border-radius: 8px;
margin: 0 auto;
overflow: hidden;
background: #fff;
transition: width 0.1s ease;
}
.ha-hero {
background-color: #3498db;
color: white;
padding: 20px 10px;
text-align: center;
box-sizing: border-box;
font-family: Georgia, "Times New Roman", serif;
transition: width 0.1s ease;
}
.ha-hero h1 { font-size: 1rem; margin: 0 0 0.3rem; }
.ha-hero p { font-size: 0.72rem; margin: 0; }
.ha-note {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-size: 0.78rem;
color: var(--ink-soft);
}
</style>
</head>
<body>
<div class="infographic-vwunit" id="ha-root">
<div class="ha-inner">
<div class="ha-layout">
<div class="ha-code-wrap">
<div class="ha-filetabs">
<button class="ha-filetab ha-tab-on" id="tab-html">index.html</button>
<button class="ha-filetab" id="tab-css">style.css</button>
</div>
<div class="ha-code">
<div class="ha-codepane ha-pane-on" id="pane-html">
<span class="ha-line"><<span class="ha-tag">section</span> <span class="ha-tag">class</span>=<span class="ha-val">"hero"</span>></span>
<span class="ha-line"> <<span class="ha-tag">h1</span>>100vw Hero Section</<span class="ha-tag">h1</span>></span>
<span class="ha-line"> <<span class="ha-tag">p</span>>This section spans the full width of the browser window using 100vw.</<span class="ha-tag">p</span>></span>
<span class="ha-line"></<span class="ha-tag">section</span>></span>
</div>
<div class="ha-codepane" id="pane-css">
<span class="ha-line"><span class="ha-sel">.hero</span> {</span>
<span class="ha-line"> <span class="ha-prop">width</span>: <span class="ha-unit-hl" id="code-vw">100vw</span>; <span class="ha-comment">/* full viewport width */</span></span>
<span class="ha-line"> <span class="ha-prop">background-color</span>: <span class="ha-val">#3498db</span>;</span>
<span class="ha-line">}</span>
</div>
</div>
</div>
<div class="ha-slider-row">
<label>simulated browser width</label>
<input type="range" id="s-viewport" min="240" max="560" value="500">
<span class="ha-val" id="v-viewport">500px</span>
</div>
<div class="ha-slider-row">
<label>.hero width (vw)</label>
<input type="range" id="s-vw" min="20" max="100" value="100">
<span class="ha-val" id="v-vw">100vw</span>
</div>
<div class="ha-viewport-label" id="viewport-label">simulated browser window — 500px wide</div>
<div class="ha-viewport-frame" id="viewport-frame">
<div class="ha-hero" id="hero">
<h1>100vw Hero Section</h1>
<p id="hero-text">This section spans the full width of the browser window using 100vw.</p>
</div>
</div>
<div class="ha-note" id="note-out">Resize the "simulated browser width" slider — the hero stretches to match every time, because 1vw is always 1% of whatever the current window width is, live.</div>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.getElementById('ha-root');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') {
dark = true;
} else if (bodyAttr === 'false') {
dark = false;
} else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
})();
(function () {
const tabHtml = document.getElementById('tab-html');
const tabCss = document.getElementById('tab-css');
const paneHtml = document.getElementById('pane-html');
const paneCss = document.getElementById('pane-css');
tabHtml.addEventListener('click', () => {
tabHtml.classList.add('ha-tab-on');
tabCss.classList.remove('ha-tab-on');
paneHtml.classList.add('ha-pane-on');
paneCss.classList.remove('ha-pane-on');
});
tabCss.addEventListener('click', () => {
tabCss.classList.add('ha-tab-on');
tabHtml.classList.remove('ha-tab-on');
paneCss.classList.add('ha-pane-on');
paneHtml.classList.remove('ha-pane-on');
});
const sViewport = document.getElementById('s-viewport');
const sVw = document.getElementById('s-vw');
const vViewport = document.getElementById('v-viewport');
const vVw = document.getElementById('v-vw');
const codeVw = document.getElementById('code-vw');
const viewportLabel = document.getElementById('viewport-label');
const viewportFrame = document.getElementById('viewport-frame');
const hero = document.getElementById('hero');
const heroText = document.getElementById('hero-text');
const note = document.getElementById('note-out');
function render() {
const viewportPx = +sViewport.value;
const vwVal = +sVw.value;
vViewport.textContent = viewportPx + 'px';
vVw.textContent = vwVal + 'vw';
codeVw.textContent = vwVal + 'vw';
viewportFrame.style.width = viewportPx + 'px';
viewportLabel.textContent = 'simulated browser window — ' + viewportPx + 'px wide';
const heroPx = Math.round(viewportPx * vwVal / 100);
hero.style.width = heroPx + 'px';
heroText.textContent = 'This section spans ' + vwVal + '% of the browser window (' + heroPx + 'px) using ' + vwVal + 'vw.';
note.textContent = '1vw always equals 1% of the current window width (' + viewportPx + 'px here) — so ' + vwVal + 'vw computes to ' + heroPx + 'px right now, and would recompute instantly if the window changed size.';
}
[sViewport, sVw].forEach(el => el.addEventListener('input', render));
render();
})();
</script>
</body>
</html>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});6️⃣ vh – Viewport Height
1 vh = 1% of the viewport height.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vh Unit Anatomy</title>
<style>
.infographic-vhunit {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
box-sizing: border-box;
margin: 0 auto;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: var(--paper);
color: var(--ink);
border-radius: 14px;
overflow: hidden;
}
.infographic-vhunit[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
}
.ha-inner { padding: 0.5rem; }
.ha-layout { display: flex; flex-direction: column; gap: 1.1rem; }
.ha-code-wrap {
background: #0b0b0b;
border: 1px solid #2a2a2a;
border-radius: 10px;
overflow: hidden;
}
.ha-filetabs {
display: flex;
background: #161616;
border-bottom: 1px solid #2a2a2a;
}
.ha-filetab {
border: none;
background: transparent;
color: #8a8a8a;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 0.78rem;
font-weight: 700;
padding: 0.6rem 1rem;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.ha-filetab:hover { color: #e5e5e5; }
.ha-filetab.ha-tab-on {
color: #ffffff;
border-bottom-color: #5b8def;
background: #0b0b0b;
}
.ha-code {
padding: 1rem 1.8rem 1rem 0.9rem;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 0.78rem;
line-height: 1.8;
overflow-x: auto;
}
.ha-codepane { display: none; }
.ha-codepane.ha-pane-on { display: block; }
.ha-line { display: block; white-space: pre; color: #8a8a8a; padding: 0.05rem 0.4rem; }
.ha-tag { color: #60a5fa; font-weight: 700; }
.ha-sel { color: #f472b6; font-weight: 700; }
.ha-prop { color: #c586c0; }
.ha-val { color: #ce9178; }
.ha-unit-hl { color: #ffd166; font-weight: 800; }
.ha-comment { color: #5c6370; font-style: italic; }
.ha-slider-row {
display: grid;
grid-template-columns: 130px 1fr 55px;
align-items: center;
gap: 0.6rem;
}
.ha-slider-row label {
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.76rem;
font-weight: 700;
color: var(--ink-soft);
}
.ha-slider-row input[type="range"] { width: 100%; accent-color: var(--accent); }
.ha-slider-row .ha-val {
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.76rem;
color: var(--ink-soft);
text-align: right;
}
.ha-viewport-label {
font-family: "SFMono-Regular", Consolas, monospace;
font-size: 0.68rem;
color: var(--ink-soft);
text-align: center;
}
.ha-viewport-frame {
border: 1px dashed #999;
border-radius: 8px;
overflow: hidden;
background: #fff;
transition: height 0.1s ease;
position: relative;
}
.ha-banner {
background: linear-gradient(to bottom, #6a11cb, #2575fc);
color: white;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
box-sizing: border-box;
padding: 10px;
transition: height 0.1s ease;
font-family: Georgia, "Times New Roman", serif;
}
.ha-banner h1 { font-size: 1.1rem; margin: 0 0 0.3rem; }
.ha-banner p { font-size: 0.7rem; margin: 0; }
.ha-note {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-size: 0.78rem;
color: var(--ink-soft);
}
</style>
</head>
<body>
<div class="infographic-vhunit" id="ha-root">
<div class="ha-inner">
<div class="ha-layout">
<div class="ha-code-wrap">
<div class="ha-filetabs">
<button class="ha-filetab ha-tab-on" id="tab-html">index.html</button>
<button class="ha-filetab" id="tab-css">style.css</button>
</div>
<div class="ha-code">
<div class="ha-codepane ha-pane-on" id="pane-html">
<span class="ha-line"><<span class="ha-tag">section</span> <span class="ha-tag">class</span>=<span class="ha-val">"banner"</span>></span>
<span class="ha-line"> <<span class="ha-tag">div</span> <span class="ha-tag">class</span>=<span class="ha-val">"content"</span>></span>
<span class="ha-line"> <<span class="ha-tag">h1</span>>100vh Banner</<span class="ha-tag">h1</span>></span>
<span class="ha-line"> <<span class="ha-tag">p</span>>This section fills the full height of the screen using 100vh.</<span class="ha-tag">p</span>></span>
<span class="ha-line"> </<span class="ha-tag">div</span>></span>
<span class="ha-line"></<span class="ha-tag">section</span>></span>
</div>
<div class="ha-codepane" id="pane-css">
<span class="ha-line"><span class="ha-sel">.banner</span> {</span>
<span class="ha-line"> <span class="ha-prop">height</span>: <span class="ha-unit-hl" id="code-vh">100vh</span>; <span class="ha-comment">/* full viewport height */</span></span>
<span class="ha-line"> <span class="ha-prop">background</span>: <span class="ha-val">linear-gradient(to bottom, #6a11cb, #2575fc)</span>;</span>
<span class="ha-line">}</span>
</div>
</div>
</div>
<div class="ha-slider-row">
<label>simulated screen height</label>
<input type="range" id="s-viewport" min="140" max="360" value="280">
<span class="ha-val" id="v-viewport">280px</span>
</div>
<div class="ha-slider-row">
<label>.banner height (vh)</label>
<input type="range" id="s-vh" min="20" max="100" value="100">
<span class="ha-val" id="v-vh">100vh</span>
</div>
<div class="ha-viewport-label" id="viewport-label">simulated screen — 280px tall</div>
<div class="ha-viewport-frame" id="viewport-frame">
<div class="ha-banner" id="banner">
<div>
<h1>100vh Banner</h1>
<p id="banner-text">This section fills the full height of the screen using 100vh.</p>
</div>
</div>
</div>
<div class="ha-note" id="note-out">Shrink the "simulated screen height" slider — the purple banner's height tracks it exactly, since 1vh is always 1% of the current screen height, recalculated live.</div>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.getElementById('ha-root');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') {
dark = true;
} else if (bodyAttr === 'false') {
dark = false;
} else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
})();
(function () {
const tabHtml = document.getElementById('tab-html');
const tabCss = document.getElementById('tab-css');
const paneHtml = document.getElementById('pane-html');
const paneCss = document.getElementById('pane-css');
tabHtml.addEventListener('click', () => {
tabHtml.classList.add('ha-tab-on');
tabCss.classList.remove('ha-tab-on');
paneHtml.classList.add('ha-pane-on');
paneCss.classList.remove('ha-pane-on');
});
tabCss.addEventListener('click', () => {
tabCss.classList.add('ha-tab-on');
tabHtml.classList.remove('ha-tab-on');
paneCss.classList.add('ha-pane-on');
paneHtml.classList.remove('ha-pane-on');
});
const sViewport = document.getElementById('s-viewport');
const sVh = document.getElementById('s-vh');
const vViewport = document.getElementById('v-viewport');
const vVh = document.getElementById('v-vh');
const codeVh = document.getElementById('code-vh');
const viewportLabel = document.getElementById('viewport-label');
const viewportFrame = document.getElementById('viewport-frame');
const banner = document.getElementById('banner');
const bannerText = document.getElementById('banner-text');
const note = document.getElementById('note-out');
function render() {
const viewportPx = +sViewport.value;
const vhVal = +sVh.value;
vViewport.textContent = viewportPx + 'px';
vVh.textContent = vhVal + 'vh';
codeVh.textContent = vhVal + 'vh';
viewportFrame.style.height = viewportPx + 'px';
viewportLabel.textContent = 'simulated screen — ' + viewportPx + 'px tall';
const bannerPx = Math.round(viewportPx * vhVal / 100);
banner.style.height = bannerPx + 'px';
bannerText.textContent = 'This section fills ' + vhVal + '% of the screen height (' + bannerPx + 'px) using ' + vhVal + 'vh.';
note.textContent = '1vh always equals 1% of the current screen height (' + viewportPx + 'px here) — so ' + vhVal + 'vh computes to ' + bannerPx + 'px right now, and would recompute instantly on any device or window resize.';
}
[sViewport, sVh].forEach(el => el.addEventListener('input', render));
render();
})();
</script>
</body>
</html>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});🔄 Comparison Table
Unit | Relative To | Responsive | Common Use |
|---|---|---|---|
px | Screen pixel | ❌ | Borders, icons |
% | Parent container | ✅ | Widths, padding |
em | Parent font size | ✅ | Buttons, spacing |
rem | Root font size | ✅ | Typography, layout |
vw | Viewport width | ✅ | Hero images, sections |
vh | Viewport height | ✅ | Fullscreen designs |
🧠 Tips for Using Units:
Use rem and % for scalable, accessible design.
Avoid too much nesting with em.
Combine vw/vh with media queries for best responsiveness.
Use px only when pixel precision is necessary.
CSS Units: px, %, em, rem, vw, vh