Not a full translation; parts that may be inaccurate are kept as-is.
Create a Blazor TabControl component with two learning objectives:
- Pass data into a RenderFragment to give it context.
- Use a CascadingParameter to pass the parent TabControl component into its child TabPage components.
Below is the final effect:

Let's start:
First, create a Blazor project (Blazor Client or Server, we'll use Blazor Server as an example).
Step 1: Create two components: TabControl and TabPage. The TabPage component has a reference to its parent TabControl property (property name Parent, decorated with CascadingParameter).
TabControl component:
File path: ./Shared/TabControl.razor
<div>This is a TabControl</div>
<CascadingValue Value="this"> @ChildContent </CascadingValue>
@code {
// If we want to use TabPage in the form of <TabPage> tag, the following code is necessary
[Parameter] public RenderFragment? ChildContent { get; set; }
}
TabPage component:
File path: ./Shared/TabPage.razor
<div>This is a TabPage</div>
@ChildContent
@code {
[CascadingParameter] private TabControl? Parent { get; set; }
[Parameter] public RenderFragment? ChildContent { get; set; }
protected override void OnInitialized()
{
if (Parent == null)
throw new ArgumentNullException(nameof(Parent), "TabPage must contain a TabControl reference");
base.OnInitialized();
}
}
TabControl associates TabPage
Add the following line in TabPage's OnInitialized method to associate TabPage with TabControl:
Parent.AddPage(this);
The AddPage method is shown in the code below. After saving the reference in TabControl, we add the ActivePage property in TabControl. See the code below:
public TabPage? ActivePage { get; set; }
readonly List<TabPage> _pages = new();
internal void AddPage(TabPage tabPage)
{
_pages.Add(tabPage);
if (_pages.Count == 1)
ActivePage = tabPage;
StateHasChanged();
}
Add a Text property to the TabPage component for display.
[Parameter]
public string? Text { get; set; }
Add the following markup in TabControl (before rendering ChildContent). These tags will be rendered all at once; clicking a tab changes the selection in TabControl.
<div class="btn-group" role="group">
@foreach (TabPage tabPage in Pages)
{
<button type="button"
class="btn @GetButtonClass(tabPage)"
@onclick=@( ()=>ActivatePage(tabPage) )>
@tabPage.Text
</button>
}
</div>
These tags create a standard Bootstrap button group. Each TabPage creates a button with the following features:
- The CSS class is set to "btn" and additional CSS classes are appended via the
GetButtonClassmethod: if the currentTabPageis theActivePage, add the CSS classbtn-primary, otherwise addbtn-secondary. - Clicking the button activates the clicked
TabPage.
Note: @onclick requires a parameterless method, so the lambda expression uses an inline @( ) to set the clicked TabPage as ActivePage.
- The button text is set via the
TabPage'sTextproperty.
Add the following code to the code section of TabControl.
string GetButtonClass(TabPage page)
{
return page == ActivePage ? "btn-primary" : "btn-secondary";
}
void ActivatePage(TabPage page)
{
ActivePage = page;
}
Using TabControl
Add a TabControlTest component:
File name: ./Pages/TabControlTest.razor
@page "/tabcontroltest"
<TabControl>
<TabPage Text="Tab 1">
<h1>The first tab</h1>
</TabPage>
<TabPage Text="Tab 2">
<h1>The second tab</h1>
</TabPage>
<TabPage Text="Tab 3">
<h1>The third tab</h1>
</TabPage>
</TabControl>
@code { }
Add the TabControlTest route in ./Shared/NavMenu.
<!-- omitted part of the code -->
<div class="nav-item px-3">
<NavLink class="nav-link" href="tabcontroltest">
<span class="oi oi-plus" aria-hidden="true"></span> TabControl Test
</NavLink>
</div>
<!-- omitted part of the code -->
Is it done? Let's see the current result:

That's not right – all three TabPage contents are displayed. To fix this, simply check in TabPage when rendering ChildContent whether the current TabPage is the selected page in TabControl: only render if selected.
@if (Parent.ActivePage == this)
{
@ChildContent
}
OK, code complete. The effect is as shown at the beginning of this article.
The code in this article has been placed on: GitHub