Skip to content
Open source · MIT · net8.0 / net9.0 / net10.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 →

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?

Any interactive one, on .NET 8, 9 and 10: WebAssembly, Server, Auto and Hybrid. A form prerenders as plain HTML and becomes interactive with the page; in static server rendering it cannot respond to input, so give its page an interactive render mode.

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.

Is it trimming or AOT safe?

No. Generating a schema reads a model's properties by reflection, so Bit.Borm is not marked as trimmable: a trimmed app keeps it whole, and the models it binds to are kept by the app that declares them.

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 🗙