FormCraft.ForFluentUI 4.0.0

dotnet add package FormCraft.ForFluentUI --version 4.0.0
                    
NuGet\Install-Package FormCraft.ForFluentUI -Version 4.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="FormCraft.ForFluentUI" Version="4.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FormCraft.ForFluentUI" Version="4.0.0" />
                    
Directory.Packages.props
<PackageReference Include="FormCraft.ForFluentUI" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add FormCraft.ForFluentUI --version 4.0.0
                    
#r "nuget: FormCraft.ForFluentUI, 4.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package FormCraft.ForFluentUI@4.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=FormCraft.ForFluentUI&version=4.0.0
                    
Install as a Cake Addin
#tool nuget:?package=FormCraft.ForFluentUI&version=4.0.0
                    
Install as a Cake Tool

FormCraft for Fluent UI Blazor

Fluent UI Blazor v5 implementation for FormCraft dynamic forms. Renders your FormBuilder<TModel> configuration with Microsoft's Fluent design language.

Installation

dotnet add package FormCraft.ForFluentUI

Setup

// Program.cs
builder.Services.AddFormCraft();
builder.Services.AddFormCraftFluentUI();
builder.Services.AddFluentUIComponents();
@* _Imports.razor *@
@using FormCraft.ForFluentUI

Then render a form exactly as you would with any other FormCraft adapter:

<FormCraftComponent
    TModel="ContactModel"
    Model="@_model"
    Configuration="@_configuration"
    OnValidSubmit="@HandleSubmit" />

Supported field types

Model type Rendered with
string FluentTextInput (or FluentTextArea when Lines > 1)
string with .WithOptions(...) FluentSelect
int, long, short, decimal, double, float (and nullable forms) FluentNumberInput<T>
bool, bool? FluentCheckbox (or FluentSwitch via .WithAttribute("Switch", true))
DateTime, DateOnly (and nullable forms) FluentDatePicker<T>
TimeOnly, TimeOnly? FluentTimePicker<T>

Field groups

.AddFieldGroup(...) renders with its card and column layout (#278): ShowInCard() produces a FluentCard, WithColumns(n) lays the group's fields out across Fluent's 12-column grid, and any field left out of every group still renders after them.

One deliberate difference from the MudBlazor adapter: ShowInCard(elevation: n) takes an integer on MudBlazor's scale, and Fluent has only five shadow buckets, so the elevation is mapped onto the nearest one rather than ignored. For an exact shadow, style the group's WithCssClass(...) instead.

Security

.WithSecurity(...) is enforced — rate limiting, CSRF protection, audit logging and field encryption behave exactly as they do in the MudBlazor adapter, because both adapters call the same FormSecurityEnforcer<TModel> in core (#321). A blocked submission never reaches your OnValidSubmit handler, and the reason is shown in a FluentMessageBar.

var config = FormBuilder<ContactModel>.Create()
    .AddField(x => x.Email, f => f.WithLabel("Email"))
    .WithSecurity(s => s
        .WithRateLimit(5, TimeSpan.FromMinutes(1))
        .EnableCsrfProtection()
        .EnableAuditLogging())
    .Build();

Set SecurityContextId on the component to a per-user or per-session value, or rate limits are shared across every user of the form (it defaults to the model type name).

Lookup and LOV pickers are inline, not modal

.AsLookup(...) and .AsLov(...) render a read-only display plus a Browse button that reveals the candidate rows in an inline panel. The MudBlazor adapter opens a modal dialog instead, and the difference is deliberate: Fluent UI v5's dialog service renders nothing unless the host application places a FluentDialogProvider in its layout, and a field component cannot check that. A Browse button that silently did nothing on an app which had not added the provider is the kind of quiet failure this library avoids elsewhere, so the picker is rendered where it cannot fail to appear.

⚠️ .AsLookup(...) for Fluent lives in FormCraft.ForFluentUI.Extensions, not in namespace FormCraft, because the MudBlazor package already publishes a method of that name there. Add using FormCraft.ForFluentUI.Extensions; and call it as usual. Both write the same attributes, so either package's .AsLookup(...) renders correctly under either adapter.

Note that the separate namespace does not, on its own, let a project reference both packages and call .AsLookup(...) by extension syntax: extension lookup spans every imported namespace at once, and the two signatures are identical, so such a project gets CS0121. One adapter per application — the supported configuration — never hits it. If you genuinely need both referenced, call the method statically (FluentUIFieldBuilderExtensions.AsLookup(field, …)) or use an extern alias; this library's own test project does the former.

(.WithNativeRequired(...) has no such caveat: #279 moved it into core, so there is exactly one.)

Custom renderers

Three ship with the package, used via .WithCustomRenderer(...) rather than registered globally — registering them would turn every double into a slider and every string into a colour picker:

.AddField(x => x.Volume, f => f.WithCustomRenderer<FluentUISliderRenderer>())
.AddField(x => x.Score,  f => f.WithCustomRenderer<FluentUIRatingRenderer>())
.AddField(x => x.Colour, f => f.WithCustomRenderer<FluentUIColorPickerRenderer>())

The rating renders a row of focusable, labelled buttons rather than Fluent's FluentRatingDisplay. Measured against 5.0.0-rc.5: that component exposes Value but no ValueChanged and no click handling — it is a read-only display, so a field bound to it would show the score and silently refuse every edit.

Running the showcase

cd FormCraft.DemoFluentApp && dotnet run

A separate app from FormCraft.DemoBlazorApp on purpose: the two adapters are mutually exclusive in one DI container, and Blazor has no per-subtree service provider, so a Fluent page cannot live inside the MudBlazor demo whatever it injects.

Not yet covered

The collection field's keyboard focus restore (#337) now matches MudBlazor's. As of #385, the file upload field has been audited and found to render no Clear button or per-file chip-close control, so the WCAG 2.1 2.4.3 focus-restore gap MudBlazor's had before #318 does not currently exist there; a future removal control is a separate feature candidate that must wire focus through FormCraft.ForFluentUI.FocusRestore from the start.

See the follow-ups on #260.

One adapter at a time

AddFormCraftFluentUI() and AddFormCraftMudBlazor() are mutually exclusive. Renderer selection is first-match-wins, so a container holding both renders a form that is partly Material and partly Fluent, with no error to point at.

Whichever registration runs second throws, so both orders fail identically:

builder.Services.AddFormCraft();
builder.Services.AddFormCraftMudBlazor();
builder.Services.AddFormCraftFluentUI();   // throws InvalidOperationException

builder.Services.AddFormCraft();
builder.Services.AddFormCraftFluentUI();
builder.Services.AddFormCraftMudBlazor();  // throws too, since #279

The guard used to live in this package alone, which meant it only caught the first order — the second slipped through and produced exactly the mixed container it exists to prevent. The rule now lives in FormCraft core (AdapterRegistration.EnsureSingleAdapter) and both adapters call it, so it is symmetric by construction rather than by two packages remembering to agree.

Registering the same adapter twice is not a conflict and stays legal.

Accessibility

A .Required(...) field is announced to assistive technology with aria-required="true", matching the guarantee the MudBlazor adapter carries since #199.

Control the decoration independently of the validator with the typed .WithNativeRequired(...), which lives in core since #279 and so needs no extra using:

.AddField(x => x.Email, f => f
    .Required("Email is required")   // the validation
    .WithNativeRequired())           // the decoration

.AddField(x => x.Nickname, f => f
    .Required("Nickname is required")
    .WithNativeRequired(false))      // validated, but not announced

It wins over the validator in both directions, and replaces the raw .WithAttribute("Required", …) form this README used to document — that still works and writes the same attribute, but it is a magic string one typo away from silently doing nothing.

File uploads are the exception: a required upload is marked with a visible * on its label and an aria-describedby hint on its Browse button, not aria-required on the hidden file input, which no keyboard user ever reaches.

FormCraft validates server-side: the form renders novalidate, so the browser runs no constraint validation of its own.

License

MIT

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
4.0.0 57 9/25/2026