Alternating dark and light sections without breaking the page
Switching a landing page between dark and light sections adds rhythm, and breaks headings, links and buttons that assumed one ground. How to make the flip safe.
Dark and light sections are one of the oldest ways to give a long page rhythm, and one of the easiest to get subtly wrong. The classic bug: the section turns dark, and the heading stays black on black, or the link keeps a colour chosen for white paper.
Scope the tokens, not the components
Components should never know which section they are in. They read semantic variables like --text and --surface, and each section sets those variables for everything inside it.
:root, .on-paper {
--ground: var(--paper);
--text: var(--ink);
--text-muted: var(--ink-60);
--line: oklch(0% 0 0 / 0.1);
}
.on-coal {
--ground: var(--coal);
--text: var(--paper);
--text-muted: var(--paper-70);
--line: oklch(100% 0 0 / 0.12);
}
section { background: var(--ground); color: var(--text); }
h2 { color: var(--text); }Because custom properties inherit, a card inside .on-coal gets the dark values without a single extra class. A heading that sets its own fixed colour is the one that breaks, which is why no component should.
What a dark band is for
- A photograph, which almost always looks better on a dark ground.
- A proof section: numbers, a quote, a demo.
- The final call to action, so the end of the page reads as an end.
Each of those is a change of mode for the reader. Alternating for its own sake is not, and a page that goes light, dark, light, dark down its length feels like it is flashing.
The joins
A hard edge between paper and coal is fine and often best on an editorial page. On a softer brand, a shaped divider can carry the change. We described one in a wave divider in CSS. Either way, keep the vertical padding the same on both sides of the join, or the dark section looks heavier than it is.
Check every component on both grounds
Buttons, links, focus rings, form fields and badges. The focus ring is the one most often forgotten, and it is the one keyboard users need. Contrast in a dark UI lists what to measure.
Editorial Ledger UI treats paper and coal as semantic tokens that flip per section, and it names the heading bug explicitly, so Claude sets section tokens rather than colouring headings by hand.
Questions
Why does my heading stay black on a dark section?
Because its colour was set to a fixed dark value, or inherited from a variable that the dark section never redefined. Components should read semantic variables that each section sets.
How often should a page alternate dark and light?
Rarely. One or two dark bands on a light page, for a photo, a proof section or the closing call to action, give rhythm. Alternating every section makes the page flicker as it scrolls.
Do I need separate components for dark sections?
No. Scope the variables to the section, with a class like .on-dark, and the same components pick up the right colours.