diff --git a/FormCraft.ForFluentUI.UnitTests/Fields/FieldConfigurationRefreshTests.cs b/FormCraft.ForFluentUI.UnitTests/Fields/FieldConfigurationRefreshTests.cs
new file mode 100644
index 00000000..d7c49c35
--- /dev/null
+++ b/FormCraft.ForFluentUI.UnitTests/Fields/FieldConfigurationRefreshTests.cs
@@ -0,0 +1,259 @@
+using FormCraft.ForFluentUI.Extensions;
+using FormCraft.ForFluentUI.UnitTests.Components;
+using Microsoft.FluentUI.AspNetCore.Components;
+
+namespace FormCraft.ForFluentUI.UnitTests.Fields;
+
+///
+/// A Fluent field component must render the configuration of the field it is currently showing
+/// (#335).
+///
+///
+///
+/// The same defect #298 fixed for MudBlazor, unfixed in this adapter: components read their
+/// configuration once in OnInitialized and never look again, so an instance re-parameterised
+/// with a different Context keeps rendering the previous field's settings. Blazor reuses a
+/// component instance whenever the render-tree shape matches, which a swapped
+/// FormCraftComponent.Configuration — a wizard step, a mode toggle — does routinely.
+///
+///
+/// Mirrors FormCraft.ForMudBlazor.UnitTests.Fields.FieldConfigurationRefreshTests deliberately.
+/// One behaviour implemented twice and drifting is this library's recurring defect (#146, #177, #184,
+/// #190, #203, #279), and the fix for it — the hook on FieldComponentBase — is now shared, so
+/// the coverage should be recognisably the same on both sides.
+///
+///
+public class FieldConfigurationRefreshTests : FluentUITestBase
+{
+ ///
+ /// The assumption the refresh rests on: Context.Field is the same object across renders.
+ ///
+ ///
+ /// Re-pinned here rather than assumed from the MudBlazor side. Both adapters go through
+ /// FieldRendererService.RenderField, which allocates a fresh FieldRenderContext per
+ /// render — so the context is not stable — but fills its Field from the built
+ /// configuration, which FormBuilder.Build() makes immutable and hands out by reference.
+ /// The guard compares that reference, so it has to hold or the refresh either never fires or
+ /// fires on every keystroke.
+ ///
+ [Fact]
+ public void Context_Field_Should_Be_The_Same_Instance_Across_Renders()
+ {
+ // Arrange
+ var component = Render>(parameters => parameters
+ .Add(p => p.Model, new TestModel())
+ .Add(p => p.Configuration, TextConfiguration("text")));
+
+ var first = component.FindComponent>().Instance.Context.Field;
+
+ // Act
+ component.Render();
+ component.Render();
+
+ // Assert
+ var second = component.FindComponent>().Instance.Context.Field;
+ ReferenceEquals(first, second).ShouldBeTrue();
+ }
+
+ ///
+ /// A different field arriving on the same instance re-reads that field's configuration.
+ ///
+ ///
+ /// Both configurations declare a field called Name at the same position, so Blazor reuses
+ /// the component — and the input type stayed on whatever the first configuration declared.
+ ///
+ [Fact]
+ public void TextField_Should_Rebind_Its_InputType_When_The_Configuration_Is_Swapped()
+ {
+ // Arrange
+ var component = Render>(parameters => parameters
+ .Add(p => p.Model, new TestModel())
+ .Add(p => p.Configuration, TextConfiguration("text")));
+
+ component.FindComponent().Instance.TextInputType
+ .ShouldBe(TextInputType.Text);
+
+ // Act
+ component.Render(parameters => parameters
+ .Add(p => p.Configuration, TextConfiguration("password")));
+
+ // Assert
+ component.FindComponent().Instance.TextInputType
+ .ShouldBe(TextInputType.Password);
+ }
+
+ ///
+ /// An attribute the new field does not declare reverts to its default (#335).
+ ///
+ ///
+ /// The complement, and the one that catches a fix that only ever overwrites. A reload
+ /// assigning each attribute it finds leaves the previous field's value in place for every
+ /// attribute the new field omits — so a field that dropped Lines would keep rendering a
+ /// text area.
+ ///
+ /// Asserted on which component renders, not on a property: the razor picks
+ /// FluentTextArea over FluentTextInput on Lines > 1, so the rendered shape
+ /// is the honest question.
+ ///
+ ///
+ [Fact]
+ public void TextField_Should_Revert_To_A_Single_Line_When_The_New_Configuration_Drops_Lines()
+ {
+ // Arrange
+ var multiLine = FormBuilder
+ .Create()
+ .AddField(x => x.Name, field => field
+ .WithLabel("Name")
+ .WithAttribute("Lines", 4))
+ .Build();
+
+ var component = Render>(parameters => parameters
+ .Add(p => p.Model, new TestModel())
+ .Add(p => p.Configuration, multiLine));
+
+ component.FindComponents().Count.ShouldBe(1);
+
+ // Act - the replacement field declares no Lines at all.
+ component.Render(parameters => parameters
+ .Add(p => p.Configuration, TextConfiguration("text")));
+
+ // Assert
+ component.FindComponents().ShouldBeEmpty();
+ component.FindComponents().Count.ShouldBe(1);
+ }
+
+ ///
+ /// A numeric field rebinds its Min when a different field declares another one (#335).
+ ///
+ ///
+ ///
+ /// The numeric component collects Min/Max/Step into a
+ /// Dictionary<string, object> through a helper that only ever adds when the
+ /// attribute is configured, then splats it with @attributes. Nothing removed a key, so
+ /// before this fix the dictionary accumulated across fields; it is now cleared on every reload.
+ ///
+ ///
+ /// ⚠️ Scope of this test. It swaps one bound for another rather than dropping it, because
+ /// omission cannot be expressed through a splat: Blazor retains a component parameter that
+ /// a later render stops supplying, so a field that declares no Min leaves
+ /// FluentNumberInput.Min holding the previous field's value even though FormCraft's
+ /// dictionary is correct. Expressing "unset" would mean FormCraft supplying Fluent's own defaults
+ /// (int.MinValue) explicitly, i.e. binding the bounds as real parameters instead of
+ /// splatting a dictionary. That is a change to how the Fluent numeric components are written and
+ /// is recorded as a follow-up rather than smuggled in here.
+ ///
+ ///
+ [Fact]
+ public void NumericField_Should_Rebind_Its_Min_When_The_Configuration_Is_Swapped()
+ {
+ // Arrange
+ // Typed as int? deliberately: AddIfConfigured reads it back with GetAttribute, so a
+ // plainly-boxed int would not match and the bound would never be configured at all. The
+ // existing numeric suite spells its Min/Max/Step the same way.
+ var component = Render>(parameters => parameters
+ .Add(p => p.Model, new NumericModel())
+ .Add(p => p.Configuration, BoundedConfiguration(5)));
+
+ // Asserted on what the Fluent input was actually bound, the way the existing numeric suite
+ // does: ExtraAttributes is splatted onto the component, so the dictionary's contents become
+ // its parameters.
+ component.FindComponent>().Instance.Min.ShouldBe(5);
+
+ // Act
+ component.Render(parameters => parameters
+ .Add(p => p.Configuration, BoundedConfiguration(9)));
+
+ // Assert
+ component.FindComponent>().Instance.Min.ShouldBe(9);
+ }
+
+ ///
+ /// A lookup keeps showing its stored value after a configuration swap (#335).
+ ///
+ ///
+ ///
+ /// The regression test for a fix that was almost worse than the bug. The first attempt reset
+ /// _displayText to empty in the hook — correct for staleness, and catastrophic on its own,
+ /// because nothing else in this component repopulates it from the model. The MudBlazor lookup
+ /// gets away with clearing because its OnParametersSet calls UpdateDisplayText() on
+ /// every render and repairs the blank on the same pass; the Fluent one has no such call, so a
+ /// field with a perfectly good stored value rendered empty for ever.
+ ///
+ ///
+ /// The hook now re-derives the text rather than clearing it, which is what "reload, not patch"
+ /// means when the property is derived rather than read.
+ ///
+ ///
+ [Fact]
+ public void LookupField_Should_Keep_Displaying_Its_Value_After_A_Configuration_Swap()
+ {
+ // Arrange
+ var model = new TripModel { CityId = 7 };
+
+ var component = Render>(parameters => parameters
+ .Add(p => p.Model, model)
+ .Add(p => p.Configuration, LookupConfiguration()));
+
+ component.FindComponent().Instance.Value.ShouldBe("7");
+
+ // Act - a different configuration object declaring the same lookup field.
+ component.Render(parameters => parameters
+ .Add(p => p.Configuration, LookupConfiguration()));
+
+ // Assert - the display still reflects the model, rather than having been blanked.
+ component.FindComponent().Instance.Value.ShouldBe("7");
+ }
+
+ private static IFormConfiguration LookupConfiguration() =>
+ FormBuilder
+ .Create()
+ .AddField(x => x.CityId, field =>
+ {
+ field.WithLabel("City");
+
+ // Called as a static method rather than as an extension on purpose: this project
+ // references BOTH adapters, and the MudBlazor package publishes an .AsLookup(...) of
+ // the same name into namespace FormCraft, so the extension form would be
+ // CS0121-ambiguous here. Same reasoning as FluentUILookupFieldComponentTests.
+ FluentUIFieldBuilderExtensions.AsLookup(
+ field,
+ dataProvider: _ => Task.FromResult(new LookupResult
+ {
+ Items = [new City(7, "Lisbon")],
+ TotalCount = 1,
+ }),
+ valueSelector: c => c.Id,
+ displaySelector: c => c.Name,
+ configureColumns: cols =>
+ cols.Add(new LookupColumn { Title = "Name", ValueSelector = c => c.Name }));
+ })
+ .Build();
+
+ private class TripModel
+ {
+ public int CityId { get; set; }
+ }
+
+ private record City(int Id, string Name);
+
+ private static IFormConfiguration BoundedConfiguration(int min) =>
+ FormBuilder
+ .Create()
+ .AddField(x => x.Amount, field => field
+ .WithLabel("Amount")
+ .WithAttribute("Min", (int?)min))
+ .Build();
+
+ private static IFormConfiguration TextConfiguration(string inputType) =>
+ FormBuilder
+ .Create()
+ .AddField(x => x.Name, field => field
+ .WithLabel("Name")
+ .WithAttribute("InputType", inputType))
+ .Build();
+
+ private class NumericModel
+ {
+ public int Amount { get; set; }
+ }
+}
diff --git a/FormCraft.ForFluentUI/Fields/AutocompleteField/FluentUIAutocompleteFieldComponent.razor.cs b/FormCraft.ForFluentUI/Fields/AutocompleteField/FluentUIAutocompleteFieldComponent.razor.cs
index 34d98d3b..d750e4de 100644
--- a/FormCraft.ForFluentUI/Fields/AutocompleteField/FluentUIAutocompleteFieldComponent.razor.cs
+++ b/FormCraft.ForFluentUI/Fields/AutocompleteField/FluentUIAutocompleteFieldComponent.razor.cs
@@ -30,10 +30,31 @@ protected override void OnInitialized()
{
base.OnInitialized();
+ SyncSelectedOption();
+ }
+
+ ///
+ ///
+ /// Moved off OnInitialized so a component instance handed a different field re-reads it
+ /// rather than rendering the previous field's settings (#335).
+ ///
+ protected override void OnFieldConfigurationChanged()
+ {
+ base.OnFieldConfigurationChanged();
+
_searchFunc = GetAttribute>>>>(
"AutocompleteSearchFunc");
_optionProvider = GetAttribute