To use RfTabList, create a tab list container and define individual tabs using RfTab components.
Each tab has a unique identifier and contains both the tab header content and the panel content.
The RfTabList manages tab state through a RfTabListContext which is passed down to child tabs
via a CascadingParameter.
RfTabList Component
The main container that manages all tabs and handles keyboard navigation.
| Parameter |
Type |
Description |
ActiveTabId |
string |
The identifier of the currently active tab. Required parameter. |
ActiveTabIdChanged |
EventCallback<string> |
Callback invoked when the active tab ID changes. |
TabListAriaLabel |
string |
ARIA label for the tab list element for accessibility. |
CssClass |
string |
CSS classes to apply to the component wrapper. |
TabsCssClass |
string |
CSS classes for the tabs container. Use Bulma classes like .is-boxed, .is-toggle, etc. |
PanelCssClass |
string |
CSS classes for the panel wrapper element. |
ChildContent |
RenderFragment |
Content containing RfTab components. |
RfTab Component
Individual tab component that defines both the tab header and panel content.
| Parameter |
Type |
Description |
TabId |
string |
Unique identifier for the tab. Required parameter. |
Tab |
RenderFragment |
Content to render in the tab header. |
Panel |
RenderFragment |
Content to render in the tab panel. |
LoadingPanel |
RenderFragment |
Content to display while the tab is loading. |
ShowPanelWhenHidden |
bool |
Whether to render panel content even when not active (for SEO). Default: false. |
TabCssClass |
string |
CSS classes to apply to the tab element. |
PanelCssClass |
string |
CSS classes to apply to the panel element. |
OnShow |
EventCallback<string> |
Callback invoked when the tab becomes visible. |
OnLoad |
EventCallback<string> |
Callback invoked when the tab is loaded for the first time. |
OnHide |
EventCallback<string> |
Callback invoked when the tab is hidden. |
Keyboard Navigation
The tab list supports full keyboard navigation:
- Arrow Left/Right: Navigate between tabs
- Home: Focus first tab
- End: Focus last tab
Basic Example
Below shows a basic example of setting up the RfTabList with multiple tabs.
<RfTabList @bind-ActiveTabId="ActiveTabId">
<RfTab TabId="tab1">
<Tab>First Tab</Tab>
<Panel>
<p>This is the content of the first tab.</p>
</Panel>
</RfTab>
<RfTab TabId="tab2">
<Tab>Second Tab</Tab>
<Panel>
<p>This is the content of the second tab.</p>
</Panel>
</RfTab>
<RfTab TabId="loading-tab" OnLoad="LoadTabContent">
<Tab>Loading Tab</Tab>
<LoadingPanel>
<p>Loading content, please wait...</p>
</LoadingPanel>
<Panel>
<p>Content loaded successfully!</p>
</Panel>
</RfTab>
</RfTabList>
NOTE: Each tab must have a unique TabId within the tab list.
The OnLoad callback is only called once when the tab is first activated.
Loading States
Tabs can display loading content using the LoadingPanel render fragment.
During loading, the tab will show a loading indicator and call the OnLoad callback.
When OnLoad Fails
If your OnLoad callback throws, the exception still reaches the caller, but the tab does not
stay stuck: the loading indicator is cleared and the tab is not marked as loaded, so activating it again
runs OnLoad again. Use this to retry a failed load rather than leaving the user on a spinner.
Disposal-Safe Loading
An OnLoad callback is an async task owned by your page, not by RfTabList.
The tab list shows the LoadingPanel until your callback returns, but it does not cancel
the callback when the user navigates away mid-load: Blazor disposes the page while your await is
still in flight, and the continuation keeps running. Calling StateHasChanged (or
updating any state that triggers a re-render) at that point throws
ObjectDisposedException and surfaces as an unhandled exception on the circuit.
The guard belongs in the hosting page: implement IDisposable, cancel a
CancellationTokenSource in Dispose, pass its token to the awaited work,
treat OperationCanceledException as a normal early return, and gate every post-load
state update behind a disposed flag. The Loader Example and the Tab List playground implement
exactly this pattern.
@implements IDisposable
@code {
// Cancelled in Dispose so in-flight loads abort instead of resuming
// onto a dead circuit.
private CancellationTokenSource LoadCancellation { get; } = new();
// True once Dispose has run; gates every post-load state update.
private bool IsDisposed { get; set; }
private void SetLoadedAt(string value)
{
if (IsDisposed)
{
// Navigated away mid-load: there is no renderer left to update.
return;
}
LoadedAt = value;
StateHasChanged();
}
private async Task LoadTabContent(string tabId)
{
try
{
await Task.Delay(3000, LoadCancellation.Token); // Or your real async fetch
}
catch (OperationCanceledException)
{
return; // Disposed mid-load: abandon the continuation.
}
SetLoadedAt(DateTime.Now.ToString("HH:mm:ss"));
}
public void Dispose()
{
IsDisposed = true;
LoadCancellation.Cancel();
LoadCancellation.Dispose();
}
}
Async Context Events
Tabs and the tab list communicate through RfTabListContext, whose
OnTabRegister event is async and multicast: each RfTab raises it
while registering and the RfTabList subscribes to adopt the tab. Extra
subscribers can attach to the same event.
Raising the event awaits every subscriber before it returns, so a tab is
not considered registered until all handlers finish their tasks. Subscribers are invoked in
registration order and run to completion. If exactly one subscriber faults, its exception is
rethrown unchanged to the raiser; if more than one faults, the faults are combined into a
single AggregateException. A subscriber that throws before its first
await does not stop the others from running.
Styling with Bulma
The tab list integrates with Bulma CSS framework classes:
.is-boxed - Boxed tabs
.is-toggle - Toggle-style tabs
.is-fullwidth - Full width tabs
.is-centered - Centered tabs
.is-right - Right-aligned tabs