New to Telerik UI for BlazorStart a free 30-day trial

Add and Remove TabStrip Tabs

Updated on Jun 13, 2025

Environment

ProductTabStrip for Blazor

Description

I have a collection of items representing separate tabs. I am iterating through that collection to render a tab for each item as shown in the Tabs Collection article. I want to allow the user to add and remove tabs. How to achieve that?

This KB article also answers the following questions:

  • How to implement add and remove tab functionality with the Telerik TabStrip component.
  • How to remove a tab using an "X" button in the tab header.
  • How to add a new tab with a "+" button, similar to browser tab controls.
  • How to position the add ("+") button next to the last tab header.

Solution

  1. Render the TabStrip tabs in a loop.
  2. Use a HeaderTemplate for the tabs to add Remove buttons. You can display the buttons conditionally based on the tab count.
  3. Declare a button for adding new tabs.
  4. Use custom styling and JavaScript to position the Add button next to the last tab header.

Adding and removing TabStrip tabs at runtime

@inject IJSRuntime JS

<div class="dynamic-tabstrip-wrapper k-relative">
    <TelerikButton OnClick="@AddTab"
                   Class="add-tab-button !k-absolute k-z-10 k-ratio-1"
                   FillMode="@ThemeConstants.Button.FillMode.Flat"
                   Icon="@SvgIcon.Plus"
                   ThemeColor="@ThemeConstants.Button.ThemeColor.Primary" />

    <TelerikTabStrip @bind-ActiveTabId="@ActiveTabId" PersistTabContent="true">
        @foreach (Tab tab in Tabs)
        {
            <TabStripTab @key="tab.Id" Id="@tab.Id">
                <HeaderTemplate>
                    <div class="k-flex-layout k-gap-2">
                        <span>@tab.Title</span>
                        @if (Tabs.Count > 1)
                        {
                            <TelerikButton OnClick="@(() => RemoveTab(tab))"
                                           Class="remove-tab-button"
                                           FillMode="@ThemeConstants.Button.FillMode.Flat"
                                           Icon="@SvgIcon.X"
                                           Size="@ThemeConstants.Button.Size.Small"
                                           ThemeColor="@ThemeConstants.Button.ThemeColor.Primary" />
                        }
                    </div>
                </HeaderTemplate>
                <Content>
                    Content for <strong>@tab.Title</strong>
                </Content>
            </TabStripTab>
        }
    </TelerikTabStrip>
</div>

<style>
    .dynamic-tabstrip-wrapper {
        --add-tab-button-size: 32.8px;
    }

        .dynamic-tabstrip-wrapper .k-tabstrip-items {
            max-width: calc(100% - var(--add-tab-button-size));
        }

        .dynamic-tabstrip-wrapper .add-tab-button {
            width: var(--add-tab-button-size);
            padding-block: 6px;
            z-index: 10000;
        }

    .remove-tab-button {
        padding: 0 !important;
    }
</style>

@* Move JavaScript code to a JS file *@
<script suppress-error="BL9992">
    window.positionAddTabButton = () => {
        const tabStripItems = Array.from(document.querySelectorAll(".dynamic-tabstrip-wrapper .k-tabstrip-item"));
        const tabStripWrapperWidth = document.querySelector(".dynamic-tabstrip-wrapper").scrollWidth;

        let totalWidth = !tabStripItems ? 0 : tabStripItems.reduce(
            (accumulator, currentItem) => accumulator + parseFloat(currentItem.getBoundingClientRect().width),
            0,
        );

        const addTabButton = document.querySelector(".add-tab-button");
        const addTabButtonWidth = addTabButton.getBoundingClientRect().width;

        // assure button is never positioned outside the boundaries of the wrapper
        if (totalWidth + addTabButtonWidth > tabStripWrapperWidth) {
            totalWidth = tabStripWrapperWidth - addTabButtonWidth;
        }

        addTabButton.style.left = `${totalWidth}px`;
    };
</script>


@code {
    private List<Tab> Tabs = new List<Tab>()
    {
        new Tab { Title = "Tab 1" },
        new Tab { Title = "Tab 2" },
        new Tab { Title = "Tab 3" }
    };

    private string ActiveTabId { get; set; } = string.Empty;

    private bool ShouldPositionAddButton { get; set; }

    private int LastTabNumber { get; set; } = 3;

    private void AddTab()
    {
        Tab tabToAdd = new Tab { Id = Guid.NewGuid().ToString(), Title = $"New Tab {++LastTabNumber}" };

        Tabs.Add(tabToAdd);

        //In this example, we are always activating the newly added tab. Adjust the logic to activate a different tab if needed.
        ActiveTabId = tabToAdd.Id;

        ShouldPositionAddButton = true;
    }

    private void RemoveTab(Tab tab)
    {
        if (Tabs.Count <= 1)
        {
            return;
        }

        // Activate the tab after or before the removed one if it's active
        if (ActiveTabId == tab.Id)
        {
            int removedTabIndex = Tabs.FindIndex(x => x.Id == tab.Id);
            if (removedTabIndex == Tabs.Count - 1)
            {
                ActiveTabId = Tabs.ElementAt(removedTabIndex - 1).Id;
            }
            else
            {
                ActiveTabId = Tabs.ElementAt(removedTabIndex + 1).Id;
            }
        }

        Tabs.Remove(tab);

        ShouldPositionAddButton = true;
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender || ShouldPositionAddButton)
        {
            ShouldPositionAddButton = false;
            await JS.InvokeVoidAsync("positionAddTabButton");
        }

        await base.OnAfterRenderAsync(firstRender);
    }

    public class Tab
    {
        public string Id { get; set; } = Guid.NewGuid().ToString();

        public string Title { get; set; } = string.Empty;
    }

}

See Also