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
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();
aria-*, focus that goes where the error isCapabilities
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.
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.
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.
In practice
See it in code
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();
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.
<BormView Definition="schema" Model="_model" ShowErrorSummary="true" OnValidSubmit="Save" />
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.
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.
| Concern | Hand-written EditForm | Borm |
|---|---|---|
| Field markup | A label, an input and a message written per field | Rendered from the schema, labels and aria-* included |
| Conditional fields | @if blocks, and clearing what was hidden by hand | VisibleWhen, RequiredWhen, ClearOnHide() |
| Cross-field rules | Revalidating the other field yourself | RevalidateOn(...), and [Compare] wired automatically |
| Wizards and repeaters | Built from scratch | Step(...) and Array(...), with focus management |
| A schema from the server | Not supported | JSON 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.
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