Background
Thanks to @kankankan from the WeChat "Avalonia Development Discussion Group" for providing the code example:


The image below shows the modified effect according to personalized requirements:

To be compatible with the Semi.Avalonia theme style, our TabControl control theme starts by referencing Semi's Card-style control theme. The effect of Semi is as follows:

After our modifications, the display effect when switching between themes is as follows:

Usage
It is recommended to copy the control code from this article and maintain it yourself, as this control may not be updated in a timely manner.
This control is a secondary development based on Semi, so the following NuGet packages need to be installed:
Install-Package Semi.Avalonia -Version 11.2.1.8
Install-Package CodeWF.AvaloniaControls -Version 0.1.1.6
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="CodeWF.AvaloniaControls.Demo.App"
xmlns:semi="https://irihi.tech/semi"
xmlns:codewf="https://codewf.com">
<Application.Styles>
<semi:SemiTheme Locale="zh-CN" />
<codewf:CodeWFTheme />
</Application.Styles>
</Application>
Usage reference, the effect has been shown earlier, the code is as follows:
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="CodeWF.AvaloniaControls.Demo.Pages.TabControlDemo">
<Grid RowDefinitions="20 Auto 20 Auto" ColumnDefinitions="20 * 20">
<TabControl Grid.Row="1" Grid.Column="1" VerticalAlignment="Top"
Theme="{StaticResource TrapezoidShapedTabControl}"
CornerRadius="10 10 0 0" TabStripPlacement="Top">
<TabControl.Styles>
<Style Selector="TabItem">
<Setter Property="CornerRadius" Value="10 10 0 0" />
<Setter Property="Padding" Value="12 8" />
</Style>
</TabControl.Styles>
<TabItem Header="Data Management" />
<TabItem Header="System Settings" />
<TabItem Header="User Center" />
<TabItem Header="Log Records" />
<TabItem Header="Help Documentation" />
</TabControl>
<TabControl Grid.Row="3" Grid.Column="1" VerticalAlignment="Top"
Theme="{StaticResource TrapezoidShapedTabControl}"
CornerRadius="10 10 0 0" TabStripPlacement="Top">
<TabControl.Styles>
<Style Selector="TabControl">
<Setter Property="Background" Value="#551890FF"></Setter>
</Style>
<Style Selector="TabItem">
<Setter Property="CornerRadius" Value="10 10 0 0" />
<Setter Property="Foreground" Value="#FFFFFF" />
<Setter Property="Padding" Value="12 8" />
<Setter Property="MinHeight" Value="40" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="50%, 0%"
EndPoint="50%, 100%">
<GradientStops>
<GradientStop Color="#BAE7FF" Offset="0" />
<GradientStop Color="#FFFFFF" Offset="1" />
</GradientStops>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
</TabControl.Styles>
<TabControl.Resources>
<SolidColorBrush x:Key="TabItemLineHeaderPointeroverForeground">#1890FF</SolidColorBrush>
<SolidColorBrush x:Key="TabItemLineHeaderSelectedForeground">#1890FF</SolidColorBrush>
</TabControl.Resources>
<TabItem Header="Data Management" />
<TabItem Header="System Settings" />
<TabItem Header="User Center" />
<TabItem Header="Log Records" />
<TabItem Header="Help Documentation" />
</TabControl>
</Grid>
</UserControl>
Implementation
Explaining code is always tedious, so I'll give a general overview.
This is the ControlTheme code for Semi's TabControl:

Our main modification is to change the border style of TabItem, so we copy and paste this Semi code directly, give the ControlTheme a different Key, and point the Value of the ItemContainerTheme (the boxed part in the image) to another TabItem control theme. Other parts of the code are modified as needed; I didn't touch the rest. Below is a screenshot of the modified code:

The TabItem control theme code is as follows. The key code is the custom border code location:

Among them, TrapezoidShapedTabItemBorder inherits from Control and mainly overrides its Render method:
public partial class TrapezoidShapedTabItemBorder : Control
{
public const double DiagonalFilletRatio = 0.8;
public static readonly StyledProperty<IBrush> BorderBrushProperty =
AvaloniaProperty.Register<TrapezoidShapedTabItemBorder, IBrush>(nameof(BorderBrush),
new SolidColorBrush(Color.Parse("#05CCCCCC")));
public static readonly StyledProperty<double> BorderThicknessProperty =
AvaloniaProperty.Register<TrapezoidShapedTabItemBorder, double>(nameof(BorderThickness), 1);
public static readonly StyledProperty<IBrush> BackgroundProperty =
AvaloniaProperty.Register<TrapezoidShapedTabItemBorder, IBrush>(nameof(Background), Brushes.DarkGreen);
public IBrush BorderBrush
{
get => GetValue(BorderBrushProperty);
set => SetValue(BorderBrushProperty, value);
}
public double BorderThickness
{
get => GetValue(BorderThicknessProperty);
set => SetValue(BorderThicknessProperty, value);
}
public IBrush Background
{
get => GetValue(BackgroundProperty);
set => SetValue(BackgroundProperty, value);
}
public override void Render(DrawingContext context)
{
base.Render(context);
if (BorderThickness < 1)
{
return;
}
if (Parent?.Parent?.Parent is not TabControl tabControl ||
Parent?.Parent is not TabItem currentTabItem)
{
return;
}
var index = tabControl.Items.IndexOf(currentTabItem);
var isFirst = index == 0;
var isLast = index == tabControl.Items.Count - 1;
var radius = currentTabItem.CornerRadius;
// Get the control's dimensions
var rect = new Rect(Bounds.Size);
var borderThickness = BorderThickness;
// Offset the path to align lines with the pixel grid
var halfBorder = borderThickness / 2.0;
var adjustedRect = rect.Deflate(halfBorder);
// Set border path
var pathGeometry = new StreamGeometry();
using (var ctx = pathGeometry.Open())
{
if (isFirst & !isLast)
{
if (tabControl.TabStripPlacement == Dock.Top)
{
DrawTopFirstTabItemBorder(ctx, adjustedRect, radius, rect);
}
}
else if (!isFirst && isLast)
{
if (tabControl.TabStripPlacement == Dock.Top)
{
DrawTopLastTabItemBorder(ctx, adjustedRect, radius, rect);
}
}
else
{
if (tabControl.TabStripPlacement == Dock.Top)
{
DrawTopOtherTabItemBorder(ctx, adjustedRect, radius, rect);
}
}
// bottom edge disappears (not drawn)
// Here we skip the bottom edge path directly to ensure the bottom edge disappears
ctx.EndFigure(isClosed: true);
}
// Draw the border
context.DrawGeometry(Background, new Pen(BorderBrush, BorderThickness)
{
Thickness = BorderThickness,
LineJoin = PenLineJoin.Round, // rounded join
LineCap = PenLineCap.Round // rounded line cap
}, pathGeometry);
}
}
In Render, depending on whether the current TabItem is the first, last, or middle one in the TabControl, different methods are called to draw the border. For example, drawing the first TabItem:

Analysis:
- This is a right-angled trapezoid.
- The left side is a vertical straight line.
- The top-left corner is a 1/4 inner arc.
- The top-right corner is also an inner arc (can be drawn proportionally).
- The right side is a slanted line with a slope.
- The bottom-left and bottom-right corners can have outer arcs.
The border drawing code is as follows:
private static void DrawTopFirstTabItemBorder(StreamGeometryContext ctx, Rect adjustedRect, CornerRadius radius,
Rect rect)
{
var x = adjustedRect.Left;
var y = adjustedRect.Bottom;
// Start at bottom-left
ctx.BeginFigure(new Point(x, y), isFilled: true);
// bottom-left outer arc
if (radius.BottomLeft > 0)
{
x = rect.Left + radius.BottomLeft;
y = adjustedRect.Bottom - radius.BottomLeft;
ctx.ArcTo(
new Point(x, y),
new Size(radius.BottomLeft, radius.BottomLeft),
0,
false,
SweepDirection.CounterClockwise);
}
// left straight line
y = adjustedRect.Top + radius.TopLeft;
ctx.LineTo(new Point(x, y));
// top-left inner arc
if (radius.TopLeft > 0)
{
x += radius.TopLeft;
y = adjustedRect.Top;
ctx.ArcTo(
new Point(x, y),
new Size(radius.TopLeft, radius.TopLeft),
0,
false,
SweepDirection.Clockwise);
}
// top straight line
x = adjustedRect.Right - radius.TopRight * 2 - radius.BottomRight * 2;
ctx.LineTo(new Point(x, y));
// top-right inner arc
if (radius.TopRight > 0)
{
x += radius.TopRight;
y += radius.TopRight * DiagonalFilletRatio;
ctx.ArcTo(
new Point(x, y),
new Size(radius.TopRight, radius.TopRight),
0,
false,
SweepDirection.Clockwise);
}
// right slanted line
x = adjustedRect.Right - radius.BottomRight;
y = adjustedRect.Bottom - radius.BottomRight;
ctx.LineTo(new Point(x, y));
// bottom-right outer arc
if (radius.BottomRight > 0)
{
x = rect.Right;
y = adjustedRect.Bottom;
ctx.ArcTo(
new Point(x, y),
new Size(radius.BottomRight, radius.BottomRight),
0,
false,
SweepDirection.CounterClockwise);
}
}
By calling the LineTo method of StreamGeometryContext to draw straight lines and the ArcTo method to draw arcs (inner and outer arcs), various border styles can be drawn. The drawing methods for the last TabItem and middle TabItems are similar.
The Edge TabItem effect is easy to achieve now, right?

Summary
This article only gives a rough idea. You can look at the specific implementation code and draw inferences; other control effects can be achieved in a similar way.
Repositories:
CodeWF.AvaloniaControls: https://github.com/dotnet9/CodeWF.AvaloniaControls
CodeF.ToolBox: https://github.com/dotnet9/CodeWF.Toolbox