Getting started
One package, one service registration and one stylesheet - then an annotated model becomes a working form: labelled, validated, accessible, and handed back to you populated once it is valid.
Install
Bit.Borm is the whole engine - the schema, the builders, JSON Schema import and export,
the state, the rules, the components and the stylesheet - with no dependency beyond Blazor itself. It
targets .NET 8, 9 and 10. FluentValidation support is a separate, optional package.
dotnet add package Bit.Borm
# only if your models already have FluentValidation validators
dotnet add package Bit.Borm.FluentValidation
Every public type lives in one namespace, prefixed Borm, so one line in _Imports.razor brings them all into scope:
@using Bit.BormRegister the services
AddBorm() registers the field renderer registry - the map from a field type to the
component that draws it. Register it in every container that renders a form: in a Blazor Web App that
is both the server and the client project, because every page is prerendered on the server first.
// Program.cs - in every project that renders a form
builder.Services.AddBorm();Link the stylesheet
It is not injected for you. Without it a form still works, as plain HTML; the stylesheet adds the theme, the grid and the error colours. There is no script to add - Borm has no JavaScript at all.
<link rel="stylesheet" href="_content/Bit.Borm/bit-borm.css" />
A first form
Annotate a plain model and let the schema generator do the work. BormView builds a
BormState for the definition and the model, renders every field, and calls
OnValidSubmit once the submit passes validation.
public class Contact { [Required, MaxLength(80)] public string Name { get; set; } = ""; [Required, EmailAddress] public string Email { get; set; } = ""; [Range(18, 120)] public int Age { get; set; } }
<BormView Definition="_definition" Model="_model" OnValidSubmit="HandleValidSubmit" /> @code { private readonly BormDefinition _definition = BormSchemaGenerator.Generate<Contact>(); private readonly Contact _model = new(); private Task HandleValidSubmit(BormState state) { // _model is populated and valid here return Task.CompletedTask; } }
State instead of a Definition when the page wants to hold the state
itself - to wire up a validator, read its dirty fields or drive it from code. Every DOM id the form
renders starts with borm_ and is derived from the field's path, so two forms on one page
that share field names each need their own IdPrefix.
Hosting models
Borm has no JavaScript and no browser-only API, so it runs the same way under every Blazor hosting model. A form prerenders as plain HTML and becomes interactive once the page does - give the page an interactive render mode, since a form in static server rendering cannot respond to input.
- Blazor WebAssembly -
AddBorm()inProgram.cs, the stylesheet inindex.html. - Blazor Server - the same, in the server's
Program.csandApp.razor. A field that updates on input sends every keystroke over the circuit, so prefer the default update-on-change, or a debounce, on a busy form. - Blazor Web App -
AddBorm()in both the server and the client project, so both containers resolve the renderer registry. - Blazor Hybrid -
AddBorm()inMauiProgram.cs, the stylesheet inwwwroot/index.html.
Where to go next
- From a C# type - what the generator reads from a model and its attributes.
- Fluent builder + conditions - typed, refactor-safe field configuration, and fields that show and hide.
- JSON Schema - a form from a document the server owns, and back out again.
- Validation modes - when fields revalidate, the error summary, and async rules.
- MCP server - point an AI agent at this site and it reads the library instead of guessing.