← buildbench

The === bar kept colliding with the H1

The terminal theme wants every H1 to sit on a row of = characters, the way an underlined heading looks in a plain README. The cheap way to do that in CSS is a pseudo-element absolutely positioned at the bottom of the H1’s padded box, holding a long string of equals signs that gets clipped by overflow: hidden.

h1 {
  position: relative;
  padding: 10px 0;
  overflow: hidden;
}
h1::after {
  content: "================================================================";
  position: absolute;
  bottom: 4px;
  inset-inline-start: 0;
  color: var(--muted);
}

First load: the = row was sitting inside the word “buildbench”. The descenders weren’t even close — the bar was riding through the x-height. I bumped the H1’s padding-bottom so the heading text would sit higher and the bar would have its own lane. Looked fine on the index.

Then I tightened the reading column from 50rem to 44rem and re-loaded a post. Different H1, longer title. It wrapped to two lines. The bar was back inside the second line of the heading.

I’d fixed the same bug twice without understanding it.

The thing I had wrong: I was thinking of ::after as a single-pixel rule sitting at bottom: 4px. It isn’t. The pseudo-element has content made of real characters, which means it has a font, a font-size, and a line-height. It occupies vertical space equal to its own line box — call it ~22px at body line-height. bottom: 4px puts the bottom edge of that 22px box 4px above the H1’s bottom edge. The visible glyphs (=) sit roughly in the middle of the box. So the rendered bar is something like 11px above the H1’s bottom edge, and if the H1’s text descends past that point, they overlap.

Padding the H1 from above doesn’t help. What matters is padding-bottom — and not just enough to clear the last text line, but enough to clear the last text line plus the height of the pseudo-element’s own line box. With one-line titles I’d accidentally given it enough room. With two-line titles I hadn’t.

The fix that finally held:

h1 {
  position: relative;
  padding: var(--space) 0 calc(var(--lh-tight) * 1em + var(--space));
  overflow: hidden;
  line-height: var(--lh-tight);
}
h1::after {
  content: "================================================================";
  position: absolute;
  bottom: var(--space);
  inset-inline-start: 0;
  line-height: var(--lh-tight);
  color: var(--muted);
}

padding-bottom now reserves a full line of --lh-tight for the bar, plus the same gutter the bar uses as its bottom offset. Fix it once, it stays fixed regardless of how many lines the title wraps to.

The lesson is small and the kind of thing I’ll forget again: a pseudo-element with text content is a line box, not a hairline. If you anchor it to an edge with bottom, you owe its parent enough padding to swallow the line box, not just the gap to the bar’s visual center. The first symptom — collision with single-line text — was hiding the real shape of the bug. The reflow at a narrower column made it admit what it actually needed.