07 / 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
Vertical Rhythm with a Consistent Baseline
Line heights and block margins are multiples of one rhythm unit, so text blocks line up down the page. A toggleable overlay draws the grid over live prose so you can see where the rhythm holds and where an image or an input breaks it — which it will, and the demo says what to do about that.
Untitled UI Pro: a 10,000+ component Figma UI kit · from $129 one-time
Untitled UI · Affiliate link
The code
<section class="mu-07" aria-labelledby="mu-07-title">
<h2 class="mu-07__sr" id="mu-07-title">Vertical rhythm article</h2>
<div class="mu-07__theme">
<input class="mu-07__theme-input" type="checkbox" id="mu-07-theme">
<label class="mu-07__theme-label" for="mu-07-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-07__sr">Toggle dark theme</span>
</label>
</div>
<div class="mu-07__stage">
<header class="mu-07__head">
<div class="mu-07__controls">
<button class="mu-07__toggle" id="mu-07-grid-btn" type="button" aria-pressed="false">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M3 6h18M3 12h18M3 18h18" fill="none" stroke="currentColor" stroke-width="1.6"/></svg>
Baseline overlay
</button>
<p class="mu-07__unit">Unit <b>1.5rem</b> · 24px at a 16px root</p>
</div>
</header>
<article class="mu-07__article" id="mu-07-article">
<h3 class="mu-07__h3">Rhythm survives real content, grids do not</h3>
<p class="mu-07__lead">Set the rhythm once and the column composes itself. Body copy takes one unit per line, a heading takes two, and the gap between blocks takes one — so the eye reads order it cannot name.</p>
<p>Turn the overlay on and the mechanism becomes obvious: every line of prose sits on a rule, and every block boundary lands on one too. Nothing here is hand-nudged. The whole column is derived from a single custom property, which means the rhythm can be retuned for a different reading size in one edit.</p>
<h4 class="mu-07__h4">Where it stops being free</h4>
<p>The image below is 3:2, which is not a multiple of anything. Its caption is smaller than the body, so its own line height is not a whole unit either. A strict per-line grid would need padding calculated from the image height — a value that changes with the viewport, so the correction cannot be static.</p>
<figure class="mu-07__figure">
<div class="mu-07__frame" role="img" aria-label="Tonal placeholder standing in for an article image, 3 by 2">
<span class="mu-07__frame-note">3:2 image area</span>
</div>
<figcaption class="mu-07__caption">A block on the grid, with an interior that is not. This is the compromise that keeps the system usable.</figcaption>
</figure>
<p>The practical answer is rhythm at the block level. Each block <em>starts</em> on the grid; its interior may not. That is nine-tenths of the perceived order at a tenth of the fragility, and it holds when an editor adds a pull quote, a table, or an embed you did not plan for.</p>
<blockquote class="mu-07__quote">
<p>If the correction has to be recalculated every time the content changes, it is not a system.</p>
</blockquote>
<p>Where a print designer can force every element onto the baseline because the page is fixed, a web page is not. Accepting that is what separates a rhythm that ships from a grid that gets quietly deleted in the third sprint.</p>
</article>
</div>
</section><section class="mu-07" aria-labelledby="mu-07-title">
<h2 class="mu-07__sr" id="mu-07-title">Vertical rhythm article</h2>
<div class="mu-07__theme">
<input class="mu-07__theme-input" type="checkbox" id="mu-07-theme">
<label class="mu-07__theme-label" for="mu-07-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-07__sr">Toggle dark theme</span>
</label>
</div>
<div class="mu-07__stage">
<header class="mu-07__head">
<div class="mu-07__controls">
<button class="mu-07__toggle" id="mu-07-grid-btn" type="button" aria-pressed="false">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M3 6h18M3 12h18M3 18h18" fill="none" stroke="currentColor" stroke-width="1.6"/></svg>
Baseline overlay
</button>
<p class="mu-07__unit">Unit <b>1.5rem</b> · 24px at a 16px root</p>
</div>
</header>
<article class="mu-07__article" id="mu-07-article">
<h3 class="mu-07__h3">Rhythm survives real content, grids do not</h3>
<p class="mu-07__lead">Set the rhythm once and the column composes itself. Body copy takes one unit per line, a heading takes two, and the gap between blocks takes one — so the eye reads order it cannot name.</p>
<p>Turn the overlay on and the mechanism becomes obvious: every line of prose sits on a rule, and every block boundary lands on one too. Nothing here is hand-nudged. The whole column is derived from a single custom property, which means the rhythm can be retuned for a different reading size in one edit.</p>
<h4 class="mu-07__h4">Where it stops being free</h4>
<p>The image below is 3:2, which is not a multiple of anything. Its caption is smaller than the body, so its own line height is not a whole unit either. A strict per-line grid would need padding calculated from the image height — a value that changes with the viewport, so the correction cannot be static.</p>
<figure class="mu-07__figure">
<div class="mu-07__frame" role="img" aria-label="Tonal placeholder standing in for an article image, 3 by 2">
<span class="mu-07__frame-note">3:2 image area</span>
</div>
<figcaption class="mu-07__caption">A block on the grid, with an interior that is not. This is the compromise that keeps the system usable.</figcaption>
</figure>
<p>The practical answer is rhythm at the block level. Each block <em>starts</em> on the grid; its interior may not. That is nine-tenths of the perceived order at a tenth of the fragility, and it holds when an editor adds a pull quote, a table, or an embed you did not plan for.</p>
<blockquote class="mu-07__quote">
<p>If the correction has to be recalculated every time the content changes, it is not a system.</p>
</blockquote>
<p>Where a print designer can force every element onto the baseline because the page is fixed, a web page is not. Accepting that is what separates a rhythm that ships from a grid that gets quietly deleted in the third sprint.</p>
</article>
</div>
</section>.mu-07 {
--mu-07-rhythm: 1.5rem;
--mu-07-h-lead: 2;
--mu-07-measure: 66ch;
--mu-07-fig-fix: 0px;
--mu-07-serif: ui-serif, Georgia, "Iowan Old Style", "Palatino Linotype", Palatino, serif;
--mu-07-ui: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--mu-07-bg: #faf6ee;
--mu-07-bg: oklch(97.5% 0.012 88);
--mu-07-surface: #fffdf8;
--mu-07-surface: oklch(99.2% 0.006 88);
--mu-07-ink: #1a1712;
--mu-07-ink: oklch(20% 0.01 70);
--mu-07-ink-2: #57503f;
--mu-07-ink-2: oklch(45% 0.018 78);
--mu-07-line: #ddd4c1;
--mu-07-line: oklch(87% 0.02 85);
--mu-07-grid: rgba(139, 74, 20, 0.22);
--mu-07-acc: #8b4a14;
--mu-07-acc: oklch(45% 0.12 55);
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--mu-07-bg);
color: var(--mu-07-ink);
font-family: var(--mu-07-ui);
line-height: 1.5;
-webkit-text-size-adjust: 100%;
}
.mu-07 *,
.mu-07 *::before,
.mu-07 *::after {
box-sizing: border-box;
}
@media (prefers-color-scheme: dark) {
.mu-07 {
--mu-07-bg: #14120e;
--mu-07-bg: oklch(18% 0.009 70);
--mu-07-surface: #1c1914;
--mu-07-surface: oklch(22.5% 0.01 72);
--mu-07-ink: #f4efe4;
--mu-07-ink: oklch(96% 0.012 88);
--mu-07-ink-2: #b3a993;
--mu-07-ink-2: oklch(76% 0.02 85);
--mu-07-line: #3b342a;
--mu-07-line: oklch(33% 0.015 78);
--mu-07-grid: rgba(232, 168, 106, 0.24);
--mu-07-acc: #e8a86a;
--mu-07-acc: oklch(79% 0.11 60);
}
}
.mu-07[data-theme="dark"],
.mu-07:has(.mu-07__theme-input:checked) {
--mu-07-bg: #14120e;
--mu-07-bg: oklch(18% 0.009 70);
--mu-07-surface: #1c1914;
--mu-07-surface: oklch(22.5% 0.01 72);
--mu-07-ink: #f4efe4;
--mu-07-ink: oklch(96% 0.012 88);
--mu-07-ink-2: #b3a993;
--mu-07-ink-2: oklch(76% 0.02 85);
--mu-07-line: #3b342a;
--mu-07-line: oklch(33% 0.015 78);
--mu-07-grid: rgba(232, 168, 106, 0.24);
--mu-07-acc: #e8a86a;
--mu-07-acc: oklch(79% 0.11 60);
}
@media (prefers-color-scheme: light) {
.mu-07:has(.mu-07__theme-input:checked) {
--mu-07-bg: #14120e;
--mu-07-bg: oklch(18% 0.009 70);
--mu-07-surface: #1c1914;
--mu-07-surface: oklch(22.5% 0.01 72);
--mu-07-ink: #f4efe4;
--mu-07-ink: oklch(96% 0.012 88);
--mu-07-ink-2: #b3a993;
--mu-07-ink-2: oklch(76% 0.02 85);
--mu-07-line: #3b342a;
--mu-07-line: oklch(33% 0.015 78);
--mu-07-grid: rgba(232, 168, 106, 0.24);
--mu-07-acc: #e8a86a;
--mu-07-acc: oklch(79% 0.11 60);
}
}
@media (prefers-color-scheme: dark) {
.mu-07:has(.mu-07__theme-input:checked) {
--mu-07-bg: #faf6ee;
--mu-07-bg: oklch(97.5% 0.012 88);
--mu-07-surface: #fffdf8;
--mu-07-surface: oklch(99.2% 0.006 88);
--mu-07-ink: #1a1712;
--mu-07-ink: oklch(20% 0.01 70);
--mu-07-ink-2: #57503f;
--mu-07-ink-2: oklch(45% 0.018 78);
--mu-07-line: #ddd4c1;
--mu-07-line: oklch(87% 0.02 85);
--mu-07-grid: rgba(139, 74, 20, 0.22);
--mu-07-acc: #8b4a14;
--mu-07-acc: oklch(45% 0.12 55);
}
}
.mu-07__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-07__theme {
position: absolute;
inset-block-start: 1rem;
inset-inline-end: 1rem;
z-index: 5;
}
.mu-07__theme-input {
position: absolute;
inset: 0;
inline-size: 44px;
block-size: 44px;
margin: 0;
opacity: 0;
cursor: pointer;
}
.mu-07__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--mu-07-line);
border-radius: 50%;
background: var(--mu-07-surface);
color: var(--mu-07-ink-2);
cursor: pointer;
}
.mu-07__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.mu-07__theme-input:focus-visible + .mu-07__theme-label {
outline: 2px solid var(--mu-07-acc);
outline-offset: 2px;
}
.mu-07__stage {
max-inline-size: 60rem;
margin-inline: auto;
padding: calc(var(--mu-07-rhythm) * 3) 1.5rem calc(var(--mu-07-rhythm) * 4);
display: grid;
gap: calc(var(--mu-07-rhythm) * 2);
grid-template-columns: minmax(0, 1fr);
}
.mu-07__head {
display: grid;
gap: var(--mu-07-rhythm);
max-inline-size: var(--mu-07-measure);
}
.mu-07__controls {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.75rem 1rem;
padding-block-start: 0.5rem;
}
.mu-07__toggle {
display: inline-flex;
align-items: center;
gap: 0.5rem;
min-block-size: 44px;
min-inline-size: 44px;
padding-inline: 1rem;
border: 1px solid var(--mu-07-line);
border-radius: 4px;
background: var(--mu-07-surface);
color: var(--mu-07-ink);
font: inherit;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: border-color 150ms ease, color 150ms ease;
}
.mu-07__toggle svg {
inline-size: 18px;
block-size: 18px;
flex: none;
}
.mu-07__toggle:hover {
border-color: var(--mu-07-acc);
}
.mu-07__toggle:focus-visible {
outline: 2px solid var(--mu-07-acc);
outline-offset: 2px;
}
.mu-07__toggle[aria-pressed="true"] {
border-color: var(--mu-07-acc);
color: var(--mu-07-acc);
}
.mu-07__unit {
margin: 0;
font-size: 0.8125rem;
color: var(--mu-07-ink-2);
font-variant-numeric: tabular-nums;
}
.mu-07__article {
position: relative;
min-inline-size: 0;
max-inline-size: var(--mu-07-measure);
font-family: var(--mu-07-serif);
font-size: 1.0625rem;
line-height: var(--mu-07-rhythm);
}
.mu-07__article.is-grid {
background-image: repeating-linear-gradient(
to bottom,
var(--mu-07-grid) 0,
var(--mu-07-grid) 1px,
transparent 1px,
transparent var(--mu-07-rhythm)
);
background-position: 0 0;
}
.mu-07__article > * {
margin: 0 0 var(--mu-07-rhythm);
min-inline-size: 0;
}
.mu-07__article > :last-child {
margin-block-end: 0;
}
.mu-07__h3 {
font-size: 1.5rem;
line-height: calc(var(--mu-07-rhythm) * var(--mu-07-h-lead));
letter-spacing: -0.018em;
font-weight: 600;
margin-block: 0 var(--mu-07-rhythm);
text-wrap: balance;
}
.mu-07__h4 {
font-family: var(--mu-07-ui);
font-size: 0.8125rem;
line-height: var(--mu-07-rhythm);
letter-spacing: 0.1em;
text-transform: uppercase;
font-weight: 700;
color: var(--mu-07-ink-2);
margin-block: calc(var(--mu-07-rhythm) * 2) var(--mu-07-rhythm);
}
.mu-07__lead {
font-size: 1.1875rem;
line-height: calc(var(--mu-07-rhythm) * 1.5);
text-wrap: pretty;
}
.mu-07__article p {
text-wrap: pretty;
}
.mu-07__figure {
margin-block: calc(var(--mu-07-rhythm) * 2) var(--mu-07-rhythm);
}
.mu-07__frame {
aspect-ratio: 3 / 2;
inline-size: 100%;
display: grid;
place-items: center;
background: var(--mu-07-surface);
border: 1px solid var(--mu-07-line);
border-radius: 4px;
margin-block-end: calc(var(--mu-07-rhythm) - var(--mu-07-fig-fix));
}
.mu-07__frame-note {
font-family: var(--mu-07-ui);
font-size: 0.75rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--mu-07-ink-2);
}
.mu-07__caption {
font-family: var(--mu-07-ui);
font-size: 0.8125rem;
line-height: var(--mu-07-rhythm);
color: var(--mu-07-ink-2);
text-wrap: pretty;
}
.mu-07__quote {
margin-block: calc(var(--mu-07-rhythm) * 2);
margin-inline: 0;
padding-inline-start: 1.25rem;
border-inline-start: 2px solid var(--mu-07-acc);
}
.mu-07__quote p {
margin: 0;
font-size: 1.3125rem;
line-height: calc(var(--mu-07-rhythm) * 1.5);
letter-spacing: -0.014em;
text-wrap: pretty;
}
@media (prefers-reduced-motion: reduce) {
.mu-07__toggle {
transition: none;
}
}
@media (min-width: 56rem) {
.mu-07__stage {
padding-inline: 3rem;
}
}.mu-07 {
--mu-07-rhythm: 1.5rem;
--mu-07-h-lead: 2;
--mu-07-measure: 66ch;
--mu-07-fig-fix: 0px;
--mu-07-serif: ui-serif, Georgia, "Iowan Old Style", "Palatino Linotype", Palatino, serif;
--mu-07-ui: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--mu-07-bg: #faf6ee;
--mu-07-bg: oklch(97.5% 0.012 88);
--mu-07-surface: #fffdf8;
--mu-07-surface: oklch(99.2% 0.006 88);
--mu-07-ink: #1a1712;
--mu-07-ink: oklch(20% 0.01 70);
--mu-07-ink-2: #57503f;
--mu-07-ink-2: oklch(45% 0.018 78);
--mu-07-line: #ddd4c1;
--mu-07-line: oklch(87% 0.02 85);
--mu-07-grid: rgba(139, 74, 20, 0.22);
--mu-07-acc: #8b4a14;
--mu-07-acc: oklch(45% 0.12 55);
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--mu-07-bg);
color: var(--mu-07-ink);
font-family: var(--mu-07-ui);
line-height: 1.5;
-webkit-text-size-adjust: 100%;
}
.mu-07 *,
.mu-07 *::before,
.mu-07 *::after {
box-sizing: border-box;
}
@media (prefers-color-scheme: dark) {
.mu-07 {
--mu-07-bg: #14120e;
--mu-07-bg: oklch(18% 0.009 70);
--mu-07-surface: #1c1914;
--mu-07-surface: oklch(22.5% 0.01 72);
--mu-07-ink: #f4efe4;
--mu-07-ink: oklch(96% 0.012 88);
--mu-07-ink-2: #b3a993;
--mu-07-ink-2: oklch(76% 0.02 85);
--mu-07-line: #3b342a;
--mu-07-line: oklch(33% 0.015 78);
--mu-07-grid: rgba(232, 168, 106, 0.24);
--mu-07-acc: #e8a86a;
--mu-07-acc: oklch(79% 0.11 60);
}
}
.mu-07[data-theme="dark"],
.mu-07:has(.mu-07__theme-input:checked) {
--mu-07-bg: #14120e;
--mu-07-bg: oklch(18% 0.009 70);
--mu-07-surface: #1c1914;
--mu-07-surface: oklch(22.5% 0.01 72);
--mu-07-ink: #f4efe4;
--mu-07-ink: oklch(96% 0.012 88);
--mu-07-ink-2: #b3a993;
--mu-07-ink-2: oklch(76% 0.02 85);
--mu-07-line: #3b342a;
--mu-07-line: oklch(33% 0.015 78);
--mu-07-grid: rgba(232, 168, 106, 0.24);
--mu-07-acc: #e8a86a;
--mu-07-acc: oklch(79% 0.11 60);
}
@media (prefers-color-scheme: light) {
.mu-07:has(.mu-07__theme-input:checked) {
--mu-07-bg: #14120e;
--mu-07-bg: oklch(18% 0.009 70);
--mu-07-surface: #1c1914;
--mu-07-surface: oklch(22.5% 0.01 72);
--mu-07-ink: #f4efe4;
--mu-07-ink: oklch(96% 0.012 88);
--mu-07-ink-2: #b3a993;
--mu-07-ink-2: oklch(76% 0.02 85);
--mu-07-line: #3b342a;
--mu-07-line: oklch(33% 0.015 78);
--mu-07-grid: rgba(232, 168, 106, 0.24);
--mu-07-acc: #e8a86a;
--mu-07-acc: oklch(79% 0.11 60);
}
}
@media (prefers-color-scheme: dark) {
.mu-07:has(.mu-07__theme-input:checked) {
--mu-07-bg: #faf6ee;
--mu-07-bg: oklch(97.5% 0.012 88);
--mu-07-surface: #fffdf8;
--mu-07-surface: oklch(99.2% 0.006 88);
--mu-07-ink: #1a1712;
--mu-07-ink: oklch(20% 0.01 70);
--mu-07-ink-2: #57503f;
--mu-07-ink-2: oklch(45% 0.018 78);
--mu-07-line: #ddd4c1;
--mu-07-line: oklch(87% 0.02 85);
--mu-07-grid: rgba(139, 74, 20, 0.22);
--mu-07-acc: #8b4a14;
--mu-07-acc: oklch(45% 0.12 55);
}
}
.mu-07__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-07__theme {
position: absolute;
inset-block-start: 1rem;
inset-inline-end: 1rem;
z-index: 5;
}
.mu-07__theme-input {
position: absolute;
inset: 0;
inline-size: 44px;
block-size: 44px;
margin: 0;
opacity: 0;
cursor: pointer;
}
.mu-07__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--mu-07-line);
border-radius: 50%;
background: var(--mu-07-surface);
color: var(--mu-07-ink-2);
cursor: pointer;
}
.mu-07__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.mu-07__theme-input:focus-visible + .mu-07__theme-label {
outline: 2px solid var(--mu-07-acc);
outline-offset: 2px;
}
.mu-07__stage {
max-inline-size: 60rem;
margin-inline: auto;
padding: calc(var(--mu-07-rhythm) * 3) 1.5rem calc(var(--mu-07-rhythm) * 4);
display: grid;
gap: calc(var(--mu-07-rhythm) * 2);
grid-template-columns: minmax(0, 1fr);
}
.mu-07__head {
display: grid;
gap: var(--mu-07-rhythm);
max-inline-size: var(--mu-07-measure);
}
.mu-07__controls {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.75rem 1rem;
padding-block-start: 0.5rem;
}
.mu-07__toggle {
display: inline-flex;
align-items: center;
gap: 0.5rem;
min-block-size: 44px;
min-inline-size: 44px;
padding-inline: 1rem;
border: 1px solid var(--mu-07-line);
border-radius: 4px;
background: var(--mu-07-surface);
color: var(--mu-07-ink);
font: inherit;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: border-color 150ms ease, color 150ms ease;
}
.mu-07__toggle svg {
inline-size: 18px;
block-size: 18px;
flex: none;
}
.mu-07__toggle:hover {
border-color: var(--mu-07-acc);
}
.mu-07__toggle:focus-visible {
outline: 2px solid var(--mu-07-acc);
outline-offset: 2px;
}
.mu-07__toggle[aria-pressed="true"] {
border-color: var(--mu-07-acc);
color: var(--mu-07-acc);
}
.mu-07__unit {
margin: 0;
font-size: 0.8125rem;
color: var(--mu-07-ink-2);
font-variant-numeric: tabular-nums;
}
.mu-07__article {
position: relative;
min-inline-size: 0;
max-inline-size: var(--mu-07-measure);
font-family: var(--mu-07-serif);
font-size: 1.0625rem;
line-height: var(--mu-07-rhythm);
}
.mu-07__article.is-grid {
background-image: repeating-linear-gradient(
to bottom,
var(--mu-07-grid) 0,
var(--mu-07-grid) 1px,
transparent 1px,
transparent var(--mu-07-rhythm)
);
background-position: 0 0;
}
.mu-07__article > * {
margin: 0 0 var(--mu-07-rhythm);
min-inline-size: 0;
}
.mu-07__article > :last-child {
margin-block-end: 0;
}
.mu-07__h3 {
font-size: 1.5rem;
line-height: calc(var(--mu-07-rhythm) * var(--mu-07-h-lead));
letter-spacing: -0.018em;
font-weight: 600;
margin-block: 0 var(--mu-07-rhythm);
text-wrap: balance;
}
.mu-07__h4 {
font-family: var(--mu-07-ui);
font-size: 0.8125rem;
line-height: var(--mu-07-rhythm);
letter-spacing: 0.1em;
text-transform: uppercase;
font-weight: 700;
color: var(--mu-07-ink-2);
margin-block: calc(var(--mu-07-rhythm) * 2) var(--mu-07-rhythm);
}
.mu-07__lead {
font-size: 1.1875rem;
line-height: calc(var(--mu-07-rhythm) * 1.5);
text-wrap: pretty;
}
.mu-07__article p {
text-wrap: pretty;
}
.mu-07__figure {
margin-block: calc(var(--mu-07-rhythm) * 2) var(--mu-07-rhythm);
}
.mu-07__frame {
aspect-ratio: 3 / 2;
inline-size: 100%;
display: grid;
place-items: center;
background: var(--mu-07-surface);
border: 1px solid var(--mu-07-line);
border-radius: 4px;
margin-block-end: calc(var(--mu-07-rhythm) - var(--mu-07-fig-fix));
}
.mu-07__frame-note {
font-family: var(--mu-07-ui);
font-size: 0.75rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--mu-07-ink-2);
}
.mu-07__caption {
font-family: var(--mu-07-ui);
font-size: 0.8125rem;
line-height: var(--mu-07-rhythm);
color: var(--mu-07-ink-2);
text-wrap: pretty;
}
.mu-07__quote {
margin-block: calc(var(--mu-07-rhythm) * 2);
margin-inline: 0;
padding-inline-start: 1.25rem;
border-inline-start: 2px solid var(--mu-07-acc);
}
.mu-07__quote p {
margin: 0;
font-size: 1.3125rem;
line-height: calc(var(--mu-07-rhythm) * 1.5);
letter-spacing: -0.014em;
text-wrap: pretty;
}
@media (prefers-reduced-motion: reduce) {
.mu-07__toggle {
transition: none;
}
}
@media (min-width: 56rem) {
.mu-07__stage {
padding-inline: 3rem;
}
}(() => {
const root = document.querySelector('.mu-07');
if (!root) return;
const btn = root.querySelector('#mu-07-grid-btn');
const article = root.querySelector('#mu-07-article');
if (!btn || !article) return;
const setGrid = (on) => {
btn.setAttribute('aria-pressed', String(on));
article.classList.toggle('is-grid', on);
};
const onClick = () => setGrid(btn.getAttribute('aria-pressed') !== 'true');
const onKey = (e) => {
if (e.key === 'g' && !e.metaKey && !e.ctrlKey && !e.altKey) onClick();
};
btn.addEventListener('click', onClick);
root.addEventListener('keydown', onKey);
root.__muCleanup = () => {
btn.removeEventListener('click', onClick);
root.removeEventListener('keydown', onKey);
setGrid(false);
};
})();(() => {
const root = document.querySelector('.mu-07');
if (!root) return;
const btn = root.querySelector('#mu-07-grid-btn');
const article = root.querySelector('#mu-07-article');
if (!btn || !article) return;
const setGrid = (on) => {
btn.setAttribute('aria-pressed', String(on));
article.classList.toggle('is-grid', on);
};
const onClick = () => setGrid(btn.getAttribute('aria-pressed') !== 'true');
const onKey = (e) => {
if (e.key === 'g' && !e.metaKey && !e.ctrlKey && !e.altKey) onClick();
};
btn.addEventListener('click', onClick);
root.addEventListener('keydown', onKey);
root.__muCleanup = () => {
btn.removeEventListener('click', onClick);
root.removeEventListener('keydown', onKey);
setGrid(false);
};
})();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: Vertical Rhythm with a Consistent Baseline
Source: https://codefronts.com/design-styles/css-minimalist-ui/vertical-rhythm-with-a-consistent-baseline/
Line heights and block margins are multiples of one rhythm unit, so text blocks line up down the page. A toggleable overlay draws the grid over live prose so you can see where the rhythm holds and where an image or an input breaks it — which it will, and the demo says what to do about that.
## HTML
```html
<section class="mu-07" aria-labelledby="mu-07-title">
<h2 class="mu-07__sr" id="mu-07-title">Vertical rhythm article</h2>
<div class="mu-07__theme">
<input class="mu-07__theme-input" type="checkbox" id="mu-07-theme">
<label class="mu-07__theme-label" for="mu-07-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-07__sr">Toggle dark theme</span>
</label>
</div>
<div class="mu-07__stage">
<header class="mu-07__head">
<div class="mu-07__controls">
<button class="mu-07__toggle" id="mu-07-grid-btn" type="button" aria-pressed="false">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M3 6h18M3 12h18M3 18h18" fill="none" stroke="currentColor" stroke-width="1.6"/></svg>
Baseline overlay
</button>
<p class="mu-07__unit">Unit <b>1.5rem</b> · 24px at a 16px root</p>
</div>
</header>
<article class="mu-07__article" id="mu-07-article">
<h3 class="mu-07__h3">Rhythm survives real content, grids do not</h3>
<p class="mu-07__lead">Set the rhythm once and the column composes itself. Body copy takes one unit per line, a heading takes two, and the gap between blocks takes one — so the eye reads order it cannot name.</p>
<p>Turn the overlay on and the mechanism becomes obvious: every line of prose sits on a rule, and every block boundary lands on one too. Nothing here is hand-nudged. The whole column is derived from a single custom property, which means the rhythm can be retuned for a different reading size in one edit.</p>
<h4 class="mu-07__h4">Where it stops being free</h4>
<p>The image below is 3:2, which is not a multiple of anything. Its caption is smaller than the body, so its own line height is not a whole unit either. A strict per-line grid would need padding calculated from the image height — a value that changes with the viewport, so the correction cannot be static.</p>
<figure class="mu-07__figure">
<div class="mu-07__frame" role="img" aria-label="Tonal placeholder standing in for an article image, 3 by 2">
<span class="mu-07__frame-note">3:2 image area</span>
</div>
<figcaption class="mu-07__caption">A block on the grid, with an interior that is not. This is the compromise that keeps the system usable.</figcaption>
</figure>
<p>The practical answer is rhythm at the block level. Each block <em>starts</em> on the grid; its interior may not. That is nine-tenths of the perceived order at a tenth of the fragility, and it holds when an editor adds a pull quote, a table, or an embed you did not plan for.</p>
<blockquote class="mu-07__quote">
<p>If the correction has to be recalculated every time the content changes, it is not a system.</p>
</blockquote>
<p>Where a print designer can force every element onto the baseline because the page is fixed, a web page is not. Accepting that is what separates a rhythm that ships from a grid that gets quietly deleted in the third sprint.</p>
</article>
</div>
</section>
```
## CSS
```css
.mu-07 {
--mu-07-rhythm: 1.5rem;
--mu-07-h-lead: 2;
--mu-07-measure: 66ch;
--mu-07-fig-fix: 0px;
--mu-07-serif: ui-serif, Georgia, "Iowan Old Style", "Palatino Linotype", Palatino, serif;
--mu-07-ui: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--mu-07-bg: #faf6ee;
--mu-07-bg: oklch(97.5% 0.012 88);
--mu-07-surface: #fffdf8;
--mu-07-surface: oklch(99.2% 0.006 88);
--mu-07-ink: #1a1712;
--mu-07-ink: oklch(20% 0.01 70);
--mu-07-ink-2: #57503f;
--mu-07-ink-2: oklch(45% 0.018 78);
--mu-07-line: #ddd4c1;
--mu-07-line: oklch(87% 0.02 85);
--mu-07-grid: rgba(139, 74, 20, 0.22);
--mu-07-acc: #8b4a14;
--mu-07-acc: oklch(45% 0.12 55);
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--mu-07-bg);
color: var(--mu-07-ink);
font-family: var(--mu-07-ui);
line-height: 1.5;
-webkit-text-size-adjust: 100%;
}
.mu-07 *,
.mu-07 *::before,
.mu-07 *::after {
box-sizing: border-box;
}
@media (prefers-color-scheme: dark) {
.mu-07 {
--mu-07-bg: #14120e;
--mu-07-bg: oklch(18% 0.009 70);
--mu-07-surface: #1c1914;
--mu-07-surface: oklch(22.5% 0.01 72);
--mu-07-ink: #f4efe4;
--mu-07-ink: oklch(96% 0.012 88);
--mu-07-ink-2: #b3a993;
--mu-07-ink-2: oklch(76% 0.02 85);
--mu-07-line: #3b342a;
--mu-07-line: oklch(33% 0.015 78);
--mu-07-grid: rgba(232, 168, 106, 0.24);
--mu-07-acc: #e8a86a;
--mu-07-acc: oklch(79% 0.11 60);
}
}
.mu-07[data-theme="dark"],
.mu-07:has(.mu-07__theme-input:checked) {
--mu-07-bg: #14120e;
--mu-07-bg: oklch(18% 0.009 70);
--mu-07-surface: #1c1914;
--mu-07-surface: oklch(22.5% 0.01 72);
--mu-07-ink: #f4efe4;
--mu-07-ink: oklch(96% 0.012 88);
--mu-07-ink-2: #b3a993;
--mu-07-ink-2: oklch(76% 0.02 85);
--mu-07-line: #3b342a;
--mu-07-line: oklch(33% 0.015 78);
--mu-07-grid: rgba(232, 168, 106, 0.24);
--mu-07-acc: #e8a86a;
--mu-07-acc: oklch(79% 0.11 60);
}
@media (prefers-color-scheme: light) {
.mu-07:has(.mu-07__theme-input:checked) {
--mu-07-bg: #14120e;
--mu-07-bg: oklch(18% 0.009 70);
--mu-07-surface: #1c1914;
--mu-07-surface: oklch(22.5% 0.01 72);
--mu-07-ink: #f4efe4;
--mu-07-ink: oklch(96% 0.012 88);
--mu-07-ink-2: #b3a993;
--mu-07-ink-2: oklch(76% 0.02 85);
--mu-07-line: #3b342a;
--mu-07-line: oklch(33% 0.015 78);
--mu-07-grid: rgba(232, 168, 106, 0.24);
--mu-07-acc: #e8a86a;
--mu-07-acc: oklch(79% 0.11 60);
}
}
@media (prefers-color-scheme: dark) {
.mu-07:has(.mu-07__theme-input:checked) {
--mu-07-bg: #faf6ee;
--mu-07-bg: oklch(97.5% 0.012 88);
--mu-07-surface: #fffdf8;
--mu-07-surface: oklch(99.2% 0.006 88);
--mu-07-ink: #1a1712;
--mu-07-ink: oklch(20% 0.01 70);
--mu-07-ink-2: #57503f;
--mu-07-ink-2: oklch(45% 0.018 78);
--mu-07-line: #ddd4c1;
--mu-07-line: oklch(87% 0.02 85);
--mu-07-grid: rgba(139, 74, 20, 0.22);
--mu-07-acc: #8b4a14;
--mu-07-acc: oklch(45% 0.12 55);
}
}
.mu-07__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-07__theme {
position: absolute;
inset-block-start: 1rem;
inset-inline-end: 1rem;
z-index: 5;
}
.mu-07__theme-input {
position: absolute;
inset: 0;
inline-size: 44px;
block-size: 44px;
margin: 0;
opacity: 0;
cursor: pointer;
}
.mu-07__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--mu-07-line);
border-radius: 50%;
background: var(--mu-07-surface);
color: var(--mu-07-ink-2);
cursor: pointer;
}
.mu-07__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.mu-07__theme-input:focus-visible + .mu-07__theme-label {
outline: 2px solid var(--mu-07-acc);
outline-offset: 2px;
}
.mu-07__stage {
max-inline-size: 60rem;
margin-inline: auto;
padding: calc(var(--mu-07-rhythm) * 3) 1.5rem calc(var(--mu-07-rhythm) * 4);
display: grid;
gap: calc(var(--mu-07-rhythm) * 2);
grid-template-columns: minmax(0, 1fr);
}
.mu-07__head {
display: grid;
gap: var(--mu-07-rhythm);
max-inline-size: var(--mu-07-measure);
}
.mu-07__controls {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.75rem 1rem;
padding-block-start: 0.5rem;
}
.mu-07__toggle {
display: inline-flex;
align-items: center;
gap: 0.5rem;
min-block-size: 44px;
min-inline-size: 44px;
padding-inline: 1rem;
border: 1px solid var(--mu-07-line);
border-radius: 4px;
background: var(--mu-07-surface);
color: var(--mu-07-ink);
font: inherit;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: border-color 150ms ease, color 150ms ease;
}
.mu-07__toggle svg {
inline-size: 18px;
block-size: 18px;
flex: none;
}
.mu-07__toggle:hover {
border-color: var(--mu-07-acc);
}
.mu-07__toggle:focus-visible {
outline: 2px solid var(--mu-07-acc);
outline-offset: 2px;
}
.mu-07__toggle[aria-pressed="true"] {
border-color: var(--mu-07-acc);
color: var(--mu-07-acc);
}
.mu-07__unit {
margin: 0;
font-size: 0.8125rem;
color: var(--mu-07-ink-2);
font-variant-numeric: tabular-nums;
}
.mu-07__article {
position: relative;
min-inline-size: 0;
max-inline-size: var(--mu-07-measure);
font-family: var(--mu-07-serif);
font-size: 1.0625rem;
line-height: var(--mu-07-rhythm);
}
.mu-07__article.is-grid {
background-image: repeating-linear-gradient(
to bottom,
var(--mu-07-grid) 0,
var(--mu-07-grid) 1px,
transparent 1px,
transparent var(--mu-07-rhythm)
);
background-position: 0 0;
}
.mu-07__article > * {
margin: 0 0 var(--mu-07-rhythm);
min-inline-size: 0;
}
.mu-07__article > :last-child {
margin-block-end: 0;
}
.mu-07__h3 {
font-size: 1.5rem;
line-height: calc(var(--mu-07-rhythm) * var(--mu-07-h-lead));
letter-spacing: -0.018em;
font-weight: 600;
margin-block: 0 var(--mu-07-rhythm);
text-wrap: balance;
}
.mu-07__h4 {
font-family: var(--mu-07-ui);
font-size: 0.8125rem;
line-height: var(--mu-07-rhythm);
letter-spacing: 0.1em;
text-transform: uppercase;
font-weight: 700;
color: var(--mu-07-ink-2);
margin-block: calc(var(--mu-07-rhythm) * 2) var(--mu-07-rhythm);
}
.mu-07__lead {
font-size: 1.1875rem;
line-height: calc(var(--mu-07-rhythm) * 1.5);
text-wrap: pretty;
}
.mu-07__article p {
text-wrap: pretty;
}
.mu-07__figure {
margin-block: calc(var(--mu-07-rhythm) * 2) var(--mu-07-rhythm);
}
.mu-07__frame {
aspect-ratio: 3 / 2;
inline-size: 100%;
display: grid;
place-items: center;
background: var(--mu-07-surface);
border: 1px solid var(--mu-07-line);
border-radius: 4px;
margin-block-end: calc(var(--mu-07-rhythm) - var(--mu-07-fig-fix));
}
.mu-07__frame-note {
font-family: var(--mu-07-ui);
font-size: 0.75rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--mu-07-ink-2);
}
.mu-07__caption {
font-family: var(--mu-07-ui);
font-size: 0.8125rem;
line-height: var(--mu-07-rhythm);
color: var(--mu-07-ink-2);
text-wrap: pretty;
}
.mu-07__quote {
margin-block: calc(var(--mu-07-rhythm) * 2);
margin-inline: 0;
padding-inline-start: 1.25rem;
border-inline-start: 2px solid var(--mu-07-acc);
}
.mu-07__quote p {
margin: 0;
font-size: 1.3125rem;
line-height: calc(var(--mu-07-rhythm) * 1.5);
letter-spacing: -0.014em;
text-wrap: pretty;
}
@media (prefers-reduced-motion: reduce) {
.mu-07__toggle {
transition: none;
}
}
@media (min-width: 56rem) {
.mu-07__stage {
padding-inline: 3rem;
}
}
```
## JavaScript
```js
(() => {
const root = document.querySelector('.mu-07');
if (!root) return;
const btn = root.querySelector('#mu-07-grid-btn');
const article = root.querySelector('#mu-07-article');
if (!btn || !article) return;
const setGrid = (on) => {
btn.setAttribute('aria-pressed', String(on));
article.classList.toggle('is-grid', on);
};
const onClick = () => setGrid(btn.getAttribute('aria-pressed') !== 'true');
const onKey = (e) => {
if (e.key === 'g' && !e.metaKey && !e.ctrlKey && !e.altKey) onClick();
};
btn.addEventListener('click', onClick);
root.addEventListener('keydown', onKey);
root.__muCleanup = () => {
btn.removeEventListener('click', onClick);
root.removeEventListener('keydown', onKey);
setGrid(false);
};
})();
```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: Vertical Rhythm with a Consistent Baseline
Source: https://codefronts.com/design-styles/css-minimalist-ui/vertical-rhythm-with-a-consistent-baseline/
Line heights and block margins are multiples of one rhythm unit, so text blocks line up down the page. A toggleable overlay draws the grid over live prose so you can see where the rhythm holds and where an image or an input breaks it — which it will, and the demo says what to do about that.
## HTML
```html
<section class="mu-07" aria-labelledby="mu-07-title">
<h2 class="mu-07__sr" id="mu-07-title">Vertical rhythm article</h2>
<div class="mu-07__theme">
<input class="mu-07__theme-input" type="checkbox" id="mu-07-theme">
<label class="mu-07__theme-label" for="mu-07-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-07__sr">Toggle dark theme</span>
</label>
</div>
<div class="mu-07__stage">
<header class="mu-07__head">
<div class="mu-07__controls">
<button class="mu-07__toggle" id="mu-07-grid-btn" type="button" aria-pressed="false">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M3 6h18M3 12h18M3 18h18" fill="none" stroke="currentColor" stroke-width="1.6"/></svg>
Baseline overlay
</button>
<p class="mu-07__unit">Unit <b>1.5rem</b> · 24px at a 16px root</p>
</div>
</header>
<article class="mu-07__article" id="mu-07-article">
<h3 class="mu-07__h3">Rhythm survives real content, grids do not</h3>
<p class="mu-07__lead">Set the rhythm once and the column composes itself. Body copy takes one unit per line, a heading takes two, and the gap between blocks takes one — so the eye reads order it cannot name.</p>
<p>Turn the overlay on and the mechanism becomes obvious: every line of prose sits on a rule, and every block boundary lands on one too. Nothing here is hand-nudged. The whole column is derived from a single custom property, which means the rhythm can be retuned for a different reading size in one edit.</p>
<h4 class="mu-07__h4">Where it stops being free</h4>
<p>The image below is 3:2, which is not a multiple of anything. Its caption is smaller than the body, so its own line height is not a whole unit either. A strict per-line grid would need padding calculated from the image height — a value that changes with the viewport, so the correction cannot be static.</p>
<figure class="mu-07__figure">
<div class="mu-07__frame" role="img" aria-label="Tonal placeholder standing in for an article image, 3 by 2">
<span class="mu-07__frame-note">3:2 image area</span>
</div>
<figcaption class="mu-07__caption">A block on the grid, with an interior that is not. This is the compromise that keeps the system usable.</figcaption>
</figure>
<p>The practical answer is rhythm at the block level. Each block <em>starts</em> on the grid; its interior may not. That is nine-tenths of the perceived order at a tenth of the fragility, and it holds when an editor adds a pull quote, a table, or an embed you did not plan for.</p>
<blockquote class="mu-07__quote">
<p>If the correction has to be recalculated every time the content changes, it is not a system.</p>
</blockquote>
<p>Where a print designer can force every element onto the baseline because the page is fixed, a web page is not. Accepting that is what separates a rhythm that ships from a grid that gets quietly deleted in the third sprint.</p>
</article>
</div>
</section>
```
## CSS
```css
.mu-07 {
--mu-07-rhythm: 1.5rem;
--mu-07-h-lead: 2;
--mu-07-measure: 66ch;
--mu-07-fig-fix: 0px;
--mu-07-serif: ui-serif, Georgia, "Iowan Old Style", "Palatino Linotype", Palatino, serif;
--mu-07-ui: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--mu-07-bg: #faf6ee;
--mu-07-bg: oklch(97.5% 0.012 88);
--mu-07-surface: #fffdf8;
--mu-07-surface: oklch(99.2% 0.006 88);
--mu-07-ink: #1a1712;
--mu-07-ink: oklch(20% 0.01 70);
--mu-07-ink-2: #57503f;
--mu-07-ink-2: oklch(45% 0.018 78);
--mu-07-line: #ddd4c1;
--mu-07-line: oklch(87% 0.02 85);
--mu-07-grid: rgba(139, 74, 20, 0.22);
--mu-07-acc: #8b4a14;
--mu-07-acc: oklch(45% 0.12 55);
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
box-sizing: border-box;
background: var(--mu-07-bg);
color: var(--mu-07-ink);
font-family: var(--mu-07-ui);
line-height: 1.5;
-webkit-text-size-adjust: 100%;
}
.mu-07 *,
.mu-07 *::before,
.mu-07 *::after {
box-sizing: border-box;
}
@media (prefers-color-scheme: dark) {
.mu-07 {
--mu-07-bg: #14120e;
--mu-07-bg: oklch(18% 0.009 70);
--mu-07-surface: #1c1914;
--mu-07-surface: oklch(22.5% 0.01 72);
--mu-07-ink: #f4efe4;
--mu-07-ink: oklch(96% 0.012 88);
--mu-07-ink-2: #b3a993;
--mu-07-ink-2: oklch(76% 0.02 85);
--mu-07-line: #3b342a;
--mu-07-line: oklch(33% 0.015 78);
--mu-07-grid: rgba(232, 168, 106, 0.24);
--mu-07-acc: #e8a86a;
--mu-07-acc: oklch(79% 0.11 60);
}
}
.mu-07[data-theme="dark"],
.mu-07:has(.mu-07__theme-input:checked) {
--mu-07-bg: #14120e;
--mu-07-bg: oklch(18% 0.009 70);
--mu-07-surface: #1c1914;
--mu-07-surface: oklch(22.5% 0.01 72);
--mu-07-ink: #f4efe4;
--mu-07-ink: oklch(96% 0.012 88);
--mu-07-ink-2: #b3a993;
--mu-07-ink-2: oklch(76% 0.02 85);
--mu-07-line: #3b342a;
--mu-07-line: oklch(33% 0.015 78);
--mu-07-grid: rgba(232, 168, 106, 0.24);
--mu-07-acc: #e8a86a;
--mu-07-acc: oklch(79% 0.11 60);
}
@media (prefers-color-scheme: light) {
.mu-07:has(.mu-07__theme-input:checked) {
--mu-07-bg: #14120e;
--mu-07-bg: oklch(18% 0.009 70);
--mu-07-surface: #1c1914;
--mu-07-surface: oklch(22.5% 0.01 72);
--mu-07-ink: #f4efe4;
--mu-07-ink: oklch(96% 0.012 88);
--mu-07-ink-2: #b3a993;
--mu-07-ink-2: oklch(76% 0.02 85);
--mu-07-line: #3b342a;
--mu-07-line: oklch(33% 0.015 78);
--mu-07-grid: rgba(232, 168, 106, 0.24);
--mu-07-acc: #e8a86a;
--mu-07-acc: oklch(79% 0.11 60);
}
}
@media (prefers-color-scheme: dark) {
.mu-07:has(.mu-07__theme-input:checked) {
--mu-07-bg: #faf6ee;
--mu-07-bg: oklch(97.5% 0.012 88);
--mu-07-surface: #fffdf8;
--mu-07-surface: oklch(99.2% 0.006 88);
--mu-07-ink: #1a1712;
--mu-07-ink: oklch(20% 0.01 70);
--mu-07-ink-2: #57503f;
--mu-07-ink-2: oklch(45% 0.018 78);
--mu-07-line: #ddd4c1;
--mu-07-line: oklch(87% 0.02 85);
--mu-07-grid: rgba(139, 74, 20, 0.22);
--mu-07-acc: #8b4a14;
--mu-07-acc: oklch(45% 0.12 55);
}
}
.mu-07__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-07__theme {
position: absolute;
inset-block-start: 1rem;
inset-inline-end: 1rem;
z-index: 5;
}
.mu-07__theme-input {
position: absolute;
inset: 0;
inline-size: 44px;
block-size: 44px;
margin: 0;
opacity: 0;
cursor: pointer;
}
.mu-07__theme-label {
display: grid;
place-items: center;
inline-size: 44px;
block-size: 44px;
border: 1px solid var(--mu-07-line);
border-radius: 50%;
background: var(--mu-07-surface);
color: var(--mu-07-ink-2);
cursor: pointer;
}
.mu-07__theme-label svg {
inline-size: 20px;
block-size: 20px;
}
.mu-07__theme-input:focus-visible + .mu-07__theme-label {
outline: 2px solid var(--mu-07-acc);
outline-offset: 2px;
}
.mu-07__stage {
max-inline-size: 60rem;
margin-inline: auto;
padding: calc(var(--mu-07-rhythm) * 3) 1.5rem calc(var(--mu-07-rhythm) * 4);
display: grid;
gap: calc(var(--mu-07-rhythm) * 2);
grid-template-columns: minmax(0, 1fr);
}
.mu-07__head {
display: grid;
gap: var(--mu-07-rhythm);
max-inline-size: var(--mu-07-measure);
}
.mu-07__controls {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.75rem 1rem;
padding-block-start: 0.5rem;
}
.mu-07__toggle {
display: inline-flex;
align-items: center;
gap: 0.5rem;
min-block-size: 44px;
min-inline-size: 44px;
padding-inline: 1rem;
border: 1px solid var(--mu-07-line);
border-radius: 4px;
background: var(--mu-07-surface);
color: var(--mu-07-ink);
font: inherit;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: border-color 150ms ease, color 150ms ease;
}
.mu-07__toggle svg {
inline-size: 18px;
block-size: 18px;
flex: none;
}
.mu-07__toggle:hover {
border-color: var(--mu-07-acc);
}
.mu-07__toggle:focus-visible {
outline: 2px solid var(--mu-07-acc);
outline-offset: 2px;
}
.mu-07__toggle[aria-pressed="true"] {
border-color: var(--mu-07-acc);
color: var(--mu-07-acc);
}
.mu-07__unit {
margin: 0;
font-size: 0.8125rem;
color: var(--mu-07-ink-2);
font-variant-numeric: tabular-nums;
}
.mu-07__article {
position: relative;
min-inline-size: 0;
max-inline-size: var(--mu-07-measure);
font-family: var(--mu-07-serif);
font-size: 1.0625rem;
line-height: var(--mu-07-rhythm);
}
.mu-07__article.is-grid {
background-image: repeating-linear-gradient(
to bottom,
var(--mu-07-grid) 0,
var(--mu-07-grid) 1px,
transparent 1px,
transparent var(--mu-07-rhythm)
);
background-position: 0 0;
}
.mu-07__article > * {
margin: 0 0 var(--mu-07-rhythm);
min-inline-size: 0;
}
.mu-07__article > :last-child {
margin-block-end: 0;
}
.mu-07__h3 {
font-size: 1.5rem;
line-height: calc(var(--mu-07-rhythm) * var(--mu-07-h-lead));
letter-spacing: -0.018em;
font-weight: 600;
margin-block: 0 var(--mu-07-rhythm);
text-wrap: balance;
}
.mu-07__h4 {
font-family: var(--mu-07-ui);
font-size: 0.8125rem;
line-height: var(--mu-07-rhythm);
letter-spacing: 0.1em;
text-transform: uppercase;
font-weight: 700;
color: var(--mu-07-ink-2);
margin-block: calc(var(--mu-07-rhythm) * 2) var(--mu-07-rhythm);
}
.mu-07__lead {
font-size: 1.1875rem;
line-height: calc(var(--mu-07-rhythm) * 1.5);
text-wrap: pretty;
}
.mu-07__article p {
text-wrap: pretty;
}
.mu-07__figure {
margin-block: calc(var(--mu-07-rhythm) * 2) var(--mu-07-rhythm);
}
.mu-07__frame {
aspect-ratio: 3 / 2;
inline-size: 100%;
display: grid;
place-items: center;
background: var(--mu-07-surface);
border: 1px solid var(--mu-07-line);
border-radius: 4px;
margin-block-end: calc(var(--mu-07-rhythm) - var(--mu-07-fig-fix));
}
.mu-07__frame-note {
font-family: var(--mu-07-ui);
font-size: 0.75rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--mu-07-ink-2);
}
.mu-07__caption {
font-family: var(--mu-07-ui);
font-size: 0.8125rem;
line-height: var(--mu-07-rhythm);
color: var(--mu-07-ink-2);
text-wrap: pretty;
}
.mu-07__quote {
margin-block: calc(var(--mu-07-rhythm) * 2);
margin-inline: 0;
padding-inline-start: 1.25rem;
border-inline-start: 2px solid var(--mu-07-acc);
}
.mu-07__quote p {
margin: 0;
font-size: 1.3125rem;
line-height: calc(var(--mu-07-rhythm) * 1.5);
letter-spacing: -0.014em;
text-wrap: pretty;
}
@media (prefers-reduced-motion: reduce) {
.mu-07__toggle {
transition: none;
}
}
@media (min-width: 56rem) {
.mu-07__stage {
padding-inline: 3rem;
}
}
```
## JavaScript
```js
(() => {
const root = document.querySelector('.mu-07');
if (!root) return;
const btn = root.querySelector('#mu-07-grid-btn');
const article = root.querySelector('#mu-07-article');
if (!btn || !article) return;
const setGrid = (on) => {
btn.setAttribute('aria-pressed', String(on));
article.classList.toggle('is-grid', on);
};
const onClick = () => setGrid(btn.getAttribute('aria-pressed') !== 'true');
const onKey = (e) => {
if (e.key === 'g' && !e.metaKey && !e.ctrlKey && !e.altKey) onClick();
};
btn.addEventListener('click', onClick);
root.addEventListener('keydown', onKey);
root.__muCleanup = () => {
btn.removeEventListener('click', onClick);
root.removeEventListener('keydown', onKey);
setGrid(false);
};
})();
```How this works
Rhythm is what makes long-form text feel calm without any decoration. Set a rhythm unit — here 1.5rem — then make every line height and every block margin a multiple of it. Body copy at line-height: 1.5 on a 1rem base occupies exactly one unit per line, headings take two, and the gaps between blocks take one or two. The result is that the left column and the right column of a two-column layout land on the same lines, and a reader perceives order without being able to name it.
Draw the grid while you tune it. The overlay here is a repeating-linear-gradient with a 1px line every rhythm unit, toggled by a single button. This is a development tool, not a shipped feature — but it is the only reliable way to find the block that is 4px off, because the eye notices the disruption long before it can locate the cause.
Margin collapse is your ally, not a bug. Adjacent block margins collapse to the larger of the two, which is precisely what a rhythm system wants: a heading with margin-block: 2 units 1 unit after a paragraph with margin-block-end: 1 unit yields two units, not three. Switch to a flex or grid container with gap and collapse stops happening — which is fine, but you then own every gap explicitly. Pick one model per region and stay in it.
The honest limit: strict baseline grids fight real content. An image is whatever height it is. A code block has its own line height. An input is 44px because a finger needs 44px. Forcing all of those onto a per-line grid means padding hacks that break the moment content changes. The practical answer is rhythm at the block level: let each block start on the grid, and accept that its interior may not. That gets you nine-tenths of the perceived order for a tenth of the fragility — and if you need the last tenth, you are building a print layout, not a web page.
Make it yours
- Change
--mu-07-rhythmto1.375remfor a tighter column, or1.75remfor a more open editorial feel. - Set the measure with
--mu-07-measure; 62ch to 72ch is the comfortable band for this serif stack. - Change the overlay colour and opacity via
--mu-07-gridto suit a darker ground. - Switch heading rhythm from 2 units to 3 by editing the
--mu-07-h-leadmultiplier. - Delete the toggle button and the script entirely to ship the rhythm without the development overlay.
- Set
--mu-07-fig-fixto the residual value your images need, keeping the correction in one place.
Gotchas — read before shipping
- A baseline grid abandoned the moment a real image appears is worse than no grid, because half the page now looks deliberate and half looks broken. Commit to block-level rhythm instead.
- Mixing collapsed margins and flex
gapin the same column silently doubles some gaps. Pick one spacing model per region. - Setting
line-heightinpxbreaks the rhythm as soon as the user changes their font size — the unit must be relative. - Shipping the overlay to production is a real mistake; it is a gradient on the reading surface and it reduces text contrast. Keep it behind a development flag.
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 rhythm — line-height multiples, repeating-linear-gradient and margin collapse all predate 2015. The lh unit is deliberately not used in the shipped CSS (it would raise the Firefox floor to 120 with no benefit here); the rhythm is expressed in rem multiples instead. The overlay script is 18 lines of vanilla ES2024 with a cleanup handle, and the demo reads correctly with the script blocked — the overlay simply never appears.