A client site I was working on had one heading on the about page that made me scratch my head. The client spotted it first: in the top dark section, the main title was supposed to sit large and centered, but all you could see was an empty dark background. The heading was not missing. It was in the DOM, it had real dimensions (223 by 81 pixels), the text was intact. Your eyes just caught nothing.
The symptom
My first reflex accused the usual suspects: opacity: 0, something stacked on top, or visibility: hidden. I inspected the element in DevTools and crossed them off one by one. Opacity 1. Visibility visible. No overlay. Then I opened the Computed panel and found the culprit at the very top: color: rgb(58, 58, 58). Dark gray. And that section's background? Nearly black. Dark-on-dark. The text was there, its color just blended perfectly into the background until nothing showed.
Because it was a color problem, I assumed at first this was one stray heading. But once I checked the other dark sections, the pattern repeated exactly: differentiator, story, newsletter, contact, all the way down to the cart drawer and the account pages. Every heading inside a dark-themed section carried the same gray. The identical bug, latent in 10-plus places. Luck had saved the site so far: most of those dark headings were short or sat behind an image, so nobody had complained. This one on the about page was large and alone in the center, so it finally got caught.
The root cause: an explicit color breaks inheritance
Here is where it gets interesting. The dark sections in this theme do not set text colors one by one. They flip the whole scheme with a single rule on the container:
.section-dark {
background: var(--color-foreground); /* dark */
color: var(--color-background); /* light */
}The trick is clean: set color once on the container, and every piece of text inside turns light through inheritance. Paragraphs, span, div, links, all obey. Except headings.
Why are headings the odd ones out? Because in critical.css, deep at line 89, there is a global rule:
h1, h2, h3, h4, h5, h6 {
color: var(--color-fg);
}This rule sets the heading color explicitly to the default dark foreground. And this is the key: the moment a property has an explicit value on an element, that element stops inheriting the property from its ancestor. The heading color is no longer left to inheritance, it is pinned. So when a dark section flips its container color to light, paragraphs follow because they were waiting on that inheritance. But the headings already own a color: var(--color-fg), so they ignore the light color coming from the container and stay dark. The result is dark-on-dark.
Why this is not a specificity war
This is the part I want to nail down, because it is easy to confuse with a different bug I have written about. This is not a specificity war. There are not two selectors fighting where the more specific one wins. That global h1-h6 rule wins outright, unopposed, and its winning is the problem. It sets an explicit color that cuts the inheritance chain before the section color can reach the heading. Bumping the section rule's specificity or piling on !important would only spread the workaround everywhere. The root cause is not who wins, but that headings were given an explicit color at all.
The fix
The fix is one line, and it sweeps all 10-plus sections at once:
h1, h2, h3, h4, h5, h6 {
color: inherit;
}inherit returns the headings to the default cascade behavior: take color from the parent. In a light section, the parent carries the dark foreground, so the heading stays dark as it should. In a dark section, the parent has already been flipped light, so the heading follows and turns light. One keyword, and every heading across the site reconnects to the color scheme of the section it lives in. I never had to touch those 10-plus sections one by one.
After the change, the main heading popped in clean white against the dark background. I also swept the light sections to confirm nothing regressed, and every heading there stayed dark. inherit does not force any color; it hands the decision back to the section, and the section already knew what its color should be.
What I took away
- A heading that seems to vanish but has real dimensions in the DOM is almost always a color problem, not a layout one. Check
colorin the Computed panel before you chasedisplayorz-index. - A property with an explicit value stops inheriting. A global
colorrule onh1-h6silently cuts headings off from the scheme a section flips. - The pattern of flipping a scheme with
coloron a container only works for elements that inherit. Elements with their own explicit color are immune to the flip. color: inheriton headings puts them back in the inheritance chain, so one change fixes every dark section at once.- This differs from a specificity war: no selector loses here. The problem is the rule that wins outright and sets a color it should never have set.
