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.

Home layout, featured article on top, 3-column grid below. Simple, and most of the work is making it stay that simple.

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:

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

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.