Install
| Product | Versions |
|---|---|
| .NET |
README
Tabalonia
Draggable tab items on Avalonia here!
This is a port of the Draggablz

Features
- Drag tabs to reorder them within the strip (with animations)
- Tear a tab off into its own floating window β just drag it out of the strip
- Drag a tab over another window's strip to dock it there, Chrome-style: the tab attaches while you hold the button, keeps following the pointer, and detaches again if you drag it away
- Built-in add / close tab buttons
- Pin the first N tabs (
FixedHeaderCount) so they can't be dragged or closed - Custom content on both sides of the strip (
LeftContent/RightContent) - Two shipped themes (Custom with light/dark variants, Fluent), fully restylable via brush resources
- AOT-compatible, no reflection
Caly fork
Published as Caly.Tabalonia for Caly PDF.
Changes on top of upstream egorozh/Tabalonia:
New API
| Member | Type | Description |
|---|---|---|
MinTabItemWidth |
double, default 60 |
Floor for tab width β tabs stop shrinking here and the strip scrolls instead |
IsTabStripOverflowing |
bool (read-only) |
True when the tabs no longer fit the strip |
CanScrollLeft / CanScrollRight |
bool (read-only) |
Whether the strip can scroll further in each direction |
TabDragStarted |
EventHandler<DragTabDragStartedEventArgs>? |
Raised when a tab drag begins |
TabDragCompleted |
EventHandler<DragTabDragCompletedEventArgs>? |
Raised when the drag ends |
DetachedHostFactory |
Func<TabsControl, (TabsControl Host, Window Window)?>? |
Supplies both the host control and its window for a torn-off tab; falls back to DetachedWindowFactory when it returns null |
Behaviour
| Change | Detail |
|---|---|
| Horizontal tab scrolling | An overflowing strip gets βΉ / βΊ buttons (PART_ScrollTabsLeftButton / PART_ScrollTabsRightButton); selecting an off-screen tab scrolls it into view |
Reorder keeps DataContext |
Dragging shifts models in place instead of remove/insert, so containers never swap model |
| Last tab is not torn off | Dragging the only tab of a window moves the window instead; it can still be docked into another strip |
| Closing a dragged tab | The drag reference is cleared, so finishing the gesture can't resurrect or reorder the closed tab |
| Thumb handlers unsubscribed | DragTabItem detaches its thumb handlers on retemplate and on leaving the tree |
| Runtime resizing | TabItemWidth, MinTabItemWidth and AdjacentHeaderItemOffset now invalidate layout when changed after load |
Themes
| Change | Detail |
|---|---|
| Scroll buttons | Added to both themes; Custom gains TabScrollNavigationButton*Brush resources, Fluent gains TabScrollLeftButton / TabScrollRightButton control themes |
| Fluent tab headers | Header font 24 β 14 (DragTabItemHeaderFontSize), tighter padding (DragTabItemHeaderMargin), a gutter before the close button and CharacterEllipsis trimming β headers no longer collide with the Γ |
| Header tooltips | Tabs show their full header on hover, so a clipped tab stays readable |
Packaging
PackageId Caly.Tabalonia Β· MIT PackageLicenseExpression Β· Avalonia pinned via $(AvaloniaVersion) Β· compiled bindings on by default.
Getting Started
Install the library as a NuGet package:
Install-Package Caly.Tabalonia
# Or 'dotnet add package Caly.Tabalonia'
Add a theme to your App.axaml:
<Application xmlns="https://github.com/avaloniaui"
xmlns:custom="clr-namespace:Tabalonia.Themes.Custom;assembly=Tabalonia">
<Application.Styles>
<FluentTheme />
<custom:CustomTheme />
</Application.Styles>
</Application>
Or use the Fluent-styled variant instead of the custom one:
xmlns:fluent="clr-namespace:Tabalonia.Themes.Fluent;assembly=Tabalonia"
...
<fluent:FluentTheme />
Then use TabsControl like a regular TabControl:
<Window xmlns:controls="clr-namespace:Tabalonia.Controls;assembly=Tabalonia">
<controls:TabsControl ItemsSource="{Binding TabItems}"
NewItemFactory="{Binding NewItemFactory}"
FixedHeaderCount="1">
<TabControl.ItemTemplate>
<DataTemplate DataType="vm:TabItemViewModel">
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="vm:TabItemViewModel">
<TextBlock Text="{Binding SimpleContent}" />
</DataTemplate>
</TabControl.ContentTemplate>
</controls:TabsControl>
</Window>
ItemsSourcemust implementIList(e.g.ObservableCollection<T>) β adding, closing, reordering and dragging tabs between windows mutate the collection directly.
API Reference
TabsControl inherits Avalonia's TabControl, so everything you know
(ItemsSource, SelectedItem, ItemTemplate, ContentTemplate, β¦) works as usual.
The additions:
Tab strip appearance
| Property | Type | Default | Description |
|---|---|---|---|
TabItemWidth |
double |
140 |
Maximum tab width; tabs shrink evenly when the strip runs out of space |
AdjacentHeaderItemOffset |
double |
0 |
Horizontal offset between neighbouring tabs (negative values overlap them) |
FixedHeaderCount |
int |
0 |
Number of leading tabs that are pinned: they can't be dragged, closed or displaced |
LeftContent / RightContent |
object? |
null |
Arbitrary content rendered to the left / right of the tab strip |
Adding and closing tabs
| Member | Type | Description |
|---|---|---|
ShowDefaultAddButton |
bool, default true |
Shows the built-in β+β button after the last tab |
ShowDefaultCloseButton |
bool, default true |
Shows the βΓβ button on each tab (except fixed ones) |
NewItemFactory |
Func<object>? |
Creates the model for a new tab when the add button is clicked |
NewItemAsyncFactory |
Func<Task<object>>? |
Async variant; takes precedence over NewItemFactory |
AddItemCommand |
ICommand (read-only) |
Adds a tab via the factory β bind it from your own buttons |
CloseItemCommand |
ICommand (read-only) |
Closes the tab passed as the command parameter (DragTabItem) |
TabClosing |
EventHandler<TabClosingEventArgs>? |
Raised before a tab closes; set e.Cancel = true to keep it open |
TabClosed |
EventHandler<TabClosedEventArgs>? |
Raised after a tab closed; e.Item is the removed model |
LastTabClosedAction |
EventHandler<CloseLastTabEventArgs>? |
What to do when the last tab goes away; default closes the window (e.Window) |
tabs.TabClosing += (_, e) =>
{
if (e.Item is DocumentViewModel { IsDirty: true })
e.Cancel = true; // keep the tab open
};
tabs.TabClosed += (_, e) => Console.WriteLine($"Closed: {e.Item}");
// Keep the window open when the last tab is closed (default closes it):
tabs.LastTabClosedAction = (_, e) => { /* e.Window stays open */ };
Tearing off and docking tabs
| Property | Type | Default | Description |
|---|---|---|---|
EnableTabDetaching |
bool |
true |
Allows dragging a tab out of the strip into a new floating window |
EnableTabAttaching |
bool |
true |
Allows this strip to give away and accept tabs dragged from other TabsControls |
DetachTriggerDistance |
double |
32 |
How far (px) the pointer must leave the strip before the tab tears off |
DetachedWindowFactory |
Func<TabsControl, Window>? |
null |
Creates the host window for a detached tab; by default the source window's size, background and decorations are cloned |
The detached window's TabsControl inherits the source control's settings
(templates, factories, events, Margin, thumb widths, β¦), so torn-off tabs behave
exactly like the original ones β including docking back.
tabsControl.DetachedWindowFactory = tabs => new MyToolWindow { Content = tabs };
Window drag thumbs
The empty areas to the left and right of the tabs act as window-drag handles
(double-click restores/maximizes the window) β useful with
ExtendClientAreaToDecorationsHint="True" for Chrome-like windows:
| Property | Default | Description |
|---|---|---|
LeftThumbWidth |
80 on macOS, 4 elsewhere |
Width of the drag area before the first tab (macOS default leaves room for traffic lights) |
RightThumbWidth |
160 on Windows, 50 elsewhere |
Width of the drag area after the add button |
Styling
The custom theme supports light and dark variants out of the box and follows
Application.RequestedThemeVariant. All colors are exposed as brush resources
(TabItemBackgroundBrush, SelectedTabItemBackgroundBrush,
TabControlWindowActiveBackgroundBrush, CloseItemButtonForeground, β¦) β
override them in your App.axaml to restyle the control without retemplating:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="SelectedTabItemBackgroundBrush" Color="#1E1E2E" />
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</Application.Resources>
The full list of resource keys lives in
Themes/Custom/Brushes.axaml.
Building from source
git clone https://github.com/egorozh/Tabalonia.git
cd Tabalonia
dotnet build Tabalonia.sln
dotnet test Tabalonia.Tests/Tabalonia.Tests.csproj
dotnet run --project Tabalonia.Demo
See CONTRIBUTING.md for the contribution workflow and CHANGELOG.md for release history.
Changes from Previous Version
Adoption Assessment
Assess this package against its supported frameworks, license, maintenance signals and dependency graph before making it a project standard.
Related Guides & Workflows
Learn how to evaluate, integrate, or audit this package in your .NET project.