Repository navigation
fix: programmatic tab selection flips between tabs in nested FluentTabs (regression 5.0.0 -> 5.0.1) #5468
Description
🐛 Bug Report
After upgrading from 5.0.0 to 5.0.1, selecting a tab from code (@bind-ActiveTabId or SelectTabAsync) in a nested FluentTabs makes the active tab flip back and forth between two the two tabs indefinitely.
💻 Repro or Code Sample
Blazor Server (InteractiveServer), net10.0. Steps: click Add session, then Show results.
Home.razor:
@page "/"
@rendermode InteractiveServer
<FluentButton OnClick="AddSession">Add session</FluentButton>
<FluentButton OnClick="ShowResults">Show results</FluentButton>
@if (_sessions.Count > 0)
{
<FluentTabs @bind-ActiveTabId="_activeTabId">
@foreach (var session in _sessions)
{
<FluentTab Id="@($"tab-{session.Id}")" Header="@($"Session {session.Id}")">
<FluentTabs @bind-ActiveTabId="session.ActiveInnerTabId">
<FluentTab Id="@($"setup-{session.Id}")" Header="Setup">Setup content</FluentTab>
<FluentTab Id="@($"results-{session.Id}")" Header="Results" Visible="@session.HasResults">
<FluentTabs>
<FluentTab Header="Canonicals">Canonicals content</FluentTab>
<FluentTab Header="Greeks">Greeks content</FluentTab>
</FluentTabs>
</FluentTab>
</FluentTabs>
</FluentTab>
}
</FluentTabs>
}
@code {
private readonly List<Session> _sessions = [];
private string? _activeTabId;
private void AddSession()
{
var session = new Session(_sessions.Count + 1);
_sessions.Add(session);
_activeTabId = $"tab-{session.Id}";
}
private async Task ShowResults()
{
var session = _sessions[^1];
await Task.Run(() => Thread.Sleep(500)).ConfigureAwait(false);
session.HasResults = true;
await InvokeAsync(StateHasChanged).ConfigureAwait(false);
session.ActiveInnerTabId = $"results-{session.Id}";
await InvokeAsync(StateHasChanged).ConfigureAwait(false);
}
private sealed class Session(int id)
{
public int Id { get; } = id;
public bool HasResults { get; set; }
public string ActiveInnerTabId { get; set; } = $"setup-{id}";
}
}🤔 Expected Behavior
The inner tabs select Results and stay there (this is the behavior of 5.0.0).
😯 Current Behavior
The activeid of the inner fluent-tablist alternates between results and setup indefinitely.
Each server render writes activeid, the client immediately raises tabchange with that same value, and the handler then sets the opposite value.
- Same repro on 5.0.0: stable on Results.
- Using
SelectTabAsyncthrough@refinstead of the binding behaves the same. - Not selecting any tab from code (user clicks only) works fine, as does the outer (non-nested) tab selection.
💁 Possible Solution
It looks related to #5381 / #5382 ("Tabs: Simplify active tab ID handling"), which removed the _clientActiveTabId guard. OnParametersSet now always writes ActiveTabId back to the activeid attribute of fluent-tablist, which raises tabchange, which updates the bound value and re-renders.
🔦 Context
We upgraded Fluent UI from 5.0.0 to 5.0.1. Our pricing page selects the Results tab automatically once pricing is done. With 5.0.1 the tabs flip endlessly. As a workaround we no longer select the tab from code, so users have to click Results themselves.
🌍 Your Environment
- OS & Device: Windows on PC
- Browser: Chromium (Playwright)
- .NET and Fluent UI Blazor library Version: .NET 10 and 5.0.1 (works on 5.0.0)
Activity
fix(tabs): do not rewrite activeid for client-originated tab changes
fix(tabs): reapply programmatic selection without reintroducing the f…
fix(tabs): reapply programmatic selection through the client-displaye…
fix(tabs): prevent active tab feedback loop on programmatic selection (…
fix(tabs): do not rewrite activeid for client-originated tab changes
fix(tabs): reapply programmatic selection without reintroducing the f…
fix(tabs): reapply programmatic selection through the client-displaye…
Nacl-HCl commentedon Oct 28, 2026
Hi Denis Voituron (@dvoituron), quick question on the new Issue discussion declaration check for #5469: it asks the PR description to confirm the change was discussed in an issue with maintainer agreement before implementation, or that a documented exception applies.
To be transparent, this PR went up about two hours after #5468 was filed, with no discussion in the issue beforehand — so I can't honestly tick that box as worded. The change has since been discussed at length here in the PR review.
Would you consider that review discussion sufficient, or is there an exception you'd point me to? I'll follow whatever you advise — I just don't want to tick a box that doesn't match what happened.
dvoituron commentedon Oct 28, 2026
This new checkbox is primarily intended to prevent the creation of pull requests that add “unnecessary” new features. This requires work on the part of the pull request creator and on our part to discuss and verify the feature.
In this case, it’s more of a bug that needs to be fixed.
The best practice is therefore to check this box, as described in this paragraph.
https://github.com/microsoft/fluentui-blazor/blob/dev/docs/contributing.md#discuss-before-implementing
Concerning this PR, I find it complicated: is it possible to avoid adding variables like
_pendingAttributeRewriteand_pendingRewriteBase, which will quickly make the code difficult to maintain (no idea if this is possible)?