H2 Labs is a blog about how the work actually goes, so it felt dishonest not to write up how the blog itself came together. Nothing here is a clever architecture. It's a small static site served by a servo app. What's interesting is the iteration, the number of small things that had to be wrong before the layout felt right.
The first pass was wrong
I started by asking for "a serious engineering-blog layout." What came back was a generic tech-blog skeleton: huge hero, gradient overlays, flashy category cards with emoji icons, marketing copy in the header. Every surface signaled product landing page. None of it signaled engineers writing things down.
The feedback that turned it around was short: "this feels commercial, not technical, fonts are way too big." That one sentence saved an afternoon of smaller corrections. Layout density does more to make a page feel like a tech blog than any specific visual treatment. Shrinking the headlines, tightening the line-height, and removing the hero section changed the feel completely before I touched a single color.
Real images matter more than real-sounding content
For a long stretch I was generating SVG illustrations to stand in for article thumbnails. They were fine individually and lifeless together. Once the real thumbnails from the reference blog replaced them, the grid felt like a tech blog, full stop. The takeaway isn't "steal images", it's that a grid of illustrations has a very different visual rhythm from a grid of photographs, and you can't tune copy or spacing around a placeholder forever.
Theming through servo's variables
The servo host injects a set of CSS custom properties on :root: --bg, --fg, --fg2, --fg3, --surface, --border, --hover, --accent. Every color in the blog resolves through those. Switching the servo theme changes the whole site without reloading.
Getting this right just means not hard-coding colors, which is easier said than done, the first draft had a brand-red baked in, and another half-dozen hex values sprinkled through button and card rules. Ripping them out and routing everything through the servo variables took a pass dedicated entirely to that.
Deduplicating the chrome
Every page had its own copy of the header, nav, and footer. The blog grew from one page to thirteen, and the drift started immediately: a nav item removed here, a logo font-size changed there. The fix was two small shared files and a pair of web components:
class H2Header extends HTMLElement {
connectedCallback() {
const section = this.getAttribute('section') || 'Labs';
const active = this.getAttribute('active') || '';
const nav = NAV_ITEMS.map(n =>
`<a href="${n.href}"${n.key === active ? ' class="active"' : ''}>${n.label}</a>`
).join('');
this.innerHTML = `
<header class="header">
... logo + nav ...
</header>
`;
}
}
Light DOM (plain innerHTML), not shadow DOM, so the servo theme CSS still applies. One file owns the nav. Every page just drops in <h2-header section="Labs" active="home"></h2-header>.
Small things, disproportionate effort
Three late bugs took longer to diagnose than the whole first draft:
- The nav menu shifted 1px up on one page. Cause: the About page set
body { line-height: 1.7 }; the others inherited1.6from the shared stylesheet. The nav links inherited whichever one was active, changing the header row height by one pixel. Pinningline-heighton.nav afixed it. - The header popped in after page load. Cause: web components have zero height until JS runs. Fix: reserve the space in CSS with
h2-header { min-height: 76px }and load the component script synchronously so it's registered before the body is parsed. - The footer drifted 1px on scroll on short pages. Cause: a
min-height: calc(100vh + 1px)hack that was supposed to guarantee an always-visible scrollbar. The pixel of extra content was real, and scrolling it moved the footer. Removed the hack, leaned onoverflow-y: scrollplus explicit::-webkit-scrollbarstyling.
None of these are interesting in isolation. Together they're the reminder that the last 10% of a layout is mostly pixel arithmetic.
What's still rough
- No RSS feed yet. Every tech blog should have one.
- No authoring pipeline, posts are hand-written HTML. Markdown with a small build step is the obvious next move.
- The article pages share a stylesheet but still duplicate the header/footer markup through the web components. That's fine, but a trivial template would remove it entirely.
The lesson
The fastest way to turn a generic layout into a tech blog is to make it denser. The fastest way to notice you've turned it into something else is to ship it to someone honest.