Visitar URL original
Borm - schema-driven forms for Blazor
Skip to content
Open source · MIT · net10.0 / net11.0

Blazor forms,
described once.

Describe a form from a C# type, with a fluent builder or as a JSON Schema document - and Borm renders it, validates it and keeps its state: conditions, computed values, cascading options, wizards and repeaters included. Every decision is made in C#, and there is no JavaScript at all.

dotnet add package Bit.Borm
ContactUs.razor
BormBuilder.For<Contact>()
    .Title("Contact us")
    .Field(x => x.Name, f => f.Required().Placeholder("Your name"))
    .Field(x => x.Email, f => f.AsEmail().Required().Trim())
    .Field(x => x.IsBusiness, f => f.Label("I'm asking for a company"))
    .Field(x => x.Company, f => f
        .VisibleWhen(nameof(Contact.IsBusiness), BormConditionOperator.IsTrue))
    .Build();
Contact us

Three ways ina C# type, a fluent builder or a JSON Schema document
Decided in C#rules, conditions and computed values - no JavaScript
Accessible by defaultreal labels, aria-*, focus that goes where the error is
Every hosting modelWebAssembly, Server, Web App and Hybrid

Capabilities

Everything a form needs once it stops being a demo

The schema is plain C# - a BormDefinition you can generate, build, import, check, tailor and export with no UI at all - and the state, BormState, runs the rules and walks the wizard without a browser. The components only draw what it decided.

Three ways to define a form

Generate one from a model and its DataAnnotations, build one with typed expressions (x => x.Address.City), or import a JSON Schema document - and export any of them back out.

From a C# type →

Validation that runs when you say

Built-in, custom, cross-field and async rules, IValidatableObject and FluentValidation - on submit, blur or change, separately before and after the first submit.

Validation modes →

Conditional fields

Show, hide, disable, lock or require a field from other answers - per row inside a repeater - and clear what the user abandoned, so it never reaches your model.

Fluent builder + conditions →

Wizards and repeaters

Steps that validate before advancing and a clickable stepper; rows that add, remove and reorder, with errors and the caret that follow their row.

Multi-step wizard →

Data that follows the form

Computed values, options loaded on demand and reloaded when what they depend on changes, change handlers, dirty tracking, snapshots and resets onto a stored record.

Async & cascading options →

Yours to restyle and extend

Every colour is a --bit-borm-* custom property - this site re-tints them to its own theme - every string goes through IBormMessageProvider, and any control can be replaced.

Custom renderer →

Rules written as text

Totals, conditions and rules as expressions - {Quantity} * {UnitPrice}, {Total} >= 10000 - that travel in JSON Schema, so a form the server sends can compute and judge with no code behind it.

Expressions →

Long forms that keep their work

Drafts and autosave, a guard before the user leaves unsaved changes, undo and redo of every answer, and repeaters of hundreds of rows shown a page at a time.

Drafts & autosave →

A designer for the form's owners

BormDesigner builds a form by clicking - fields, rules, conditions, a live preview and the JSON Schema - for the people who own the questions but not the code.

Form designer →

In practice

See it in code

01

Describe the form

A field is configured where it is named, with the property's own type: rename the property and the form follows. Conditions, rules and layout live in the same place.

Fluent builder →
var schema = BormBuilder.For<AccountSignup>()
    .Field(x => x.FullName, f => f.Required().MinLength(2))
    .Field(x => x.AccountKind, f => f.AsRadio())
    .Field(x => x.CompanyName, f => f
        .Required("Company name is required for business accounts.")
        .VisibleWhen("AccountKind", BormConditionOperator.Equals, "Business"))
    .Build();
02

Render it

BormView draws every field, the error summary and the buttons, and calls you back only once the submit is valid. Want the layout yourself? Place fields one at a time with BormField.

Getting started →
<BormView Definition="schema" Model="_model"
          ShowErrorSummary="true" OnValidSubmit="Save" />
03

Hold the state

Stash a draft and put it back as the new baseline, check one answer without judging the whole form, read everything known about a field - BormState is the form, minus the drawing.

Editing an existing record →
var state = new BormState(schema, new BormModelDataAccessor(model));

var draft = state.Snapshot();                  // every path the schema binds, with its value
state.Reset(draft);                            // written back, and made the new baseline
await state.ValidateFieldAsync("Customer");    // one answer, not the whole form
var field = state.GetFieldState("Customer");   // IsDirty, IsTouched, Error, Messages

Comparison

Why not write the EditForm by hand?

Blazor's EditForm gives you validation plumbing and leaves every field, every condition and every step to markup you write and keep in step. Borm moves all of that into a schema.

ConcernHand-written EditFormBorm
Field markupA label, an input and a message written per fieldRendered from the schema, labels and aria-* included
Conditional fields@if blocks, and clearing what was hidden by handVisibleWhen, RequiredWhen, ClearOnHide()
Cross-field rulesRevalidating the other field yourselfRevalidateOn(...), and [Compare] wired automatically
Wizards and repeatersBuilt from scratchStep(...) and Array(...), with focus management
A schema from the serverNot supportedJSON Schema import and export

Where to start

Choose your path

New to Borm?

Install, register, link the stylesheet - and render a validated form from an annotated model.

Getting started →

Does the server own the schema?

Paste a JSON Schema document into the live editor and watch the form it becomes - then export it back.

Open the JSON Schema editor →

Building with an AI agent?

This site is an MCP server: point your agent at /mcp and it reads the shipped library instead of guessing.

Try the MCP server →

Questions

Frequently asked

Is there any JavaScript?

None. Every decision - validation, conditions, computed values, the wizard's position - is made in C#, and the renderers are plain HTML elements, so there is no script to ship or to load.

Which render modes does it work in?

Every one, on .NET 10 and 11: WebAssembly, Server, Auto and Hybrid. A form prerenders as plain HTML and becomes interactive with the page. On a page rendered on the server alone, a form with a FormName posts like any HTML form and is judged there exactly as it would be in the browser - see it work.

Can I keep my DataAnnotations and FluentValidation validators?

Yes. The generator turns attributes into field rules, UseDataAnnotations() runs a model's IValidatableObject, and UseFluentValidation() from Bit.Borm.FluentValidation runs an AbstractValidator<T> - and the two compose.

Can I lay the form out myself?

Yes - Columns and ColumnSpan for a grid, named groups for fieldsets, and BormField to place one field at a time in markup of your own, keeping its label, rules and accessibility wiring. Any control can be replaced through the renderer registry.

Does it work in a trimmed app?

Yes, though it is not marked as trimmable: a bound model is read, written and generated from by reflection over its properties, which no annotation can describe. The default partial trimming keeps Bit.Borm and your app's own assembly whole, and this site is published that way. With TrimMode full, keep Bit.Borm and the assemblies holding your models with <TrimmerRootAssembly>.

Describe it once

One package, one service and one stylesheet - and a model becomes a form.

dotnet add package Bit.Borm
An unhandled error has occurred. Reload 🗙