Tabs
Tabbed navigation with horizontal tab strip and content panels
Namespace: Beep.Skia.Controls — Inherits: MaterialControl → SkiaComponent
Table of Contents
Overview
The Tabs control provides tabbed navigation with a horizontal tab strip and associated content panels, rendered entirely with SkiaSharp. Each tab has a label, optional icon, and a content component that is shown when the tab is active. The active tab indicator is drawn as a 3px-thick underline that slides smoothly between tab positions using an animated transition.
The tab strip supports horizontal scrolling when the total tab width exceeds the control width, with fade indicators drawn at the edges when scrollable. Tab content is rendered directly within the control's body area below the strip.
Animated Indicator
The active-tab underline slides smoothly with a spring animation between tab positions.
Scrollable Tabs
When tabs overflow, horizontal scroll arrows appear and the strip becomes scrollable.
Icon Support
Each tab can display an SKPath icon above or beside its label text.
Content Hosting
Any SkiaComponent can be the content of a tab. Only the active tab's content is rendered.
Properties
| Property | Type | Description |
|---|---|---|
SelectedIndex |
int |
Zero-based index of the currently active tab. Setting this switches the visible content panel and moves the indicator. Default is 0. |
TabItems |
List<TabItem> |
Collection of TabItem models defining the tabs. Each contains text, icon, and a content component reference. |
TabPosition |
TabPosition |
Enum controlling tab strip placement: Top (default) or Bottom. Bottom-positioned tabs draw the indicator above instead of below. |
Scrollable |
bool |
When true and tabs exceed the available width, horizontal scroll controls become visible. Default is true. |
TabHeight |
float |
Height of the tab strip. Default is 48f. |
IndicatorHeight |
float |
Thickness of the active-tab underline. Default is 3f. |
ContentPadding |
float |
Padding around the content panel area. Default is 8f. |
TabMinWidth |
float |
Minimum width per tab header. Default is 90f. Tabs with short labels still receive this minimum width. |
TabMaxWidth |
float |
Maximum width per tab header. Default is 360f. Tabs grow to fill available space evenly up to this limit. |
TabItem Model
The TabItem class defines a single tab in the tab strip. It is a model class (not a SkiaComponent) that holds the data the Tabs control uses for rendering.
| Property | Type | Description |
|---|---|---|
Text |
string |
Label displayed on the tab. Rendered using TitleSmall typography for inactive tabs and TitleSmall + Primary color for the active tab. |
Icon |
SKPath |
Optional vector icon rendered above the text. When present, tab height expands to accommodate the icon. Icon color follows the same active/inactive state rules as text. |
Content |
SkiaComponent |
The component displayed when this tab is active. Only one tab's content is drawn at a time. The content's coordinate space is relative to the content panel's top-left corner. Set to null for tabs that have no content. |
IsEnabled |
bool |
When false, the tab appears dimmed and cannot be selected. Default is true. |
Tag |
object |
Arbitrary data attached to the tab. Not rendered or serialized by default. |
Badge |
string |
Optional badge text displayed as a small pill next to the tab label. Renderered in Error color on a small rounded background. |
Events
| Event | Signature | Description |
|---|---|---|
SelectedIndexChanged |
EventHandler<int> |
Raised when the active tab changes. The integer argument is the new SelectedIndex. Raised after the indicator animation begins but before it completes. |
Code Example
Basic Tabs with Multiple Content Panels
var tabs = new Tabs
{
X = 20,
Y = 20,
Width = 600,
Height = 400,
SelectedIndex = 0,
Scrollable = true
};
tabs.Initialize();
var generalContent = new Label
{
Text = "General settings content goes here.",
X = 0, Y = 0,
Width = 580, Height = 300
};
generalContent.Initialize();
var appearanceContent = new Card
{
Title = "Appearance",
Subtitle = "Color theme and font settings",
X = 0, Y = 0,
Width = 580, Height = 300
};
appearanceContent.Initialize();
var advancedContent = new Label
{
Text = "Advanced configuration options.",
X = 0, Y = 0,
Width = 580, Height = 300
};
advancedContent.Initialize();
tabs.TabItems.Add(new TabItem
{
Text = "General",
Content = generalContent
});
tabs.TabItems.Add(new TabItem
{
Text = "Appearance",
Content = appearanceContent
});
tabs.TabItems.Add(new TabItem
{
Text = "Advanced",
Content = advancedContent,
IsEnabled = false
});
tabs.SelectedIndexChanged += (s, index) =>
Console.WriteLine($"Tab switched to index {index}");
drawingManager.AddComponent(tabs);
drawingManager.RequestRedraw();
Tabs with Icons
var homeIcon = SKPath.ParseSvgPathData(
"M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z");
var settingsIcon = SKPath.ParseSvgPathData(
"M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 0 0 .12-.61l-1.92-3.32a.49.49 0 0 0-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94L14.4 2.81a.484.484 0 0 0-.41-.31h-3.98a.48.48 0 0 0-.41.31l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96a.49.49 0 0 0-.59.22L2.72 8.87c-.12.2-.07.44.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58a.49.49 0 0 0-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.22.23.38.41.31h3.98c.19 0 .36-.09.41-.31l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.44-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z");
tabs.TabItems.Clear();
tabs.TabItems.Add(new TabItem { Text = "Home", Icon = homeIcon, Content = homePanel });
tabs.TabItems.Add(new TabItem { Text = "Settings", Icon = settingsIcon, Content = settingsPanel });
Tip
For dynamic tab content that changes at runtime, update the Content property of the relevant TabItem and invalidate the tabs control. The newly assigned component will be rendered on the next draw cycle without needing to recreate the tabs control.