Namespace: Beep.Skia.Controls — Inherits: MaterialControl → SkiaComponent

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

PropertyTypeDescription
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.

PropertyTypeDescription
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

EventSignatureDescription
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.