Skip to content
All writing05/05 · Essay
Nov 2025·6 min read·Visual Builders · Architecture · Design Systems

Make the builder a function of the data.

The visual no-code editor I shipped is config, not UI. Here's why that matters at scale.

elements.config.ts

{ label: "Video card",dragType: "zone",validZones: ["slide"],settings: ["aspect"],}
renders

Catalog · Video card

Video card
data → ui

ReelBuilder is a visual no-code editor. Merchants drag elements between zones, configure them in a sidebar, and see the result in a live preview. The version that shipped supports 24 configurable elements across 4 widget types and 5 templates.

If you'd asked me three years ago to build that, I would have hardcoded each element's UI. Drag-and-drop with twenty-four hardcoded element configurations would have been a six-month project, and adding the twenty-fifth element would have been another two-week project after that.

The version we shipped takes a different approach: every element is a typed config entry, and the editor is a function of those entries.

The naive approach

Most visual builders I've torn down go like this: the settings panel is a switch statement on element type. ButtonSettings, ImageSettings, TextSettings — twenty more.

This works at five elements. It collapses at twenty. The reason isn't the conditional — it's the fork. Each settings component drifts. They use slightly different validation patterns, slightly different field labels, slightly different hover states. By element ten, the settings panel feels different across element types because, mechanically, it is different across element types.

A second pattern fails the same way: drag zones that aren't aware of element type. Drop validation happens at the runtime layer, which means the UI lets you drag anywhere and tells you “invalid” after you let go. Every competitor visual builder I tested at the time shipped this bug.

What config-as-data buys you

The version we shipped declares every element as a typed config: label, icon, drag-type discriminator, valid zones, settings keys. The editor reads this catalog at boot and generates the layers tree, the drag-zone validation, and the settings panel sections from the same source.

There is no ButtonSettings component. There's a Settings component that takes settings: string[] and renders the matching field components from a registry.

This buys three things:

01 · New elements ship in hours, not weeks

Adding the twenty-fifth element is editing one config file. The editor doesn't change. The drag system doesn't change. The settings panel doesn't change. This is the difference between “two-week sprint” and “afternoon's work.”

02 · Type-validated drop zones

Each element declares its valid zones. The drag system checks at drag-start, not drop. Invalid drags don't silently fail; they don't start. Merchants never wonder why their drag broke — the UI tells them upfront which zones accept which element.

03 · Per-widget adaptation without forks

Four widget types share one editor. Story widgets hide videoCard / template / productImage. Floating widgets hide videoCard / template. Product Page widgets hide template / videoShape. None of these are forks. They're declarative elementOverrides maps that read the same catalog and filter visibility.

The trade-off

This isn't free. Config-as-data demands more architecture upfront and more discipline forever. Specifically:

  • Every new field type needs a registry entry. Adding a new validation rule isn't “just write the validation”; it's “extend the registry, write the validator, document the contract.”
  • Generic settings components are harder to make polished than bespoke ones. We spent more time on hover states, focus management, and field-error semantics than we would have for any single hardcoded settings panel.
  • Catalog drift is a real failure mode. Every config entry must be reviewed; one inconsistent entry creates an inconsistent UI.

In exchange: a 24-element editor that 3 engineers maintain, ships new elements weekly, and has zero drag-zone bugs.

When this approach matters

Config-as-data is over-engineering for a 5-element builder. The line, in my experience: somewhere around 8-10 elements, hardcoded UI starts costing more than the abstraction. By 15, the abstraction is paying for itself. By 20+, the hardcoded version is unmaintainable and the team that built it is asking why every new element takes two weeks.

The senior decision isn't “always use abstractions.” It's recognizing the curve: at what scale does the abstraction become cheaper than the alternative? For visual builders, the curve crosses earlier than most teams expect.

◇ ◇ ◇

This isn't just an engineering decision. The architecture is the design — it determines what merchants can do, how confidently they can do it, and how fast the team can grow the catalog. A designer who can't reason about the catalog is a designer who builds editors that don't scale.

◇ ◇ ◇

Want the receipts? The case studies show this thinking applied to shipped products.