Skip to content

chore(TabStrip): add kb for adding and removing tabs #2998

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions components/tabstrip/tabs-collection.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ To deactivate all tabs, set the ActiveTabId parameter to `string.Empty`.
}
````

## Add and Remove Tabs

If you are iterating through a collection to render the tabs and you need to allow the users to add and remove tabs, you may use the `ActiveTabId` parameter to set the active tab after adding and removing tabs. See details and example in this article: [Add and Remove Tabs](slug:tabstrip-kb-add-remove-tabs).


## See Also

* [TabStrip Events](slug:tabstrip-events)
186 changes: 177 additions & 9 deletions knowledge-base/tabstrip-dynamic-tabs.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
title: Dynamic Tabs
description: How to create Dynamic Tabs in TabStip.
title: Add and Remove TabStrip Tabs
description: Learn how to dynamically add and remove tabs
type: how-to
page_title: Dynamic Tabs
slug: tabstrip-kb-dynamic-tabs
position:
tags:
page_title: How to Add and Remove TabStrip Tabs
slug: tabstrip-kb-add-remove-tabs
tags: telerik,blazor,tabstrip,add tabs,remove tabs
ticketid:
res_type: kb
---

Expand All @@ -20,12 +20,180 @@ res_type: kb
</tbody>
</table>


## Description

How to create Dynamic Tabs in TabStip? How to add and remove tabs dynamically? How to get information about the currently active tab? How to set the content of the tabs dynamically?
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](slug:tabstrip-tabs-collection). 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

An example is available in the following project: [https://github.com/telerik/blazor-ui/tree/master/tabstrip/DynamicTabs](https://github.com/telerik/blazor-ui/tree/master/tabstrip/DynamicTabs).
1. [Render the TabStrip tabs in a loop](slug:tabstrip-tabs-collection).
1. Use a [`HeaderTemplate`](slug:tabstrip-header-template) for the tabs to add Remove buttons. You can display the buttons conditionally based on the tab count.
1. Declare a button for adding new tabs.
1. Use custom styling and JavaScript to position the Add button next to the last tab header.

>caption Adding and removing TabStrip tabs at runtime

````RAZOR
@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

* [Dynamic Tab Collection](slug:tabstrip-tabs-collection)
* [TabStrip Tab `HeaderTemplate`](slug:tabstrip-header-template)