<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alexis Bonneau</title>
    <description>The latest articles on DEV Community by Alexis Bonneau (@alexis_bonneau_101adc714c).</description>
    <link>https://dev.to/alexis_bonneau_101adc714c</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3923158%2Fd9af258b-ca7a-4140-b77a-a5549c698d22.png</url>
      <title>DEV Community: Alexis Bonneau</title>
      <link>https://dev.to/alexis_bonneau_101adc714c</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alexis_bonneau_101adc714c"/>
    <language>en</language>
    <item>
      <title>I built a no-code form builder for Blazor because my forms changed faster than my release cycle</title>
      <dc:creator>Alexis Bonneau</dc:creator>
      <pubDate>Tue, 02 Jun 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/alexis_bonneau_101adc714c/i-built-a-no-code-form-builder-for-blazor-because-my-forms-changed-faster-than-my-release-cycle-477n</link>
      <guid>https://dev.to/alexis_bonneau_101adc714c/i-built-a-no-code-form-builder-for-blazor-because-my-forms-changed-faster-than-my-release-cycle-477n</guid>
      <description>&lt;p&gt;Every time a client asked for a new field, a new section, or a slightly different layout, the process was the same. Update the model. Update the UI. Maybe add a column to the database. Push a deployment. Review, staging, production. For something that should take five minutes.&lt;/p&gt;

&lt;p&gt;If you work on a multi-tenant SaaS it gets worse. Different tenants want different fields and different layouts. You either build a rigid config system that never quite covers the next request, or you end up with a bloated form component that tries to handle every case at once.&lt;/p&gt;

&lt;p&gt;I looked for something off the shelf. Most options were JS-heavy and didn't sit cleanly inside a Blazor app. Others needed a separate hosted backend. None of them were just a self-contained .NET package I could drop in and own.&lt;/p&gt;

&lt;p&gt;So I built one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;The goal was simple. Let the right users (admins, or whoever has the rights) design their own forms directly inside the app. No code changes, no deployment.&lt;/p&gt;

&lt;p&gt;The part I cared about most: it had to stay &lt;strong&gt;type-safe by default&lt;/strong&gt;. You start from a plain C# model, your own class, with your own data annotations. Formaze generates the form from it, and admins arrange the layout, rename labels, group fields and reorder them live. Fields that map to your model bind straight back to your C# properties, with data-annotation validation. No opaque JSON blob you have to map by hand on both ends.&lt;/p&gt;

&lt;p&gt;And when an admin needs a field that isn't on your model at all, they can add a &lt;strong&gt;dynamic&lt;/strong&gt; one. These are kept as typed key/value entries on the side, so the editor never forces you to round-trip through the model first.&lt;/p&gt;

&lt;p&gt;The whole thing lives inside the .NET ecosystem. Nothing to host, nothing to call out to.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Install the package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Formaze.Blazor.MudBlazor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register the services (Formaze builds on MudBlazor, so that has to be registered too):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Program.cs&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMudServices&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddFormazeJson&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// JSON store; in-memory, EF Core and custom stores also exist&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Define a model like you normally would:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ContactModel&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Required&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Full name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GroupName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Identity"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;EmailAddress&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GroupName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Contact"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Email&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;DataType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DataType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MultilineText&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GroupName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Contact"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then drop a single component on the page. The same component handles both sides: flip &lt;code&gt;EditMode&lt;/code&gt; on for the people allowed to design the form, off for the people filling it in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;FormazeComponent T="ContactModel"
                  Key="contact"
                  Model="_model"
                  EditMode="_isAdmin"
                  OnValidSubmit="HandleSubmit" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The form an admin builds visually, with fields grouped into labeled sections and dragged into order, is exactly the layout users get. The configuration is saved against the &lt;code&gt;Key&lt;/code&gt; through a store (&lt;code&gt;IFormazeStore&lt;/code&gt;), so you decide where it lives: a JSON file, your EF Core database, or your own implementation. No mapping layer in between, and no redeploy to change a form.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://formaze.dev/demo" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyirupbpt19e9iwn9enmq.png" alt="The Formaze live editor: drag-and-drop fields, groups and labels" width="800" height="729"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The editor, live. &lt;a href="https://formaze.dev/demo" rel="noopener noreferrer"&gt;Try it in the browser&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can actually do
&lt;/h2&gt;

&lt;p&gt;Beyond the basic generate-a-form story, the features that turned out to matter most in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Conditional fields&lt;/strong&gt;: show a field only when another one equals a given value, configured live in the editor (works for dynamic fields too).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic fields&lt;/strong&gt;: let an admin add a field that doesn't exist on your model, no code change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model sync&lt;/strong&gt;: add a property to your C# class and it shows up automatically in a "New fields" group on the next render. Nothing to wire up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom field renderers&lt;/strong&gt;: override how any single field renders with your own component, with automatic fallback to the default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pluggable storage&lt;/strong&gt;: JSON, in-memory, EF Core, or your own &lt;code&gt;IFormazeStore&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-field configuration&lt;/strong&gt;: labels, placeholders, helper text, required/disabled/read-only, min/max/step, masks, date formats, and 1 to 4 column layouts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conditional fields are the one I'd show first. An admin marks a field "show only when...", picks the controlling field and the value, and it's live, with no recompile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// On the model, the field is just a normal property:&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Other (please specify)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GroupName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Feedback"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;OtherReason&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// In the editor, the admin sets: show "OtherReason" only when "Reason" == "Other".&lt;/span&gt;
&lt;span class="c1"&gt;// Hidden fields aren't rendered *or* validated.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What it does not do (yet)
&lt;/h2&gt;

&lt;p&gt;Being honest here, because it matters when you decide whether to adopt something.&lt;/p&gt;

&lt;p&gt;It depends on MudBlazor, and that's a real consideration today. You can already swap the &lt;em&gt;rendered&lt;/em&gt; fields for your own components through the &lt;code&gt;CustomFieldRenderer&lt;/code&gt; parameter (anything you don't override falls back to the default renderer), so your end users don't have to get a Mud-flavored UI. What isn't decoupled yet is the dependency itself and the admin editor, which is built on MudBlazor. So Mud lands in your dependency graph regardless, and making the whole thing component-agnostic is on the roadmap.&lt;/p&gt;

&lt;p&gt;It needs Interactive Server or WebAssembly render mode. Static SSR is not supported.&lt;/p&gt;

&lt;p&gt;Keyboard navigation for the drag-and-drop was the main accessibility gap at launch. That came straight out of community feedback on the first release, and it's the kind of thing I'd want to know about before installing anything. It's been reworked since: keyboard reordering, screen-reader field groups and visible focus now ship as part of a WCAG 2.2 AA pass, and the remaining edges are tracked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I wrote this up
&lt;/h2&gt;

&lt;p&gt;I shipped the first version, posted it, and got a pile of genuinely useful feedback (bugs, accessibility, edge cases). Several releases later, a lot of that feedback is now turned into fixes. Writing the experience down felt more useful than another "look at my thing" post, because the underlying problem (forms that change faster than your release cycle) is one a lot of Blazor devs hit.&lt;/p&gt;

&lt;p&gt;If it sounds like something you've fought with, the package is on NuGet and there's a live demo at &lt;a href="https://formaze.dev/demo" rel="noopener noreferrer"&gt;formaze.dev/demo&lt;/a&gt;. It's free to use (with a small "Powered by Formaze" watermark). Feedback welcome, especially the critical kind.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>blazor</category>
      <category>csharp</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
