From 542256980578c94d224fe602da1ba6a3c601ef89 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Thu, 2 Jul 2026 20:25:55 +0200 Subject: [PATCH 1/3] Make native contraint validation ui opt-in --- .../Documentation/Components/Forms/Forms.md | 18 +++++- src/Core/Components/Base/FluentInputBase.cs | 21 +++++++ .../Infrastructure/LibraryConfiguration.cs | 8 +++ .../Components/Switch/FluentSwitchTests.razor | 22 +------ .../TextArea/FluentTextAreaTests.razor | 19 ------ .../FluentInputValidationDefaultsTests.cs | 62 +++++++++++++++++++ 6 files changed, 108 insertions(+), 42 deletions(-) create mode 100644 tests/Core/Components/Validation/FluentInputValidationDefaultsTests.cs diff --git a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Forms/Forms.md b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Forms/Forms.md index b190b5a7ca..6da78db158 100644 --- a/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Forms/Forms.md +++ b/examples/Demo/FluentUI.Demo.Client/Documentation/Components/Forms/Forms.md @@ -5,10 +5,26 @@ icon: Form --- ## Validation + The Fluent UI Razor components work with a validation summary in the same way the standard Blazor (input) components do. An extra component is provided to make it possible to show a validation summary that follows the Fluent Design guidelines: - FluentValidationSummary +### Native constraint validation UI + +By default the Fluent UI components render validation feedback using the library's UI. If you prefer the browser's native HTML5 constraint validation UI (the built-in validation bubbles/messages driven by the Constraint Validation API), you can opt in at library registration time. + +```csharp +// Program.cs +builder.Services.AddFluentUIComponents(config => +{ + // Default is false. Set to true to enable the browser's native constraint validation UI. + config.UseNativeConstraintValidationUi = true; +}); +``` + +The name "constraint" refers to the HTML5 Constraint Validation API (attributes like `required`, `pattern`, `min`, `max`, etc.) and the browser's native UI for reporting those violations. Use this option when you want parity with the browser's built-in validation experience; otherwise keep the library defaults for a consistent Fluent UI look-and-feel. + See the [documentation](https://learn.microsoft.com/en-us/aspnet/core/blazor/forms/validation?view=aspnetcore-10.0#validation-summary-and-validation-message-components) on the Learn site for more information on the standard components. As the Fluent component is based on the standard component, the same documentation applies ## Example form with validation @@ -20,8 +36,6 @@ Not all of the library's input components are used in this form. No data is actu {{ BasicForm }} - ## API FluentValidationSummary {{ API Type=FluentValidationSummary }} - diff --git a/src/Core/Components/Base/FluentInputBase.cs b/src/Core/Components/Base/FluentInputBase.cs index e604d21500..4adaaf12f3 100644 --- a/src/Core/Components/Base/FluentInputBase.cs +++ b/src/Core/Components/Base/FluentInputBase.cs @@ -31,6 +31,14 @@ protected FluentInputBase(LibraryConfiguration configuration) { ValueExpression = () => CurrentValueOrDefault; configuration?.DefaultValues.ApplyDefaults(this); + + // Apply the library configured default for using the native browser + // constraint validation UI. This value acts as the component default + // and can be overridden by setting the component parameter in markup. + if (configuration is not null) + { + UseNativeConstraintValidationUi = configuration.UseNativeConstraintValidationUi; + } } [Inject] @@ -206,6 +214,12 @@ protected FluentInputBase(LibraryConfiguration configuration) [Parameter] public virtual bool ReadOnly { get; set; } + /// + /// Gets or sets whether the control will use the native browser constraint validation UI. + /// + [Parameter] + public bool UseNativeConstraintValidationUi { get; set; } + /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0059:Unnecessary assignment of a value", Justification = "TODO")] protected virtual async Task ChangeHandlerAsync(ChangeEventArgs e) @@ -229,6 +243,13 @@ protected virtual async Task ChangeHandlerAsync(ChangeEventArgs e) /// protected virtual async Task ReportValidityAsync() { + // Only call the browser native constraint validation UI when enabled. + // This behavior is opt-in via UseNativeValidationUi (default is false). + if (!UseNativeConstraintValidationUi) + { + return; + } + if (string.IsNullOrWhiteSpace(Id)) { return; diff --git a/src/Core/Infrastructure/LibraryConfiguration.cs b/src/Core/Infrastructure/LibraryConfiguration.cs index a6fc50e519..530d4820cd 100644 --- a/src/Core/Infrastructure/LibraryConfiguration.cs +++ b/src/Core/Infrastructure/LibraryConfiguration.cs @@ -55,6 +55,14 @@ public class LibraryConfiguration /// public LibraryToastOptions Toast { get; } = new LibraryToastOptions(); + /// + /// Gets or sets a value indicating whether Fluent input components should use the + /// browser's native constraint validation UI by default (e.g., showing built-in + /// validation bubbles). Default is false. Consumers can opt-in to enable the + /// native validation UI for parity with existing browser behavior. + /// + public bool UseNativeConstraintValidationUi { get; set; } + /// /// Gets the sanitized markup string for safe rendering in HTML/Styles contexts. /// diff --git a/tests/Core/Components/Switch/FluentSwitchTests.razor b/tests/Core/Components/Switch/FluentSwitchTests.razor index 13e1721fc3..174a2c6082 100644 --- a/tests/Core/Components/Switch/FluentSwitchTests.razor +++ b/tests/Core/Components/Switch/FluentSwitchTests.razor @@ -1,5 +1,4 @@ -@using Microsoft.FluentUI.AspNetCore.Components.Utilities -@using Xunit; +@using Xunit; @inherits FluentUITestContext @code @@ -84,25 +83,6 @@ Assert.Equal(expectedValue, value); } - [Fact] - public void FluentSwitch_OnChange_ReportsValidity() - { - // Arrange - using var context = new IdentifierContext(i => "myId"); - var value = false; - var cut = Render(@); - - // Act - cut.Find("fluent-switch").Change(""); - - // Assert - var reportValidityInvocations = JSInterop.Invocations - .Where(invocation => invocation.Identifier == "Microsoft.FluentUI.Blazor.Utilities.Attributes.reportValidity") - .ToList(); - - Assert.Single(reportValidityInvocations); - } - [Fact] public void FluentSwitch_TryParseValueFromString() { diff --git a/tests/Core/Components/TextArea/FluentTextAreaTests.razor b/tests/Core/Components/TextArea/FluentTextAreaTests.razor index eb1ca80df5..ea1d5539e6 100644 --- a/tests/Core/Components/TextArea/FluentTextAreaTests.razor +++ b/tests/Core/Components/TextArea/FluentTextAreaTests.razor @@ -130,25 +130,6 @@ .MarkupMatches(""); } - [Fact] - public void FluentTextArea_OnChange_ReportsValidity() - { - // Arrange - using var context = new IdentifierContext(i => "myId"); - var value = "init"; - var cut = Render(@); - - // Act - cut.Find("fluent-textarea").Change("new value"); - - // Assert - var reportValidityInvocations = JSInterop.Invocations - .Where(invocation => invocation.Identifier == "Microsoft.FluentUI.Blazor.Utilities.Attributes.reportValidity") - .ToList(); - - Assert.Single(reportValidityInvocations); - } - [Theory] [InlineData(false, 0, "init", "init")] // No Immediate [InlineData(true, 0, "new value", "new value")] // With Immediate and Delay = 0 ms diff --git a/tests/Core/Components/Validation/FluentInputValidationDefaultsTests.cs b/tests/Core/Components/Validation/FluentInputValidationDefaultsTests.cs new file mode 100644 index 0000000000..f9aceba578 --- /dev/null +++ b/tests/Core/Components/Validation/FluentInputValidationDefaultsTests.cs @@ -0,0 +1,62 @@ +// ------------------------------------------------------------------------ +// This file is licensed to you under the MIT License. +// ------------------------------------------------------------------------ + +using AngleSharp.Html.Parser; +using Bunit; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.FluentUI.AspNetCore.Components; +using Xunit; + +namespace Microsoft.FluentUI.AspNetCore.Components.Tests.Components.Validation; + +public class FluentInputValidationDefaultsTests +{ + [Fact] + public void Default_NoOptIn_ReportValidityNotCalled() + { + using var ctx = new Bunit.BunitContext(); + ctx.JSInterop.Mode = JSRuntimeMode.Loose; + ctx.Services.AddSingleton(new HtmlParser()); + ctx.Services.AddFluentUIComponents(); + + // Arrange +#pragma warning disable CS0619 // RenderComponent is obsolete in current bUnit, but safe for tests + var cut = ctx.Render(parameters => parameters.Add(p => p.Id, "myId").Add(p => p.Value, "init")); +#pragma warning restore CS0619 + + // Act + cut.Find("fluent-textarea").Change("new value"); + + // Assert + var reportValidityInvocations = ctx.JSInterop.Invocations + .Where(invocation => invocation.Identifier == "Microsoft.FluentUI.Blazor.Utilities.Attributes.reportValidity") + .ToList(); + + Assert.Empty(reportValidityInvocations); + } + + [Fact] + public void OptIn_ReportsValidity() + { + using var ctx = new Bunit.BunitContext(); + ctx.JSInterop.Mode = JSRuntimeMode.Loose; + ctx.Services.AddSingleton(new HtmlParser()); + ctx.Services.AddFluentUIComponents(config => config.UseNativeConstraintValidationUi = true); + + // Arrange +#pragma warning disable CS0619 // RenderComponent is obsolete in current bUnit, but safe for tests + var cut = ctx.Render(parameters => parameters.Add(p => p.Id, "myId").Add(p => p.Value, "init")); +#pragma warning restore CS0619 + + // Act + cut.Find("fluent-textarea").Change("new value"); + + // Assert + var reportValidityInvocations = ctx.JSInterop.Invocations + .Where(invocation => invocation.Identifier == "Microsoft.FluentUI.Blazor.Utilities.Attributes.reportValidity") + .ToList(); + + Assert.Single(reportValidityInvocations); + } +} From 93c1fc29cb6a891a089eed471b01acd5d4882d89 Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Thu, 2 Jul 2026 23:24:45 +0200 Subject: [PATCH 2/3] Process review comments --- src/Core/Components/Base/FluentInputBase.cs | 8 ++++---- .../Validation/FluentInputValidationDefaultsTests.cs | 4 ---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Core/Components/Base/FluentInputBase.cs b/src/Core/Components/Base/FluentInputBase.cs index 4adaaf12f3..37c4cb54b5 100644 --- a/src/Core/Components/Base/FluentInputBase.cs +++ b/src/Core/Components/Base/FluentInputBase.cs @@ -37,7 +37,7 @@ protected FluentInputBase(LibraryConfiguration configuration) // and can be overridden by setting the component parameter in markup. if (configuration is not null) { - UseNativeConstraintValidationUi = configuration.UseNativeConstraintValidationUi; + UseNativeConstraintValidationUI = configuration.UseNativeConstraintValidationUI; } } @@ -218,7 +218,7 @@ protected FluentInputBase(LibraryConfiguration configuration) /// Gets or sets whether the control will use the native browser constraint validation UI. /// [Parameter] - public bool UseNativeConstraintValidationUi { get; set; } + public bool UseNativeConstraintValidationUI { get; set; } /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0059:Unnecessary assignment of a value", Justification = "TODO")] @@ -244,8 +244,8 @@ protected virtual async Task ChangeHandlerAsync(ChangeEventArgs e) protected virtual async Task ReportValidityAsync() { // Only call the browser native constraint validation UI when enabled. - // This behavior is opt-in via UseNativeValidationUi (default is false). - if (!UseNativeConstraintValidationUi) + // This behavior is opt-in via UseNativeConstraintValidationUi (default is false). + if (!UseNativeConstraintValidationUI) { return; } diff --git a/tests/Core/Components/Validation/FluentInputValidationDefaultsTests.cs b/tests/Core/Components/Validation/FluentInputValidationDefaultsTests.cs index f9aceba578..38275dbfed 100644 --- a/tests/Core/Components/Validation/FluentInputValidationDefaultsTests.cs +++ b/tests/Core/Components/Validation/FluentInputValidationDefaultsTests.cs @@ -21,9 +21,7 @@ public void Default_NoOptIn_ReportValidityNotCalled() ctx.Services.AddFluentUIComponents(); // Arrange -#pragma warning disable CS0619 // RenderComponent is obsolete in current bUnit, but safe for tests var cut = ctx.Render(parameters => parameters.Add(p => p.Id, "myId").Add(p => p.Value, "init")); -#pragma warning restore CS0619 // Act cut.Find("fluent-textarea").Change("new value"); @@ -45,9 +43,7 @@ public void OptIn_ReportsValidity() ctx.Services.AddFluentUIComponents(config => config.UseNativeConstraintValidationUi = true); // Arrange -#pragma warning disable CS0619 // RenderComponent is obsolete in current bUnit, but safe for tests var cut = ctx.Render(parameters => parameters.Add(p => p.Id, "myId").Add(p => p.Value, "init")); -#pragma warning restore CS0619 // Act cut.Find("fluent-textarea").Change("new value"); From ee395e71cfba157ea5fb555dbadb2165f5b495ad Mon Sep 17 00:00:00 2001 From: Vincent Baaij Date: Fri, 3 Jul 2026 07:55:24 +0200 Subject: [PATCH 3/3] Fix build errors, rename test file --- src/Core/Infrastructure/LibraryConfiguration.cs | 2 +- ...InputValidationDefaultsTests.cs => ReportValidityTests.cs} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename tests/Core/Components/Validation/{FluentInputValidationDefaultsTests.cs => ReportValidityTests.cs} (95%) diff --git a/src/Core/Infrastructure/LibraryConfiguration.cs b/src/Core/Infrastructure/LibraryConfiguration.cs index 530d4820cd..742349631a 100644 --- a/src/Core/Infrastructure/LibraryConfiguration.cs +++ b/src/Core/Infrastructure/LibraryConfiguration.cs @@ -61,7 +61,7 @@ public class LibraryConfiguration /// validation bubbles). Default is false. Consumers can opt-in to enable the /// native validation UI for parity with existing browser behavior. /// - public bool UseNativeConstraintValidationUi { get; set; } + public bool UseNativeConstraintValidationUI { get; set; } /// /// Gets the sanitized markup string for safe rendering in HTML/Styles contexts. diff --git a/tests/Core/Components/Validation/FluentInputValidationDefaultsTests.cs b/tests/Core/Components/Validation/ReportValidityTests.cs similarity index 95% rename from tests/Core/Components/Validation/FluentInputValidationDefaultsTests.cs rename to tests/Core/Components/Validation/ReportValidityTests.cs index 38275dbfed..974cdf0a76 100644 --- a/tests/Core/Components/Validation/FluentInputValidationDefaultsTests.cs +++ b/tests/Core/Components/Validation/ReportValidityTests.cs @@ -10,7 +10,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components.Tests.Components.Validation; -public class FluentInputValidationDefaultsTests +public class ReportValidityTests { [Fact] public void Default_NoOptIn_ReportValidityNotCalled() @@ -40,7 +40,7 @@ public void OptIn_ReportsValidity() using var ctx = new Bunit.BunitContext(); ctx.JSInterop.Mode = JSRuntimeMode.Loose; ctx.Services.AddSingleton(new HtmlParser()); - ctx.Services.AddFluentUIComponents(config => config.UseNativeConstraintValidationUi = true); + ctx.Services.AddFluentUIComponents(config => config.UseNativeConstraintValidationUI = true); // Arrange var cut = ctx.Render(parameters => parameters.Add(p => p.Id, "myId").Add(p => p.Value, "init"));