Tabs

Organize content into switchable panels. Supports keyboard navigation, icons, badges, HTMX lazy-loaded panels, and URL-hash sync.

Groups tab is selected
Likes tab is selected
Comments tab is selected
Saved tab is selected
Usage Example
@tabs.Tabs(tabs.Config{
    ID: "demo",
    Tabs: []tabs.Tab{
        {ID: "groups", Label: "Groups", Content: groupsContent()},
        {ID: "likes", Label: "Likes", Content: likesContent()},
    },
})

With Icons

Give each Tab an Icon component to render before its label.

Groups tab is selected
Likes tab is selected
Comments tab is selected
Saved tab is selected
With Icons
@tabs.Tabs(tabs.Config{
    ID: "icon-demo",
    Tabs: []tabs.Tab{
        {ID: "groups", Label: "Groups", Icon: groupsIcon(), Content: groupsContent()},
        {ID: "likes", Label: "Likes", Icon: likesIcon(), Content: likesContent()},
    },
})

With Icons and Badges

Set Tab.Badge to show a count pill alongside the label.

Groups tab is selected
Likes tab is selected
Comments tab is selected
Saved tab is selected
With Icons and Badges
@tabs.Tabs(tabs.Config{
    ID: "badge-demo",
    Tabs: []tabs.Tab{
        {ID: "groups", Label: "Groups", Icon: groupsIcon(), Badge: "2", Content: groupsContent()},
        {ID: "likes", Label: "Likes", Icon: likesIcon(), Badge: "3124", Content: likesContent()},
    },
})

Custom Labels

Use LabelSlot for rich tab labels while Label remains the accessible name.

200 response is selected
404 response is selected
202 response is selected
Custom Labels
@tabs.Tabs(tabs.Config{
    ID: "status-demo",
    Tabs: []tabs.Tab{
        {ID: "ok", Label: "200", LabelSlot: statusBadge("200", badge.Success), Content: responseContent("200")},
        {ID: "missing", Label: "404", LabelSlot: statusBadge("404", badge.Danger), Content: responseContent("404")},
    },
})

HTMX Lazy Loading

Give a Tab an HTMX config instead of Content; its panel is fetched from the server the first time it's opened.

Overview tab is selected
Loading...
Loading...
HTMX Lazy Loading
@tabs.Tabs(tabs.Config{
    ID: "htmx-demo",
    Tabs: []tabs.Tab{
        {ID: "overview", Label: "Overview", Content: overviewContent()},
        {ID: "details", Label: "Details", HTMX: &tabs.TabHTMX{Get: "/api/components/tab-content/details"}},
    },
})

URL Hash Sync

SyncHash: true mirrors the active tab in the URL fragment, so switching updates the hash and deep links like #comments restore on load.

Groups tab is selected
Likes tab is selected
Comments tab is selected
Saved tab is selected
URL Hash Sync
@tabs.Tabs(tabs.Config{
    ID:       "my-tabs",
    SyncHash: true,
    Tabs: []tabs.Tab{
        {ID: "groups", Label: "Groups", Content: groupsContent()},
        {ID: "comments", Label: "Comments", Content: commentsContent()},
    },
})

API Reference

PropTypeDefaultDescription
IDstring""Unique tab-set id (scopes Alpine state and panel ids).
Tabs[]TabnilTab definitions (ID, Label, optional LabelSlot, Icon, Badge, Content, HTMX).
DefaultTabstring""ID of the initially active tab (defaults to the first).
SyncHashboolfalseMirror the active tab in the URL fragment.
RootClassstring""Extra classes on the tab-set container.