01 / 20
All 20 in 20 CSS Minimalist UI
- 01Modular Type Scale with a Ratio Variable
- 02Type Hierarchy Without Any Color Change
- 03System Font Stack That Needs No Webfont
- 04Optical Alignment vs Mathematical Centering
- 05Spacing Scale from a Single Base Unit
- 06Whitespace That Groups Instead of Separates
- 07Vertical Rhythm with a Consistent Baseline
- 08Minimal Layout with CSS Grid and No Divs
- 09The One Accent Color Restraint Budget
- 10Grayscale Palette with Enough Contrast
- 11Borders and Dividers You Can Actually Remove
- 12Elevation Without Shadows or Gradients
- 13Why Minimal Interfaces Fail Accessibility
- 14Minimalist and Brutalist Side by Side
- 15Minimalist Dashboard Card and Metric
- 16Minimalist Product Card for Ecommerce
- 17Minimalist Portfolio Project Listing
- 18Minimalist Article Layout and Typography
- 19Minimalist Mobile App Screen in a Frame
- 20Minimalist Landing Page Section
Modular Type Scale with a Ratio Variable
One custom property holds the ratio and every step is derived from it with calc(), so the whole type system moves when you change a single number. The same specimen renders at 1.200, 1.250 and 1.333 side by side with every computed size printed, so you can see what the ratio actually changes before you commit to one.
Untitled UI Pro: a 10,000+ component Figma UI kit · from $129 one-time
Untitled UI · Affiliate link
The code
<section class="mu-01" aria-labelledby="mu-01-title">
<h2 class="mu-01__sr" id="mu-01-title">Modular type scale specimen</h2>
<div class="mu-01__theme">
<input class="mu-01__theme-input" type="checkbox" id="mu-01-theme">
<label class="mu-01__theme-label" for="mu-01-theme">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 4a8 8 0 0 0 0 16Z" fill="currentColor"/></svg>
<span class="mu-01__sr">Toggle dark theme</span>
</label>
</div>
<div class="mu-01__stage">
<div class="mu-01__strips">
<article class="mu-01__strip" style="--mu-01-ratio: 1.2" aria-labelledby="mu-01-r1">
<header class="mu-01__strip-head">
<h3 class="mu-01__strip-title" id="mu-01-r1">1.200</h3>
<p class="mu-01__strip-sub">Minor third · dense UI</p>
</header>
<div class="mu-01__spec">
<p class="mu-01__s4">Ranger</p>
<p class="mu-01__s3">Monthly recurring revenue</p>
<p class="mu-01__s2">Draft saved</p>
<p class="mu-01__s1">A ratio this tight keeps a dense console readable, because adjacent steps stay close enough to sit in the same table row.</p>
<p class="mu-01__s0">Continue to payment</p>
</div>
<dl class="mu-01__vals">
<div><dt>s4</dt><dd>33.18px</dd></div>
<div><dt>s3</dt><dd>27.65px</dd></div>
<div><dt>s2</dt><dd>23.04px</dd></div>
<div><dt>s1</dt><dd>19.20px</dd></div>
<div><dt>s0</dt><dd>16.00px</dd></div>
</dl>
</article>
<article class="mu-01__strip mu-01__strip--mid" style="--mu-01-ratio: 1.25" aria-labelledby="mu-01-r2">
<header class="mu-01__strip-head">
<h3 class="mu-01__strip-title" id="mu-01-r2">1.250</h3>
<p class="mu-01__strip-sub">Major third · product UI</p>
</header>
<div class="mu-01__spec">
<p class="mu-01__s4">Ranger</p>
<p class="mu-01__s3">Monthly recurring revenue</p>
<p class="mu-01__s2">Draft saved</p>
<p class="mu-01__s1">The working default for most product interfaces: distinguishable steps that still leave room for four or five levels of hierarchy.</p>
<p class="mu-01__s0">Continue to payment</p>
</div>
<dl class="mu-01__vals">
<div><dt>s4</dt><dd>39.06px</dd></div>
<div><dt>s3</dt><dd>31.25px</dd></div>
<div><dt>s2</dt><dd>25.00px</dd></div>
<div><dt>s1</dt><dd>20.00px</dd></div>
<div><dt>s0</dt><dd>16.00px</dd></div>
</dl>
</article>
<article class="mu-01__strip" style="--mu-01-ratio: 1.333" aria-labelledby="mu-01-r3">
<header class="mu-01__strip-head">
<h3 class="mu-01__strip-title" id="mu-01-r3">1.333</h3>
<p class="mu-01__strip-sub">Perfect fourth · editorial</p>
</header>
<div class="mu-01__spec">
<p class="mu-01__s4">Ranger</p>
<p class="mu-01__s3">Monthly recurring revenue</p>
<p class="mu-01__s2">Draft saved</p>
<p class="mu-01__s1">Decisive on a landing page and hostile in a data table — by the fourth step the jump is 13px, which no dense interface can absorb.</p>
<p class="mu-01__s0">Continue to payment</p>
</div>
<dl class="mu-01__vals">
<div><dt>s4</dt><dd>50.53px</dd></div>
<div><dt>s3</dt><dd>37.91px</dd></div>
<div><dt>s2</dt><dd>28.44px</dd></div>
<div><dt>s1</dt><dd>21.33px</dd></div>
<div><dt>s0</dt><dd>16.00px</dd></div>
</dl>
</article>
</div>
</div>
</section><section class="mu-01" aria-labelledby="mu-01-title">
<h2 class="mu-01__sr" id="mu-01-title">Modular type scale specimen</h2>
<div class="mu-01__theme">
<input class="mu-01__theme-input" type="checkbox" id="mu-01-theme">
<label class="mu-01__theme-label" for="mu-01-theme">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 4a8 8 0 0 0 0 16Z" fill="currentColor"/></svg>
<span class="mu-01__sr">Toggle dark theme</span>
</label>
</div>
<div class="mu-01__stage">
<div class="mu-01__strips">
<article class="mu-01__strip" style="--mu-01-ratio: 1.2" aria-labelledby="mu-01-r1">
<header class="mu-01__strip-head">
<h3 class="mu-01__strip-title" id="mu-01-r1">1.200</h3>
<p class="mu-01__strip-sub">Minor third · dense UI</p>
</header>
<div class="mu-01__spec">
<p class="mu-01__s4">Ranger</p>
<p class="mu-01__s3">Monthly recurring revenue</p>
<p class="mu-01__s2">Draft saved</p>
<p class="mu-01__s1">A ratio this tight keeps a dense console readable, because adjacent steps stay close enough to sit in the same table row.</p>
<p class="mu-01__s0">Continue to payment</p>
</div>
<dl class="mu-01__vals">
<div><dt>s4</dt><dd>33.18px</dd></div>
<div><dt>s3</dt><dd>27.65px</dd></div>
<div><dt>s2</dt><dd>23.04px</dd></div>
<div><dt>s1</dt><dd>19.20px</dd></div>
<div><dt>s0</dt><dd>16.00px</dd></div>
</dl>
</article>
<article class="mu-01__strip mu-01__strip--mid" style="--mu-01-ratio: 1.25" aria-labelledby="mu-01-r2">
<header class="mu-01__strip-head">
<h3 class="mu-01__strip-title" id="mu-01-r2">1.250</h3>
<p class="mu-01__strip-sub">Major third · product UI</p>
</header>
<div class="mu-01__spec">
<p class="mu-01__s4">Ranger</p>
<p class="mu-01__s3">Monthly recurring revenue</p>
<p class="mu-01__s2">Draft saved</p>
<p class="mu-01__s1">The working default for most product interfaces: distinguishable steps that still leave room for four or five levels of hierarchy.</p>
<p class="mu-01__s0">Continue to payment</p>
</div>
<dl class="mu-01__vals">
<div><dt>s4</dt><dd>39.06px</dd></div>
<div><dt>s3</dt><dd>31.25px</dd></div>
<div><dt>s2</dt><dd>25.00px</dd></div>
<div><dt>s1</dt><dd>20.00px</dd></div>
<div><dt>s0</dt><dd>16.00px</dd></div>
</dl>
</article>
<article class="mu-01__strip" style="--mu-01-ratio: 1.333" aria-labelledby="mu-01-r3">
<header class="mu-01__strip-head">
<h3 class="mu-01__strip-title" id="mu-01-r3">1.333</h3>
<p class="mu-01__strip-sub">Perfect fourth · editorial</p>
</header>
<div class="mu-01__spec">
<p class="mu-01__s4">Ranger</p>
<p class="mu-01__s3">Monthly recurring revenue</p>
<p class="mu-01__s2">Draft saved</p>
<p class="mu-01__s1">Decisive on a landing page and hostile in a data table — by the fourth step the jump is 13px, which no dense interface can absorb.</p>
<p class="mu-01__s0">Continue to payment</p>
</div>
<dl class="mu-01__vals">
<div><dt>s4</dt><dd>50.53px</dd></div>
<div><dt>s3</dt><dd>37.91px</dd></div>
<div><dt>s2</dt><dd>28.44px</dd></div>
<div><dt>s1</dt><dd>21.33px</dd></div>
<div><dt>s0</dt><dd>16.00px</dd></div>
</dl>
</article>
</div>
</div>
</section>.mu-01 {
--mu-01-ratio: 1.25;
--mu-01-base: clamp(0.9375rem, 0.9rem + 0.25vw, 1rem);
--mu-01-s0: var(--mu-01-base);
--mu-01-s1: calc(var(--mu-01-s0) * var(--mu-01-ratio));
--mu-01-s2: calc(var(--mu-01-s1) * var(--mu-01-ratio));
--mu-01-s3: calc(var(--mu-01-s2) * var(--mu-01-ratio));
--mu-01-s4: calc(var(--mu-01-s3) * var(--mu-01-ratio));
--mu-01-unit: 0.5rem;
--mu-01-font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--mu-01-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
--mu-01-bg: #eef0f2;
--mu-01-bg: oklch(94.6% 0.005 240);
--mu-01-surface: #ffffff;
--mu-01-surface: oklch(100% 0 0);
--mu-01-ink: #14181c;
--mu-01-ink: oklch(19% 0.012 250);
--mu-01-ink-2: #4c565f;
--mu-01-ink-2: oklch(43% 0.018 250);
--mu-01-line: #c9cfd5;
--mu-01-line: oklch(84% 0.008 250);
--mu-01-acc: #1d5cff;
--mu-01-acc: oklch(52% 0.21 264);
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--mu-01-bg);
color: var(--mu-01-ink);
font-family: var(--mu-01-font);
font-size: var(--mu-01-s0);
line-height: 1.5;
-webkit-text-size-adjust: 100%;
}
.mu-01 *,
.mu-01 *::before,
.mu-01 *::after {
box-sizing: border-box;
}
@media (prefers-color-scheme: dark) {
.mu-01 {
--mu-01-bg: #0f1215;
--mu-01-bg: oklch(17% 0.008 250);
--mu-01-surface: #171b1f;
--mu-01-surface: oklch(22% 0.009 250);
--mu-01-ink: #eef1f4;
--mu-01-ink: oklch(95% 0.004 250);
--mu-01-ink-2: #a3adb6;
--mu-01-ink-2: oklch(74% 0.012 250);
--mu-01-line: #333c44;
--mu-01-line: oklch(33% 0.012 250);
--mu-01-acc: #7fa6ff;
--mu-01-acc: oklch(76% 0.14 264);
}
}
.mu-01[data-theme="dark"],
.mu-01:has(.mu-01__theme-input:checked) {
--mu-01-bg: #0f1215;
--mu-01-bg: oklch(17% 0.008 250);
--mu-01-surface: #171b1f;
--mu-01-surface: oklch(22% 0.009 250);
--mu-01-ink: #eef1f4;
--mu-01-ink: oklch(95% 0.004 250);
--mu-01-ink-2: #a3adb6;
--mu-01-ink-2: oklch(74% 0.012 250);
--mu-01-line: #333c44;
--mu-01-line: oklch(33% 0.012 250);
--mu-01-acc: #7fa6ff;
--mu-01-acc: oklch(76% 0.14 264);
}
@media (prefers-color-scheme: light) {
.mu-01:has(.mu-01__theme-input:checked) {
--mu-01-bg: #0f1215;
--mu-01-bg: oklch(17% 0.008 250);
--mu-01-surface: #171b1f;
--mu-01-surface: oklch(22% 0.009 250);
--mu-01-ink: #eef1f4;
--mu-01-ink: oklch(95% 0.004 250);
--mu-01-ink-2: #a3adb6;
--mu-01-ink-2: oklch(74% 0.012 250);
--mu-01-line: #333c44;
--mu-01-line: oklch(33% 0.012 250);
--mu-01-acc: #7fa6ff;
--mu-01-acc: oklch(76% 0.14 264);
}
}
@media (prefers-color-scheme: dark) {
.mu-01:has(.mu-01__theme-input:checked) {
--mu-01-bg: #eef0f2;
--mu-01-bg: oklch(94.6% 0.005 240);
--mu-01-surface: #ffffff;
--mu-01-surface: oklch(100% 0 0);
--mu-01-ink: #14181c;
--mu-01-ink: oklch(19% 0.012 250);
--mu-01-ink-2: #4c565f;
--mu-01-ink-2: oklch(43% 0.018 250);
--mu-01-line: #c9cfd5;
--mu-01-line: oklch(84% 0.008 250);
--mu-01-acc: #1d5cff;
--mu-01-acc: oklch(52% 0.21 264);
}
}
.mu-01__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
.mu-01__theme {
position: absolute;
inset-block-start: 1rem;
inset-inline-end: 1rem;
z-index: 5;
}
.mu-01__theme-input {
position: absolute;
inset: 0;
inline-size: 44px;
block-size: 44px;
margin: 0;
opacity: 0;
cursor: pointer;
}
.mu-01__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--mu-01-line);
border-radius: 50%;
background: var(--mu-01-surface);
color: var(--mu-01-ink-2);
cursor: pointer;
}
.mu-01__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.mu-01__theme-input:focus-visible + .mu-01__theme-label {
outline: 2px solid var(--mu-01-acc);
outline-offset: 2px;
}
.mu-01__stage {
max-inline-size: 78rem;
margin-inline: auto;
padding: calc(var(--mu-01-unit) * 7) calc(var(--mu-01-unit) * 3) calc(var(--mu-01-unit) * 9);
display: grid;
gap: calc(var(--mu-01-unit) * 6);
grid-template-columns: minmax(0, 1fr);
}
.mu-01__strips {
display: grid;
gap: calc(var(--mu-01-unit) * 3);
grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
}
.mu-01__strip {
min-inline-size: 0;
background: var(--mu-01-surface);
border: 1px solid var(--mu-01-line);
border-radius: 4px;
padding: calc(var(--mu-01-unit) * 3);
display: grid;
gap: calc(var(--mu-01-unit) * 3);
align-content: start;
}
.mu-01__strip--mid {
border-color: var(--mu-01-acc);
}
.mu-01__strip-head {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: calc(var(--mu-01-unit) * 1.5);
padding-block-end: calc(var(--mu-01-unit) * 2);
border-block-end: 1px solid var(--mu-01-line);
}
.mu-01__strip-title {
margin: 0;
font-family: var(--mu-01-mono);
font-size: 1.375rem;
font-weight: 500;
letter-spacing: -0.01em;
min-inline-size: 0;
}
.mu-01__strip-sub {
margin: 0;
font-size: 0.75rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--mu-01-ink-2);
min-inline-size: 0;
}
.mu-01__spec {
display: grid;
gap: calc(var(--mu-01-unit) * 1.5);
min-inline-size: 0;
}
.mu-01__spec p {
margin: 0;
min-inline-size: 0;
overflow-wrap: break-word;
}
.mu-01__s4 {
font-size: var(--mu-01-s4);
line-height: 1.05;
letter-spacing: -0.028em;
font-weight: 600;
}
.mu-01__s3 {
font-size: var(--mu-01-s3);
line-height: 1.15;
letter-spacing: -0.018em;
font-weight: 600;
}
.mu-01__s2 {
font-size: var(--mu-01-s2);
line-height: 1.25;
font-weight: 500;
}
.mu-01__s1 {
font-size: var(--mu-01-s1);
line-height: 1.5;
color: var(--mu-01-ink-2);
max-inline-size: 34ch;
text-wrap: pretty;
}
.mu-01__s0 {
font-size: var(--mu-01-s0);
line-height: 1.5;
font-weight: 500;
}
.mu-01__vals {
margin: 0;
display: grid;
gap: 2px;
padding-block-start: calc(var(--mu-01-unit) * 2);
border-block-start: 1px solid var(--mu-01-line);
font-family: var(--mu-01-mono);
font-size: 0.75rem;
font-variant-numeric: tabular-nums;
color: var(--mu-01-ink-2);
}
.mu-01__vals > div {
display: flex;
justify-content: space-between;
gap: var(--mu-01-unit);
}
.mu-01__vals dt {
margin: 0;
}
.mu-01__vals dd {
margin: 0;
color: var(--mu-01-ink);
}
@media (min-width: 64rem) {
.mu-01__stage {
padding-inline: calc(var(--mu-01-unit) * 6);
}
}.mu-01 {
--mu-01-ratio: 1.25;
--mu-01-base: clamp(0.9375rem, 0.9rem + 0.25vw, 1rem);
--mu-01-s0: var(--mu-01-base);
--mu-01-s1: calc(var(--mu-01-s0) * var(--mu-01-ratio));
--mu-01-s2: calc(var(--mu-01-s1) * var(--mu-01-ratio));
--mu-01-s3: calc(var(--mu-01-s2) * var(--mu-01-ratio));
--mu-01-s4: calc(var(--mu-01-s3) * var(--mu-01-ratio));
--mu-01-unit: 0.5rem;
--mu-01-font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--mu-01-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
--mu-01-bg: #eef0f2;
--mu-01-bg: oklch(94.6% 0.005 240);
--mu-01-surface: #ffffff;
--mu-01-surface: oklch(100% 0 0);
--mu-01-ink: #14181c;
--mu-01-ink: oklch(19% 0.012 250);
--mu-01-ink-2: #4c565f;
--mu-01-ink-2: oklch(43% 0.018 250);
--mu-01-line: #c9cfd5;
--mu-01-line: oklch(84% 0.008 250);
--mu-01-acc: #1d5cff;
--mu-01-acc: oklch(52% 0.21 264);
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--mu-01-bg);
color: var(--mu-01-ink);
font-family: var(--mu-01-font);
font-size: var(--mu-01-s0);
line-height: 1.5;
-webkit-text-size-adjust: 100%;
}
.mu-01 *,
.mu-01 *::before,
.mu-01 *::after {
box-sizing: border-box;
}
@media (prefers-color-scheme: dark) {
.mu-01 {
--mu-01-bg: #0f1215;
--mu-01-bg: oklch(17% 0.008 250);
--mu-01-surface: #171b1f;
--mu-01-surface: oklch(22% 0.009 250);
--mu-01-ink: #eef1f4;
--mu-01-ink: oklch(95% 0.004 250);
--mu-01-ink-2: #a3adb6;
--mu-01-ink-2: oklch(74% 0.012 250);
--mu-01-line: #333c44;
--mu-01-line: oklch(33% 0.012 250);
--mu-01-acc: #7fa6ff;
--mu-01-acc: oklch(76% 0.14 264);
}
}
.mu-01[data-theme="dark"],
.mu-01:has(.mu-01__theme-input:checked) {
--mu-01-bg: #0f1215;
--mu-01-bg: oklch(17% 0.008 250);
--mu-01-surface: #171b1f;
--mu-01-surface: oklch(22% 0.009 250);
--mu-01-ink: #eef1f4;
--mu-01-ink: oklch(95% 0.004 250);
--mu-01-ink-2: #a3adb6;
--mu-01-ink-2: oklch(74% 0.012 250);
--mu-01-line: #333c44;
--mu-01-line: oklch(33% 0.012 250);
--mu-01-acc: #7fa6ff;
--mu-01-acc: oklch(76% 0.14 264);
}
@media (prefers-color-scheme: light) {
.mu-01:has(.mu-01__theme-input:checked) {
--mu-01-bg: #0f1215;
--mu-01-bg: oklch(17% 0.008 250);
--mu-01-surface: #171b1f;
--mu-01-surface: oklch(22% 0.009 250);
--mu-01-ink: #eef1f4;
--mu-01-ink: oklch(95% 0.004 250);
--mu-01-ink-2: #a3adb6;
--mu-01-ink-2: oklch(74% 0.012 250);
--mu-01-line: #333c44;
--mu-01-line: oklch(33% 0.012 250);
--mu-01-acc: #7fa6ff;
--mu-01-acc: oklch(76% 0.14 264);
}
}
@media (prefers-color-scheme: dark) {
.mu-01:has(.mu-01__theme-input:checked) {
--mu-01-bg: #eef0f2;
--mu-01-bg: oklch(94.6% 0.005 240);
--mu-01-surface: #ffffff;
--mu-01-surface: oklch(100% 0 0);
--mu-01-ink: #14181c;
--mu-01-ink: oklch(19% 0.012 250);
--mu-01-ink-2: #4c565f;
--mu-01-ink-2: oklch(43% 0.018 250);
--mu-01-line: #c9cfd5;
--mu-01-line: oklch(84% 0.008 250);
--mu-01-acc: #1d5cff;
--mu-01-acc: oklch(52% 0.21 264);
}
}
.mu-01__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
.mu-01__theme {
position: absolute;
inset-block-start: 1rem;
inset-inline-end: 1rem;
z-index: 5;
}
.mu-01__theme-input {
position: absolute;
inset: 0;
inline-size: 44px;
block-size: 44px;
margin: 0;
opacity: 0;
cursor: pointer;
}
.mu-01__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--mu-01-line);
border-radius: 50%;
background: var(--mu-01-surface);
color: var(--mu-01-ink-2);
cursor: pointer;
}
.mu-01__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.mu-01__theme-input:focus-visible + .mu-01__theme-label {
outline: 2px solid var(--mu-01-acc);
outline-offset: 2px;
}
.mu-01__stage {
max-inline-size: 78rem;
margin-inline: auto;
padding: calc(var(--mu-01-unit) * 7) calc(var(--mu-01-unit) * 3) calc(var(--mu-01-unit) * 9);
display: grid;
gap: calc(var(--mu-01-unit) * 6);
grid-template-columns: minmax(0, 1fr);
}
.mu-01__strips {
display: grid;
gap: calc(var(--mu-01-unit) * 3);
grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
}
.mu-01__strip {
min-inline-size: 0;
background: var(--mu-01-surface);
border: 1px solid var(--mu-01-line);
border-radius: 4px;
padding: calc(var(--mu-01-unit) * 3);
display: grid;
gap: calc(var(--mu-01-unit) * 3);
align-content: start;
}
.mu-01__strip--mid {
border-color: var(--mu-01-acc);
}
.mu-01__strip-head {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: calc(var(--mu-01-unit) * 1.5);
padding-block-end: calc(var(--mu-01-unit) * 2);
border-block-end: 1px solid var(--mu-01-line);
}
.mu-01__strip-title {
margin: 0;
font-family: var(--mu-01-mono);
font-size: 1.375rem;
font-weight: 500;
letter-spacing: -0.01em;
min-inline-size: 0;
}
.mu-01__strip-sub {
margin: 0;
font-size: 0.75rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--mu-01-ink-2);
min-inline-size: 0;
}
.mu-01__spec {
display: grid;
gap: calc(var(--mu-01-unit) * 1.5);
min-inline-size: 0;
}
.mu-01__spec p {
margin: 0;
min-inline-size: 0;
overflow-wrap: break-word;
}
.mu-01__s4 {
font-size: var(--mu-01-s4);
line-height: 1.05;
letter-spacing: -0.028em;
font-weight: 600;
}
.mu-01__s3 {
font-size: var(--mu-01-s3);
line-height: 1.15;
letter-spacing: -0.018em;
font-weight: 600;
}
.mu-01__s2 {
font-size: var(--mu-01-s2);
line-height: 1.25;
font-weight: 500;
}
.mu-01__s1 {
font-size: var(--mu-01-s1);
line-height: 1.5;
color: var(--mu-01-ink-2);
max-inline-size: 34ch;
text-wrap: pretty;
}
.mu-01__s0 {
font-size: var(--mu-01-s0);
line-height: 1.5;
font-weight: 500;
}
.mu-01__vals {
margin: 0;
display: grid;
gap: 2px;
padding-block-start: calc(var(--mu-01-unit) * 2);
border-block-start: 1px solid var(--mu-01-line);
font-family: var(--mu-01-mono);
font-size: 0.75rem;
font-variant-numeric: tabular-nums;
color: var(--mu-01-ink-2);
}
.mu-01__vals > div {
display: flex;
justify-content: space-between;
gap: var(--mu-01-unit);
}
.mu-01__vals dt {
margin: 0;
}
.mu-01__vals dd {
margin: 0;
color: var(--mu-01-ink);
}
@media (min-width: 64rem) {
.mu-01__stage {
padding-inline: calc(var(--mu-01-unit) * 6);
}
}Here's a working CSS Minimalist UI from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Modular Type Scale with a Ratio Variable
Source: https://codefronts.com/design-styles/css-minimalist-ui/modular-type-scale-with-a-ratio-variable/
One custom property holds the ratio and every step is derived from it with calc(), so the whole type system moves when you change a single number. The same specimen renders at 1.200, 1.250 and 1.333 side by side with every computed size printed, so you can see what the ratio actually changes before you commit to one.
## HTML
```html
<section class="mu-01" aria-labelledby="mu-01-title">
<h2 class="mu-01__sr" id="mu-01-title">Modular type scale specimen</h2>
<div class="mu-01__theme">
<input class="mu-01__theme-input" type="checkbox" id="mu-01-theme">
<label class="mu-01__theme-label" for="mu-01-theme">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 4a8 8 0 0 0 0 16Z" fill="currentColor"/></svg>
<span class="mu-01__sr">Toggle dark theme</span>
</label>
</div>
<div class="mu-01__stage">
<div class="mu-01__strips">
<article class="mu-01__strip" style="--mu-01-ratio: 1.2" aria-labelledby="mu-01-r1">
<header class="mu-01__strip-head">
<h3 class="mu-01__strip-title" id="mu-01-r1">1.200</h3>
<p class="mu-01__strip-sub">Minor third · dense UI</p>
</header>
<div class="mu-01__spec">
<p class="mu-01__s4">Ranger</p>
<p class="mu-01__s3">Monthly recurring revenue</p>
<p class="mu-01__s2">Draft saved</p>
<p class="mu-01__s1">A ratio this tight keeps a dense console readable, because adjacent steps stay close enough to sit in the same table row.</p>
<p class="mu-01__s0">Continue to payment</p>
</div>
<dl class="mu-01__vals">
<div><dt>s4</dt><dd>33.18px</dd></div>
<div><dt>s3</dt><dd>27.65px</dd></div>
<div><dt>s2</dt><dd>23.04px</dd></div>
<div><dt>s1</dt><dd>19.20px</dd></div>
<div><dt>s0</dt><dd>16.00px</dd></div>
</dl>
</article>
<article class="mu-01__strip mu-01__strip--mid" style="--mu-01-ratio: 1.25" aria-labelledby="mu-01-r2">
<header class="mu-01__strip-head">
<h3 class="mu-01__strip-title" id="mu-01-r2">1.250</h3>
<p class="mu-01__strip-sub">Major third · product UI</p>
</header>
<div class="mu-01__spec">
<p class="mu-01__s4">Ranger</p>
<p class="mu-01__s3">Monthly recurring revenue</p>
<p class="mu-01__s2">Draft saved</p>
<p class="mu-01__s1">The working default for most product interfaces: distinguishable steps that still leave room for four or five levels of hierarchy.</p>
<p class="mu-01__s0">Continue to payment</p>
</div>
<dl class="mu-01__vals">
<div><dt>s4</dt><dd>39.06px</dd></div>
<div><dt>s3</dt><dd>31.25px</dd></div>
<div><dt>s2</dt><dd>25.00px</dd></div>
<div><dt>s1</dt><dd>20.00px</dd></div>
<div><dt>s0</dt><dd>16.00px</dd></div>
</dl>
</article>
<article class="mu-01__strip" style="--mu-01-ratio: 1.333" aria-labelledby="mu-01-r3">
<header class="mu-01__strip-head">
<h3 class="mu-01__strip-title" id="mu-01-r3">1.333</h3>
<p class="mu-01__strip-sub">Perfect fourth · editorial</p>
</header>
<div class="mu-01__spec">
<p class="mu-01__s4">Ranger</p>
<p class="mu-01__s3">Monthly recurring revenue</p>
<p class="mu-01__s2">Draft saved</p>
<p class="mu-01__s1">Decisive on a landing page and hostile in a data table — by the fourth step the jump is 13px, which no dense interface can absorb.</p>
<p class="mu-01__s0">Continue to payment</p>
</div>
<dl class="mu-01__vals">
<div><dt>s4</dt><dd>50.53px</dd></div>
<div><dt>s3</dt><dd>37.91px</dd></div>
<div><dt>s2</dt><dd>28.44px</dd></div>
<div><dt>s1</dt><dd>21.33px</dd></div>
<div><dt>s0</dt><dd>16.00px</dd></div>
</dl>
</article>
</div>
</div>
</section>
```
## CSS
```css
.mu-01 {
--mu-01-ratio: 1.25;
--mu-01-base: clamp(0.9375rem, 0.9rem + 0.25vw, 1rem);
--mu-01-s0: var(--mu-01-base);
--mu-01-s1: calc(var(--mu-01-s0) * var(--mu-01-ratio));
--mu-01-s2: calc(var(--mu-01-s1) * var(--mu-01-ratio));
--mu-01-s3: calc(var(--mu-01-s2) * var(--mu-01-ratio));
--mu-01-s4: calc(var(--mu-01-s3) * var(--mu-01-ratio));
--mu-01-unit: 0.5rem;
--mu-01-font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--mu-01-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
--mu-01-bg: #eef0f2;
--mu-01-bg: oklch(94.6% 0.005 240);
--mu-01-surface: #ffffff;
--mu-01-surface: oklch(100% 0 0);
--mu-01-ink: #14181c;
--mu-01-ink: oklch(19% 0.012 250);
--mu-01-ink-2: #4c565f;
--mu-01-ink-2: oklch(43% 0.018 250);
--mu-01-line: #c9cfd5;
--mu-01-line: oklch(84% 0.008 250);
--mu-01-acc: #1d5cff;
--mu-01-acc: oklch(52% 0.21 264);
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--mu-01-bg);
color: var(--mu-01-ink);
font-family: var(--mu-01-font);
font-size: var(--mu-01-s0);
line-height: 1.5;
-webkit-text-size-adjust: 100%;
}
.mu-01 *,
.mu-01 *::before,
.mu-01 *::after {
box-sizing: border-box;
}
@media (prefers-color-scheme: dark) {
.mu-01 {
--mu-01-bg: #0f1215;
--mu-01-bg: oklch(17% 0.008 250);
--mu-01-surface: #171b1f;
--mu-01-surface: oklch(22% 0.009 250);
--mu-01-ink: #eef1f4;
--mu-01-ink: oklch(95% 0.004 250);
--mu-01-ink-2: #a3adb6;
--mu-01-ink-2: oklch(74% 0.012 250);
--mu-01-line: #333c44;
--mu-01-line: oklch(33% 0.012 250);
--mu-01-acc: #7fa6ff;
--mu-01-acc: oklch(76% 0.14 264);
}
}
.mu-01[data-theme="dark"],
.mu-01:has(.mu-01__theme-input:checked) {
--mu-01-bg: #0f1215;
--mu-01-bg: oklch(17% 0.008 250);
--mu-01-surface: #171b1f;
--mu-01-surface: oklch(22% 0.009 250);
--mu-01-ink: #eef1f4;
--mu-01-ink: oklch(95% 0.004 250);
--mu-01-ink-2: #a3adb6;
--mu-01-ink-2: oklch(74% 0.012 250);
--mu-01-line: #333c44;
--mu-01-line: oklch(33% 0.012 250);
--mu-01-acc: #7fa6ff;
--mu-01-acc: oklch(76% 0.14 264);
}
@media (prefers-color-scheme: light) {
.mu-01:has(.mu-01__theme-input:checked) {
--mu-01-bg: #0f1215;
--mu-01-bg: oklch(17% 0.008 250);
--mu-01-surface: #171b1f;
--mu-01-surface: oklch(22% 0.009 250);
--mu-01-ink: #eef1f4;
--mu-01-ink: oklch(95% 0.004 250);
--mu-01-ink-2: #a3adb6;
--mu-01-ink-2: oklch(74% 0.012 250);
--mu-01-line: #333c44;
--mu-01-line: oklch(33% 0.012 250);
--mu-01-acc: #7fa6ff;
--mu-01-acc: oklch(76% 0.14 264);
}
}
@media (prefers-color-scheme: dark) {
.mu-01:has(.mu-01__theme-input:checked) {
--mu-01-bg: #eef0f2;
--mu-01-bg: oklch(94.6% 0.005 240);
--mu-01-surface: #ffffff;
--mu-01-surface: oklch(100% 0 0);
--mu-01-ink: #14181c;
--mu-01-ink: oklch(19% 0.012 250);
--mu-01-ink-2: #4c565f;
--mu-01-ink-2: oklch(43% 0.018 250);
--mu-01-line: #c9cfd5;
--mu-01-line: oklch(84% 0.008 250);
--mu-01-acc: #1d5cff;
--mu-01-acc: oklch(52% 0.21 264);
}
}
.mu-01__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
.mu-01__theme {
position: absolute;
inset-block-start: 1rem;
inset-inline-end: 1rem;
z-index: 5;
}
.mu-01__theme-input {
position: absolute;
inset: 0;
inline-size: 44px;
block-size: 44px;
margin: 0;
opacity: 0;
cursor: pointer;
}
.mu-01__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--mu-01-line);
border-radius: 50%;
background: var(--mu-01-surface);
color: var(--mu-01-ink-2);
cursor: pointer;
}
.mu-01__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.mu-01__theme-input:focus-visible + .mu-01__theme-label {
outline: 2px solid var(--mu-01-acc);
outline-offset: 2px;
}
.mu-01__stage {
max-inline-size: 78rem;
margin-inline: auto;
padding: calc(var(--mu-01-unit) * 7) calc(var(--mu-01-unit) * 3) calc(var(--mu-01-unit) * 9);
display: grid;
gap: calc(var(--mu-01-unit) * 6);
grid-template-columns: minmax(0, 1fr);
}
.mu-01__strips {
display: grid;
gap: calc(var(--mu-01-unit) * 3);
grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
}
.mu-01__strip {
min-inline-size: 0;
background: var(--mu-01-surface);
border: 1px solid var(--mu-01-line);
border-radius: 4px;
padding: calc(var(--mu-01-unit) * 3);
display: grid;
gap: calc(var(--mu-01-unit) * 3);
align-content: start;
}
.mu-01__strip--mid {
border-color: var(--mu-01-acc);
}
.mu-01__strip-head {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: calc(var(--mu-01-unit) * 1.5);
padding-block-end: calc(var(--mu-01-unit) * 2);
border-block-end: 1px solid var(--mu-01-line);
}
.mu-01__strip-title {
margin: 0;
font-family: var(--mu-01-mono);
font-size: 1.375rem;
font-weight: 500;
letter-spacing: -0.01em;
min-inline-size: 0;
}
.mu-01__strip-sub {
margin: 0;
font-size: 0.75rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--mu-01-ink-2);
min-inline-size: 0;
}
.mu-01__spec {
display: grid;
gap: calc(var(--mu-01-unit) * 1.5);
min-inline-size: 0;
}
.mu-01__spec p {
margin: 0;
min-inline-size: 0;
overflow-wrap: break-word;
}
.mu-01__s4 {
font-size: var(--mu-01-s4);
line-height: 1.05;
letter-spacing: -0.028em;
font-weight: 600;
}
.mu-01__s3 {
font-size: var(--mu-01-s3);
line-height: 1.15;
letter-spacing: -0.018em;
font-weight: 600;
}
.mu-01__s2 {
font-size: var(--mu-01-s2);
line-height: 1.25;
font-weight: 500;
}
.mu-01__s1 {
font-size: var(--mu-01-s1);
line-height: 1.5;
color: var(--mu-01-ink-2);
max-inline-size: 34ch;
text-wrap: pretty;
}
.mu-01__s0 {
font-size: var(--mu-01-s0);
line-height: 1.5;
font-weight: 500;
}
.mu-01__vals {
margin: 0;
display: grid;
gap: 2px;
padding-block-start: calc(var(--mu-01-unit) * 2);
border-block-start: 1px solid var(--mu-01-line);
font-family: var(--mu-01-mono);
font-size: 0.75rem;
font-variant-numeric: tabular-nums;
color: var(--mu-01-ink-2);
}
.mu-01__vals > div {
display: flex;
justify-content: space-between;
gap: var(--mu-01-unit);
}
.mu-01__vals dt {
margin: 0;
}
.mu-01__vals dd {
margin: 0;
color: var(--mu-01-ink);
}
@media (min-width: 64rem) {
.mu-01__stage {
padding-inline: calc(var(--mu-01-unit) * 6);
}
}
```Here's a working CSS Minimalist UI from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Modular Type Scale with a Ratio Variable
Source: https://codefronts.com/design-styles/css-minimalist-ui/modular-type-scale-with-a-ratio-variable/
One custom property holds the ratio and every step is derived from it with calc(), so the whole type system moves when you change a single number. The same specimen renders at 1.200, 1.250 and 1.333 side by side with every computed size printed, so you can see what the ratio actually changes before you commit to one.
## HTML
```html
<section class="mu-01" aria-labelledby="mu-01-title">
<h2 class="mu-01__sr" id="mu-01-title">Modular type scale specimen</h2>
<div class="mu-01__theme">
<input class="mu-01__theme-input" type="checkbox" id="mu-01-theme">
<label class="mu-01__theme-label" for="mu-01-theme">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M12 4a8 8 0 0 0 0 16Z" fill="currentColor"/></svg>
<span class="mu-01__sr">Toggle dark theme</span>
</label>
</div>
<div class="mu-01__stage">
<div class="mu-01__strips">
<article class="mu-01__strip" style="--mu-01-ratio: 1.2" aria-labelledby="mu-01-r1">
<header class="mu-01__strip-head">
<h3 class="mu-01__strip-title" id="mu-01-r1">1.200</h3>
<p class="mu-01__strip-sub">Minor third · dense UI</p>
</header>
<div class="mu-01__spec">
<p class="mu-01__s4">Ranger</p>
<p class="mu-01__s3">Monthly recurring revenue</p>
<p class="mu-01__s2">Draft saved</p>
<p class="mu-01__s1">A ratio this tight keeps a dense console readable, because adjacent steps stay close enough to sit in the same table row.</p>
<p class="mu-01__s0">Continue to payment</p>
</div>
<dl class="mu-01__vals">
<div><dt>s4</dt><dd>33.18px</dd></div>
<div><dt>s3</dt><dd>27.65px</dd></div>
<div><dt>s2</dt><dd>23.04px</dd></div>
<div><dt>s1</dt><dd>19.20px</dd></div>
<div><dt>s0</dt><dd>16.00px</dd></div>
</dl>
</article>
<article class="mu-01__strip mu-01__strip--mid" style="--mu-01-ratio: 1.25" aria-labelledby="mu-01-r2">
<header class="mu-01__strip-head">
<h3 class="mu-01__strip-title" id="mu-01-r2">1.250</h3>
<p class="mu-01__strip-sub">Major third · product UI</p>
</header>
<div class="mu-01__spec">
<p class="mu-01__s4">Ranger</p>
<p class="mu-01__s3">Monthly recurring revenue</p>
<p class="mu-01__s2">Draft saved</p>
<p class="mu-01__s1">The working default for most product interfaces: distinguishable steps that still leave room for four or five levels of hierarchy.</p>
<p class="mu-01__s0">Continue to payment</p>
</div>
<dl class="mu-01__vals">
<div><dt>s4</dt><dd>39.06px</dd></div>
<div><dt>s3</dt><dd>31.25px</dd></div>
<div><dt>s2</dt><dd>25.00px</dd></div>
<div><dt>s1</dt><dd>20.00px</dd></div>
<div><dt>s0</dt><dd>16.00px</dd></div>
</dl>
</article>
<article class="mu-01__strip" style="--mu-01-ratio: 1.333" aria-labelledby="mu-01-r3">
<header class="mu-01__strip-head">
<h3 class="mu-01__strip-title" id="mu-01-r3">1.333</h3>
<p class="mu-01__strip-sub">Perfect fourth · editorial</p>
</header>
<div class="mu-01__spec">
<p class="mu-01__s4">Ranger</p>
<p class="mu-01__s3">Monthly recurring revenue</p>
<p class="mu-01__s2">Draft saved</p>
<p class="mu-01__s1">Decisive on a landing page and hostile in a data table — by the fourth step the jump is 13px, which no dense interface can absorb.</p>
<p class="mu-01__s0">Continue to payment</p>
</div>
<dl class="mu-01__vals">
<div><dt>s4</dt><dd>50.53px</dd></div>
<div><dt>s3</dt><dd>37.91px</dd></div>
<div><dt>s2</dt><dd>28.44px</dd></div>
<div><dt>s1</dt><dd>21.33px</dd></div>
<div><dt>s0</dt><dd>16.00px</dd></div>
</dl>
</article>
</div>
</div>
</section>
```
## CSS
```css
.mu-01 {
--mu-01-ratio: 1.25;
--mu-01-base: clamp(0.9375rem, 0.9rem + 0.25vw, 1rem);
--mu-01-s0: var(--mu-01-base);
--mu-01-s1: calc(var(--mu-01-s0) * var(--mu-01-ratio));
--mu-01-s2: calc(var(--mu-01-s1) * var(--mu-01-ratio));
--mu-01-s3: calc(var(--mu-01-s2) * var(--mu-01-ratio));
--mu-01-s4: calc(var(--mu-01-s3) * var(--mu-01-ratio));
--mu-01-unit: 0.5rem;
--mu-01-font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--mu-01-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
--mu-01-bg: #eef0f2;
--mu-01-bg: oklch(94.6% 0.005 240);
--mu-01-surface: #ffffff;
--mu-01-surface: oklch(100% 0 0);
--mu-01-ink: #14181c;
--mu-01-ink: oklch(19% 0.012 250);
--mu-01-ink-2: #4c565f;
--mu-01-ink-2: oklch(43% 0.018 250);
--mu-01-line: #c9cfd5;
--mu-01-line: oklch(84% 0.008 250);
--mu-01-acc: #1d5cff;
--mu-01-acc: oklch(52% 0.21 264);
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--mu-01-bg);
color: var(--mu-01-ink);
font-family: var(--mu-01-font);
font-size: var(--mu-01-s0);
line-height: 1.5;
-webkit-text-size-adjust: 100%;
}
.mu-01 *,
.mu-01 *::before,
.mu-01 *::after {
box-sizing: border-box;
}
@media (prefers-color-scheme: dark) {
.mu-01 {
--mu-01-bg: #0f1215;
--mu-01-bg: oklch(17% 0.008 250);
--mu-01-surface: #171b1f;
--mu-01-surface: oklch(22% 0.009 250);
--mu-01-ink: #eef1f4;
--mu-01-ink: oklch(95% 0.004 250);
--mu-01-ink-2: #a3adb6;
--mu-01-ink-2: oklch(74% 0.012 250);
--mu-01-line: #333c44;
--mu-01-line: oklch(33% 0.012 250);
--mu-01-acc: #7fa6ff;
--mu-01-acc: oklch(76% 0.14 264);
}
}
.mu-01[data-theme="dark"],
.mu-01:has(.mu-01__theme-input:checked) {
--mu-01-bg: #0f1215;
--mu-01-bg: oklch(17% 0.008 250);
--mu-01-surface: #171b1f;
--mu-01-surface: oklch(22% 0.009 250);
--mu-01-ink: #eef1f4;
--mu-01-ink: oklch(95% 0.004 250);
--mu-01-ink-2: #a3adb6;
--mu-01-ink-2: oklch(74% 0.012 250);
--mu-01-line: #333c44;
--mu-01-line: oklch(33% 0.012 250);
--mu-01-acc: #7fa6ff;
--mu-01-acc: oklch(76% 0.14 264);
}
@media (prefers-color-scheme: light) {
.mu-01:has(.mu-01__theme-input:checked) {
--mu-01-bg: #0f1215;
--mu-01-bg: oklch(17% 0.008 250);
--mu-01-surface: #171b1f;
--mu-01-surface: oklch(22% 0.009 250);
--mu-01-ink: #eef1f4;
--mu-01-ink: oklch(95% 0.004 250);
--mu-01-ink-2: #a3adb6;
--mu-01-ink-2: oklch(74% 0.012 250);
--mu-01-line: #333c44;
--mu-01-line: oklch(33% 0.012 250);
--mu-01-acc: #7fa6ff;
--mu-01-acc: oklch(76% 0.14 264);
}
}
@media (prefers-color-scheme: dark) {
.mu-01:has(.mu-01__theme-input:checked) {
--mu-01-bg: #eef0f2;
--mu-01-bg: oklch(94.6% 0.005 240);
--mu-01-surface: #ffffff;
--mu-01-surface: oklch(100% 0 0);
--mu-01-ink: #14181c;
--mu-01-ink: oklch(19% 0.012 250);
--mu-01-ink-2: #4c565f;
--mu-01-ink-2: oklch(43% 0.018 250);
--mu-01-line: #c9cfd5;
--mu-01-line: oklch(84% 0.008 250);
--mu-01-acc: #1d5cff;
--mu-01-acc: oklch(52% 0.21 264);
}
}
.mu-01__sr {
position: absolute;
inline-size: 1px;
block-size: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
.mu-01__theme {
position: absolute;
inset-block-start: 1rem;
inset-inline-end: 1rem;
z-index: 5;
}
.mu-01__theme-input {
position: absolute;
inset: 0;
inline-size: 44px;
block-size: 44px;
margin: 0;
opacity: 0;
cursor: pointer;
}
.mu-01__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--mu-01-line);
border-radius: 50%;
background: var(--mu-01-surface);
color: var(--mu-01-ink-2);
cursor: pointer;
}
.mu-01__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.mu-01__theme-input:focus-visible + .mu-01__theme-label {
outline: 2px solid var(--mu-01-acc);
outline-offset: 2px;
}
.mu-01__stage {
max-inline-size: 78rem;
margin-inline: auto;
padding: calc(var(--mu-01-unit) * 7) calc(var(--mu-01-unit) * 3) calc(var(--mu-01-unit) * 9);
display: grid;
gap: calc(var(--mu-01-unit) * 6);
grid-template-columns: minmax(0, 1fr);
}
.mu-01__strips {
display: grid;
gap: calc(var(--mu-01-unit) * 3);
grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
}
.mu-01__strip {
min-inline-size: 0;
background: var(--mu-01-surface);
border: 1px solid var(--mu-01-line);
border-radius: 4px;
padding: calc(var(--mu-01-unit) * 3);
display: grid;
gap: calc(var(--mu-01-unit) * 3);
align-content: start;
}
.mu-01__strip--mid {
border-color: var(--mu-01-acc);
}
.mu-01__strip-head {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: calc(var(--mu-01-unit) * 1.5);
padding-block-end: calc(var(--mu-01-unit) * 2);
border-block-end: 1px solid var(--mu-01-line);
}
.mu-01__strip-title {
margin: 0;
font-family: var(--mu-01-mono);
font-size: 1.375rem;
font-weight: 500;
letter-spacing: -0.01em;
min-inline-size: 0;
}
.mu-01__strip-sub {
margin: 0;
font-size: 0.75rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--mu-01-ink-2);
min-inline-size: 0;
}
.mu-01__spec {
display: grid;
gap: calc(var(--mu-01-unit) * 1.5);
min-inline-size: 0;
}
.mu-01__spec p {
margin: 0;
min-inline-size: 0;
overflow-wrap: break-word;
}
.mu-01__s4 {
font-size: var(--mu-01-s4);
line-height: 1.05;
letter-spacing: -0.028em;
font-weight: 600;
}
.mu-01__s3 {
font-size: var(--mu-01-s3);
line-height: 1.15;
letter-spacing: -0.018em;
font-weight: 600;
}
.mu-01__s2 {
font-size: var(--mu-01-s2);
line-height: 1.25;
font-weight: 500;
}
.mu-01__s1 {
font-size: var(--mu-01-s1);
line-height: 1.5;
color: var(--mu-01-ink-2);
max-inline-size: 34ch;
text-wrap: pretty;
}
.mu-01__s0 {
font-size: var(--mu-01-s0);
line-height: 1.5;
font-weight: 500;
}
.mu-01__vals {
margin: 0;
display: grid;
gap: 2px;
padding-block-start: calc(var(--mu-01-unit) * 2);
border-block-start: 1px solid var(--mu-01-line);
font-family: var(--mu-01-mono);
font-size: 0.75rem;
font-variant-numeric: tabular-nums;
color: var(--mu-01-ink-2);
}
.mu-01__vals > div {
display: flex;
justify-content: space-between;
gap: var(--mu-01-unit);
}
.mu-01__vals dt {
margin: 0;
}
.mu-01__vals dd {
margin: 0;
color: var(--mu-01-ink);
}
@media (min-width: 64rem) {
.mu-01__stage {
padding-inline: calc(var(--mu-01-unit) * 6);
}
}
```How this works
A ratio is a decision; a list of pixel values is twelve decisions you will contradict later. Set one base size and one ratio, then derive every step by multiplication. The result is that adjacent sizes are always distinguishable by the same proportional amount, which is exactly what makes hierarchy read without any other signal. Hand-picked sizes drift — 14, 16, 18, 20, 24 has three different intervals in it, and the 18 will end up doing nothing that the 16 was not already doing.
The mechanism is a chain of calc(), not maths you do in your head. Each step multiplies the previous step by --mu-01-ratio. Because custom properties resolve at computed-value time, redeclaring the ratio on any element recalculates every step inside it — which is how the three specimens here run three different scales from one identical rule set. Nothing is duplicated, and there is no build step.
Fluidity belongs on the base, not on every step. Put a clamp() on the base size and every derived step scales with the viewport automatically, in proportion. Writing clamp() on each step separately is the common mistake: the steps then interpolate at different rates and the scale you designed only exists at two viewport widths.
The trap, stated plainly. A large ratio looks decisive in a heading specimen and is unusable in a dense UI. At 1.333 the jump from body to the step above it is 5px at 16px base but 21px four steps up, so a table header and a page title end up almost the same size or absurdly far apart. Interface work generally wants 1.125 to 1.250, with editorial and landing pages taking the larger ratios. If your product has both — a marketing site and a dense console — you want two ratios, not one compromise ratio that serves neither.
Make it yours
- Change
--mu-01-ratioon the root and the entire scale rebuilds; nothing else needs editing. - Retune the base with
--mu-01-base: clamp(0.95rem, 0.9rem + 0.3vw, 1.0625rem)to shift every step at once. - Add a step by appending
--mu-01-s6: calc(var(--mu-01-s5) * var(--mu-01-ratio))— the chain extends indefinitely. - For exponent maths, gate it:
@supports (font-size: pow(1, 1)) { --mu-01-s3: calc(var(--mu-01-base) * pow(var(--mu-01-ratio), 3)); }and keep thecalc()chain as the fallback. - Swap the specimen face by editing
--mu-01-font; the annotation face is a separate property so labels stay monospace. - Drop the third strip for a two-way comparison — the strips are independent and carry their own ratio.
Gotchas — read before shipping
- A ratio chosen from a heading specimen fails in a dense table. Test the scale on your smallest real UI text, not on a hero headline, or you will ship a scale whose lower steps are indistinguishable.
- Rounding to whole pixels breaks the ratio. Let the browser keep the fractional value —
19.2pxis correct and snapping it to19pxcompounds into visible drift by the fourth step. - Applying
clamp()to every step instead of the base makes the steps interpolate independently, so the proportional relationship you designed only holds at the clamp endpoints. - More steps is not more system. Five to seven steps that each have a job beats twelve where four are never used and two are used interchangeably.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 114+ | 17.5+ | 121+ | 114+ |
Floor set by :has(), oklch(), text-wrap as this demo is written. The core technique itself goes back further — Chrome 111+.
oklch() and the :has() theme toggle set the floor, not the type scale — custom properties, calc() and clamp() have been Baseline since 2020, so the scale itself works far further back. pow() is deliberately avoided in the shipped CSS (it would raise the floor to Chrome 125 / Safari 18 / Firefox 118) and offered only as an @supports-gated upgrade. Older engines take the sRGB hex declared first and keep the operating-system colour scheme.