# Tabs A tabbed interface. One tab panel is visible at a time. The active tab has a highlighted style; all others are hidden. Client-side JS switches panels without a server round-trip. --- ## HTML structure ``` div[id].tabs-root div.tabs-list.flex.gap-1.border-b.mb-4 ← tab button strip button.tabs-trigger[data-tab={tabId}] ← one per tab; ACTIVE/INACTIVE variant {label} div.tabs-panel[data-tab={tabId}] ← one per tab; hidden or visible {content} ``` --- ## CSS mechanics | Class | Effect | |---|---| | `tabs-trigger` | `px-4 py-2 text-sm font-medium rounded-t-md -mb-px` | | Active trigger | `bg-background border border-b-0 border-border text-foreground` | | Inactive trigger | `text-muted-foreground hover:text-foreground hover:bg-muted/40` | | `tabs-panel[hidden]` | `display: none` via the HTML `hidden` attribute | --- ## JavaScript (`initTabs` in `components.js`) Runs on `DOMContentLoaded` and `htmx:afterSwap`. **Per-instance initialization:** 1. Guard `_tabsInit` prevents double-binding 2. Reads all `.tabs-trigger` and `.tabs-panel` elements within the root 3. Activates the first tab on init (removes `hidden`, applies active class) 4. On trigger click: - Deactivate all panels (set `hidden`, downgrade trigger class to inactive) - Activate the clicked panel by matching `data-tab` attribute - Apply active class to the clicked trigger --- ## Constructor signature ```csharp public Tabs( string id, IEnumerable<(string Id, string Label, string Content)> tabs) ``` | Parameter | Description | |---|---| | `id` | Root element id — must be unique per page if multiple Tabs are rendered | | `tabs` | List of `(Id, Label, Content)` tuples; `Id` must be unique within this instance | --- ## Usage examples ### Simple tabbed content ```csharp new Tabs( id: "settings-tabs", tabs: new[] { ("general", "General", "
General settings content here.
"), ("security", "Security", "Security settings content here.
"), ("billing", "Billing", "Billing details here.
"), }) ``` ### HTML-rich content in a tab ```csharp new Tabs( id: "code-tabs", tabs: new[] { ("csharp", "C#", "var x = 42;"),
("fsharp", "F#", "let x = 42"),
("vb", "VB.NET", "Dim x As Integer = 42"),
})
```
### Embedding a full component in a tab
```csharp
// Pre-render the inner component to HTML string
var buf = new System.Buffers.ArrayBufferWriterHigh level numbers.
"), ("detail", "Detail", tableHtml), }) ``` ### Multiple independent tab groups ```csharp new Tabs(id: "tabs-a", tabs: setA) new Tabs(id: "tabs-b", tabs: setB) ``` The `id` scopes JS initialization — each Tabs instance is independent. --- ## Tips and tricks - The `Id` of each tab tuple is used as the `data-tab` attribute — keep it URL-safe and unique within the instance. - The first tab is always activated on page load regardless of which tab was active before navigation. - Tab `Content` is raw HTML — HTML-encode any user-supplied values. - For lazy-loaded tab content, place HTMX attributes in the `Content` string and use `hx-trigger="revealed"` to load content when the panel becomes visible. - Tabs do not push to the URL hash by default. If you need deep-linkable tabs, listen to the `click` event on `.tabs-trigger` elements and update `location.hash`. - Tabs do not push to the URL hash by default. If you need deep-linkable tabs, listen to the `click` event on `.tabs-trigger` elements and update `location.hash`. --- ## Complete page example **`Templates/ProfileSettingsPage.htmx`** ```html