Product Design
A Template Needs Its Own Grammar
June 29, 2026
Vowframes launched after I stopped delaying it for one more template. Six weeks later, I was building the fifth.
Ivory Bloom had a reason to exist: it was meant to test whether the template engine could produce a genuinely different wedding site. The first preview was pretty. It was also one of Vowframes’ existing templates wearing ivory.
The reference came from a set of AI-generated product images with a specific wedding-site design inside them. The first pass picked up the soft palette and general mood. It kept familiar rounded buttons, familiar sections, and too much of the structure we already had.
The result looked coordinated. It did not feel new.
A palette cannot carry the template
Template systems naturally pull every design toward the same shape. Shared components save time. Shared data keeps editing predictable. Shared sections make the renderer easier to maintain.
They also encourage the builder to treat templates as token sets: change the colors, swap the font, adjust the border radius, and ship another option.
Couples choosing a wedding site notice the composition first. One design might feel editorial and spacious. Another may be festive and dense. A third may rely on photography, ornament, or a strong schedule. Those differences come from hierarchy, proportion, image treatment, section order, and rhythm.
Ivory Bloom needed its own rules for those decisions.
We went back to the reference images and traced how the page moved. Where does the eye stop? Which sections feel ceremonial? Where can a countdown live without turning into a dashboard widget? How do the details pages preserve the same character as the homepage?
That pass produced a design worth adding to the engine.
Static HTML made the argument clearer
We built the template as a standalone HTML page before connecting it to Vowframes data.
That separated two kinds of failure. If the standalone page looked generic, the design needed work. If it broke after integration, the template engine or shared components were carrying assumptions from the older templates.
The separation helped immediately. We could judge the typography, spacing, photography, navigation, and optional widget area without debugging Django context or template variables at the same time.
Once the page held together, we connected real wedding data and rendered every supported page. The countdown exposed another problem: a reusable component can be functionally correct and still look imported from a different product. It needed to follow Ivory Bloom’s spacing and visual weight while keeping the same underlying behavior.
The same check applied to RSVP, schedule, gallery, and story sections. A good homepage only covered one route. Every shared feature had to speak Ivory Bloom’s visual language.
Shared components need room to change shape
The fifth template became a test of the engine.
Some components accepted content and left the layout to the template. Those adapted well. Others carried fixed spacing, button treatments, or assumptions about the surrounding section. They made every template converge.
A useful template engine shares semantics and behavior. It gives each design control over composition.
The countdown still counts down. The RSVP form still collects the same fields. The gallery still receives the same images. Their containers, hierarchy, and relationship to the page can change. That boundary keeps the product maintainable without forcing every wedding to look like it came from the same site builder.
Optional widgets need the same treatment. Ivory Bloom reserved a real section for them instead of dropping a countdown wherever there happened to be space. The template decided how optional content belongs on the page.
The demo needed comparison, not screenshots alone
After Ivory Bloom shipped, we added live template switching to the Vowframes demo.
The switcher only appears on localhost and demo.vowframes.com. A small floating control lets someone move between templates and return to the original view. The page transition uses a restrained animation so the change feels deliberate without hiding the layout.
This did more than help sales. Switching the same wedding between templates became a design test. Weak differences showed up immediately. If two options felt identical after the hero, the engine had preserved too much structure. If a component broke only on one template, the comparison exposed it quickly.
We also updated the marketing pages with the new template and its screenshots. A template hidden inside the editor does little for someone deciding whether the product fits their wedding.
Ivory Bloom started as a visual option. It ended up improving the boundary between shared product behavior and template-specific design. The fifth template showed us where the engine had confused a theme with a template.
Building with AI: I chose the reference, composition rules, and shared-component boundary; AI translated much of that direction into code. The first pass copied the palette more faithfully than the design grammar, then integration preserved a hardcoded root class and browser-dependent dates. Static comparison, exhaustive template dispatch, fixed locale formatting, and route-by-route review turned those plausible shortcuts into visible failures.
Where integration broke
The prototype became IvoryBloomLayout, IvoryBloomPage, and dedicated hero, events, RSVP, info, and countdown variants. The public manifest gained the ivory_bloom template ID, its demo slug, mood preset, and separate hero/mobile thumbnails.
The first integrated preview looked broken even though the components were correct. TemplateDemoRenderer had hardcoded the wrapper class pure-elegance-template, while every new rule was scoped under .ivory-bloom-template. Fixing the class dispatch made the whole theme appear. That is a useful template-engine test: a new design should not quietly inherit another template's root identity.
Two browser errors then exposed shared-component assumptions. Both countdown variants initialized from Date.now() during server rendering, so the seconds differed at hydration. Event sections used toLocaleString(undefined), letting Node and the browser choose different locales. Initializing the clock after mount and pinning event formatting to en-IN removed both mismatches across all five templates.
I opened the demo at desktop and mobile widths, then walked through events, event detail, gallery, registry, profile, and RSVP. Marketing cards were switched from borrowed Royal Photo art to real ivory_bloom/thumb-hero.jpg and thumb-mobile.jpg captures.
Ivory Bloom became a complete route family and a truthful preview rather than a homepage skin. New templates now have to enter through exhaustive TemplateId dispatch, use fixed-locale rendering and hydration-safe clocks, and survive every shared page before their screenshots reach the marketing site.