|
| 1 | +--- |
| 2 | +name: fluentui-blazor |
| 3 | +description: > |
| 4 | + Guide for using the Microsoft Fluent UI Blazor component library |
| 5 | + (Microsoft.FluentUI.AspNetCore.Components NuGet package) in Blazor applications. |
| 6 | + Use this when the user is building a Blazor app with Fluent UI components, |
| 7 | + setting up the library, using FluentUI components like FluentButton, FluentDataGrid, |
| 8 | + FluentDialog, FluentToast, FluentNavMenu, FluentTextField, FluentSelect, |
| 9 | + FluentAutocomplete, FluentDesignTheme, or any component prefixed with "Fluent". |
| 10 | + Also use when troubleshooting missing providers, JS interop issues, or theming. |
| 11 | +--- |
| 12 | + |
| 13 | +# Fluent UI Blazor — Consumer Usage Guide |
| 14 | + |
| 15 | +This skill teaches how to correctly use the **Microsoft.FluentUI.AspNetCore.Components** (version 4) NuGet package in Blazor applications. |
| 16 | + |
| 17 | +## Critical Rules |
| 18 | + |
| 19 | +### 1. No manual `<script>` or `<link>` tags needed |
| 20 | + |
| 21 | +The library auto-loads all CSS and JS via Blazor's static web assets and JS initializers. **Never tell users to add `<script>` or `<link>` tags for the core library.** |
| 22 | + |
| 23 | +### 2. Providers are mandatory for service-based components |
| 24 | + |
| 25 | +These provider components **MUST** be added to the root layout (e.g. `MainLayout.razor`) for their corresponding services to work. Without them, service calls **fail silently** (no error, no UI). |
| 26 | + |
| 27 | +```razor |
| 28 | +<FluentToastProvider /> |
| 29 | +<FluentDialogProvider /> |
| 30 | +<FluentMessageBarProvider /> |
| 31 | +<FluentTooltipProvider /> |
| 32 | +<FluentKeyCodeProvider /> |
| 33 | +``` |
| 34 | + |
| 35 | +### 3. Service registration in Program.cs |
| 36 | + |
| 37 | +```csharp |
| 38 | +builder.Services.AddFluentUIComponents(); |
| 39 | + |
| 40 | +// Or with configuration: |
| 41 | +builder.Services.AddFluentUIComponents(options => |
| 42 | +{ |
| 43 | + options.UseTooltipServiceProvider = true; // default: true |
| 44 | + options.ServiceLifetime = ServiceLifetime.Scoped; // default |
| 45 | +}); |
| 46 | +``` |
| 47 | + |
| 48 | +**ServiceLifetime rules:** |
| 49 | +- `ServiceLifetime.Scoped` — for Blazor Server / Interactive (default) |
| 50 | +- `ServiceLifetime.Singleton` — for Blazor WebAssembly standalone |
| 51 | +- `ServiceLifetime.Transient` — **throws `NotSupportedException`** |
| 52 | + |
| 53 | +### 4. Icons require a separate NuGet package |
| 54 | + |
| 55 | +``` |
| 56 | +dotnet add package Microsoft.FluentUI.AspNetCore.Components.Icons |
| 57 | +``` |
| 58 | + |
| 59 | +Usage with a `@using` alias: |
| 60 | + |
| 61 | +```razor |
| 62 | +@using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons |
| 63 | +
|
| 64 | +<FluentIcon Value="@(Icons.Regular.Size24.Save)" /> |
| 65 | +<FluentIcon Value="@(Icons.Filled.Size20.Delete)" Color="@Color.Error" /> |
| 66 | +``` |
| 67 | + |
| 68 | +Pattern: `Icons.[Variant].[Size].[Name]` |
| 69 | +- Variants: `Regular`, `Filled` |
| 70 | +- Sizes: `Size12`, `Size16`, `Size20`, `Size24`, `Size28`, `Size32`, `Size48` |
| 71 | + |
| 72 | +Custom image: `Icon.FromImageUrl("/path/to/image.png")` |
| 73 | + |
| 74 | +**Never use string-based icon names** — icons are strongly-typed classes. |
| 75 | + |
| 76 | +### 5. List component binding model |
| 77 | + |
| 78 | +`FluentSelect<TOption>`, `FluentCombobox<TOption>`, `FluentListbox<TOption>`, and `FluentAutocomplete<TOption>` do NOT work like `<InputSelect>`. They use: |
| 79 | + |
| 80 | +- `Items` — the data source (`IEnumerable<TOption>`) |
| 81 | +- `OptionText` — `Func<TOption, string?>` to extract display text |
| 82 | +- `OptionValue` — `Func<TOption, string?>` to extract the value string |
| 83 | +- `SelectedOption` / `SelectedOptionChanged` — for single selection binding |
| 84 | +- `SelectedOptions` / `SelectedOptionsChanged` — for multi-selection binding |
| 85 | + |
| 86 | +```razor |
| 87 | +<FluentSelect Items="@countries" |
| 88 | + OptionText="@(c => c.Name)" |
| 89 | + OptionValue="@(c => c.Code)" |
| 90 | + @bind-SelectedOption="@selectedCountry" |
| 91 | + Label="Country" /> |
| 92 | +``` |
| 93 | + |
| 94 | +**NOT** like this (wrong pattern): |
| 95 | +```razor |
| 96 | +@* WRONG — do not use InputSelect pattern *@ |
| 97 | +<FluentSelect @bind-Value="@selectedValue"> |
| 98 | + <option value="1">One</option> |
| 99 | +</FluentSelect> |
| 100 | +``` |
| 101 | + |
| 102 | +### 6. FluentAutocomplete specifics |
| 103 | + |
| 104 | +- Use `ValueText` (NOT `Value` — it's obsolete) for the search input text |
| 105 | +- `OnOptionsSearch` is the required callback to filter options |
| 106 | +- Default is `Multiple="true"` |
| 107 | + |
| 108 | +```razor |
| 109 | +<FluentAutocomplete TOption="Person" |
| 110 | + OnOptionsSearch="@OnSearch" |
| 111 | + OptionText="@(p => p.FullName)" |
| 112 | + @bind-SelectedOptions="@selectedPeople" |
| 113 | + Label="Search people" /> |
| 114 | +
|
| 115 | +@code { |
| 116 | + private void OnSearch(OptionsSearchEventArgs<Person> args) |
| 117 | + { |
| 118 | + args.Items = allPeople.Where(p => |
| 119 | + p.FullName.Contains(args.Text, StringComparison.OrdinalIgnoreCase)); |
| 120 | + } |
| 121 | +} |
| 122 | +``` |
| 123 | + |
| 124 | +### 7. Dialog service pattern |
| 125 | + |
| 126 | +**Do NOT toggle visibility of `<FluentDialog>` tags.** The service pattern is: |
| 127 | + |
| 128 | +1. Create a content component implementing `IDialogContentComponent<TData>`: |
| 129 | + |
| 130 | +```csharp |
| 131 | +public partial class EditPersonDialog : IDialogContentComponent<Person> |
| 132 | +{ |
| 133 | + [Parameter] public Person Content { get; set; } = default!; |
| 134 | + |
| 135 | + [CascadingParameter] public FluentDialog Dialog { get; set; } = default!; |
| 136 | + |
| 137 | + private async Task SaveAsync() |
| 138 | + { |
| 139 | + await Dialog.CloseAsync(Content); |
| 140 | + } |
| 141 | + |
| 142 | + private async Task CancelAsync() |
| 143 | + { |
| 144 | + await Dialog.CancelAsync(); |
| 145 | + } |
| 146 | +} |
| 147 | +``` |
| 148 | + |
| 149 | +2. Show the dialog via `IDialogService`: |
| 150 | + |
| 151 | +```csharp |
| 152 | +[Inject] private IDialogService DialogService { get; set; } = default!; |
| 153 | + |
| 154 | +private async Task ShowEditDialog() |
| 155 | +{ |
| 156 | + var dialog = await DialogService.ShowDialogAsync<EditPersonDialog, Person>( |
| 157 | + person, |
| 158 | + new DialogParameters |
| 159 | + { |
| 160 | + Title = "Edit Person", |
| 161 | + PrimaryAction = "Save", |
| 162 | + SecondaryAction = "Cancel", |
| 163 | + Width = "500px", |
| 164 | + PreventDismissOnOverlayClick = true, |
| 165 | + }); |
| 166 | + |
| 167 | + var result = await dialog.Result; |
| 168 | + if (!result.Cancelled) |
| 169 | + { |
| 170 | + var updatedPerson = result.Data as Person; |
| 171 | + } |
| 172 | +} |
| 173 | +``` |
| 174 | + |
| 175 | +For convenience dialogs: |
| 176 | +```csharp |
| 177 | +await DialogService.ShowConfirmationAsync("Are you sure?", "Yes", "No"); |
| 178 | +await DialogService.ShowSuccessAsync("Done!"); |
| 179 | +await DialogService.ShowErrorAsync("Something went wrong."); |
| 180 | +``` |
| 181 | + |
| 182 | +### 8. Toast notifications |
| 183 | + |
| 184 | +```csharp |
| 185 | +[Inject] private IToastService ToastService { get; set; } = default!; |
| 186 | + |
| 187 | +ToastService.ShowSuccess("Item saved successfully"); |
| 188 | +ToastService.ShowError("Failed to save"); |
| 189 | +ToastService.ShowWarning("Check your input"); |
| 190 | +ToastService.ShowInfo("New update available"); |
| 191 | +``` |
| 192 | + |
| 193 | +`FluentToastProvider` parameters: `Position` (default `TopRight`), `Timeout` (default 7000ms), `MaxToastCount` (default 4). |
| 194 | + |
| 195 | +### 9. Design tokens and themes work only after render |
| 196 | + |
| 197 | +Design tokens rely on JS interop. **Never set them in `OnInitialized`** — use `OnAfterRenderAsync`. |
| 198 | + |
| 199 | +```razor |
| 200 | +<FluentDesignTheme Mode="DesignThemeModes.System" |
| 201 | + OfficeColor="OfficeColor.Teams" |
| 202 | + StorageName="mytheme" /> |
| 203 | +``` |
| 204 | + |
| 205 | +### 10. FluentEditForm vs EditForm |
| 206 | + |
| 207 | +`FluentEditForm` is only needed inside `FluentWizard` steps (per-step validation). For regular forms, use standard `EditForm` with Fluent form components: |
| 208 | + |
| 209 | +```razor |
| 210 | +<EditForm Model="@model" OnValidSubmit="HandleSubmit"> |
| 211 | + <DataAnnotationsValidator /> |
| 212 | + <FluentTextField @bind-Value="@model.Name" Label="Name" Required /> |
| 213 | + <FluentSelect Items="@options" |
| 214 | + OptionText="@(o => o.Label)" |
| 215 | + @bind-SelectedOption="@model.Category" |
| 216 | + Label="Category" /> |
| 217 | + <FluentValidationSummary /> |
| 218 | + <FluentButton Type="ButtonType.Submit" Appearance="Appearance.Accent">Save</FluentButton> |
| 219 | +</EditForm> |
| 220 | +``` |
| 221 | + |
| 222 | +Use `FluentValidationMessage` and `FluentValidationSummary` instead of standard Blazor validation components for Fluent styling. |
| 223 | + |
| 224 | +## Reference files |
| 225 | + |
| 226 | +For detailed guidance on specific topics, see: |
| 227 | + |
| 228 | +- [Setup and configuration](references/SETUP.md) |
| 229 | +- [Layout and navigation](references/LAYOUT-AND-NAVIGATION.md) |
| 230 | +- [Data grid](references/DATAGRID.md) |
| 231 | +- [Theming](references/THEMING.md) |
0 commit comments