Skip to content

Affixes, counters & live updates

The details that separate a generated form from a designed one. A price field wearing a currency symbol; a summary that counts down to its limit as you type; a section heading that belongs to the schema rather than to the page around it. The preview on the right is driven by UpdateOnInput() — the model is written on every keystroke, so nothing here is wired up by hand.

Live form
New listing

The basics

What someone sees first in the search results.

A sentence or two. Shown under the title in search results.

Details

Start typing — the suggestions are hints, not a closed list.

Anyone with this code can edit the listing.

Live preview

Updates as you type, straight from the model.

Untitled listing

No summary yet.

€1,200 / month · 65 m² · —
○ Draft
View source
var schema = BormBuilder.For<ListingDraft>()
    .Title("New listing")

    // A section heading that lives in the schema, so the page does not have to interleave its own
    // markup with the generated fields.
    .Static("basics", "The basics", "What someone sees first in the search results.")

    .Field(x => x.Title, f => f
        .Required()
        .MaxLength(60)
        .CharacterCount()          // "12 / 60", counting as you type
        .UpdateOnInput())          // write on every keystroke, not on blur

    .Field(x => x.Summary, f => f
        .AsTextArea(3)
        .MaxLength(180)
        .CharacterCount()
        .UpdateOnInput(debounceMilliseconds: 150))   // one write per pause, not per character

    .Static("details", "Details", configure: f => f.Attr("divider", true))

    .Field(x => x.Price, f => f
        .Prefix("€").Suffix("/ month")               // decoration inside the field's border
        .Range(0, 100_000).Step(50)
        .UpdateOnInput())

    .Field(x => x.FloorArea, f => f
        .Suffix("m²")
        .Range(1, 5_000)
        .UpdateOnInput())

    // Suggests without restricting: the field still accepts anything typed into it.
    .Field(x => x.City, f => f
        .Required()
        .Suggest("Amsterdam", "Berlin", "Lisbon", "Paris", "Warsaw")
        .Clearable()                                 // one-click "empty this", shown only when non-empty
        .UpdateOnInput()
        .InputAttr("data-testid", "city"))           // arbitrary HTML attributes, splatted onto the input

    .Field(x => x.Passcode, f => f
        .AsPassword()
        .Revealable()                                // opt-in "show the characters" toggle
        .MinLength(6))

    .Field(x => x.Published, f => f.AsSwitch())      // a checkbox that announces itself as a switch
    .Build();
An unhandled error has occurred. Reload 🗙