FormCraft.ForFluentUI
4.0.0
dotnet add package FormCraft.ForFluentUI --version 4.0.0
NuGet\Install-Package FormCraft.ForFluentUI -Version 4.0.0
<PackageReference Include="FormCraft.ForFluentUI" Version="4.0.0" />
<PackageVersion Include="FormCraft.ForFluentUI" Version="4.0.0" />
<PackageReference Include="FormCraft.ForFluentUI" />
paket add FormCraft.ForFluentUI --version 4.0.0
#r "nuget: FormCraft.ForFluentUI, 4.0.0"
#:package FormCraft.ForFluentUI@4.0.0
#addin nuget:?package=FormCraft.ForFluentUI&version=4.0.0
#tool nuget:?package=FormCraft.ForFluentUI&version=4.0.0
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 | Versions 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. |
-
net10.0
- FormCraft (>= 4.0.0)
- Microsoft.AspNetCore.Components.Web (>= 10.0.12)
- Microsoft.FluentUI.AspNetCore.Components (>= 5.0.0-rc.5-26219.1)
-
net8.0
- FormCraft (>= 4.0.0)
- Microsoft.AspNetCore.Components.Web (>= 8.0.31)
- Microsoft.FluentUI.AspNetCore.Components (>= 5.0.0-rc.5-26219.1)
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 |