Dogfooding HERB in the Design System
HERB has been up and running for two days and already I want to use it everywhere. The question was: where do we start dogfooding it?
Sirius2 was the obvious answer. We have fifteen-plus components built in ERB and Slim, a documentation app that renders examples, and a constant stream of new components being added. It’s the perfect proving ground. Complex enough to stress-test the language, controlled enough that a rough edge is a quick fix rather than a production incident.
Today we switched Sirius2 over to HERB, and the feedback loop is already paying off in both directions. HERB needs real-world usage to find rough edges in the DSL. Sirius2 needs a more enjoyable way to build and iterate on components. Using one to build the other gives us both.
What HERB looks like in practice
Instead of the ERB ceremony:
<%= render Sirius2::Button.new(variant: :primary, tone: :danger) do %>
Delete
<% end %>
You write:
<Button variant="primary" tone="danger">
Delete
</Button>
Slots use colon syntax:
<Card title="Settings">
<Card:Section title="General">content here</Card:Section>
</Card>
It reads like HTML because it mostly is HTML. The uppercase tag names are the only signal that something is a component. The component tree is visible in the markup, so you can glance at a template and see the structure without mentally parsing Ruby.
What we’re learning
Using HERB to write Sirius2’s documentation pages and component examples is already surfacing things we wouldn’t have found in isolation.
The slot syntax needed refinement. Our first attempt required braces around the parent name, which was noisy and easy to get wrong. We simplified it to <Card:Section> which reads naturally and matches how you’d think about it.
Error messages from the tokenizer needed work. When you make a syntax mistake in HERB, the error should point you to the right line and explain what went wrong. Our first iteration was showing internal tokenizer state names, which is meaningless if you’re a template author, not a parser developer.
The way HERB handles attribute binding, class={expression} with single curly braces, felt right immediately. Printing expressions with double curlies ({{value}}) and control flow with {% %} also landed well. These are the parts of the DSL we don’t need to touch.
The other direction
The symbiosis goes both ways. HERB makes developing Sirius2 noticeably faster and more enjoyable. Writing component documentation in HERB instead of ERB means the examples are clean and readable. Adding a new component variant, say a new Button tone, is a one-line template change that reads like what it renders.
Before HERB, I’d write a component in Ruby, write the template in Slim or ERB, write the documentation example in ERB, and mentally translate between three different representations of the same thing. Now the template and the documentation are both HERB, and they both look like the HTML they produce.
We’re not ready to push HERB to our product codebases yet. It needs more time cooking in Sirius2 first. But every component we write, every documentation page we build, every edge case we hit is making the language more solid. When it’s ready for production use, it’ll have been battle-tested against sixty-plus components and hundreds of template files.
That’s the whole point of dogfooding. Ship it to yourself first, where the cost of a rough edge is a quick fix, not a broken product.