Four visual variants sharing one JS module, with data-attribute configuration
The Tabs module is initialized via Sadrazam.Tabs.listen() which scans for all tab heading containers.
| Attribute / Selector | Description |
|---|---|
| div[class*="tab-classic__heading"] | Wrapper element containing all tab heads. The JS auto-discovers these. |
| data-tab-id | Placed on each tab head. Its value must match the id of the corresponding panel. |
| id="tab-{name}" | The content panel ID. Must match the data-tab-id value on the head. |
| .is-default (class) | Marks which tab opens by default. Fallback when no hash or session is found. If omitted, the first tab is used. |
| data-tab-hash | Custom hash value. Selecting the tab sets the URL to #value. On page load, a matching hash restores that tab. |
| data-tab-target | Activates a tab from outside the tab group and scrolls to it. |
| data-tab-persist | On heading container. Enables sessionStorage persistence. Without it, the active tab is not remembered across page loads. |
On initialization, the active tab is determined by the first match in this order:
Top-bordered heads, active tab removes bottom border to merge with panel. Container: .tab-classic__container → Heading: .tab-classic__heading → Head: .tab-classic__head → Body: .tab-classic__body → Panel: .tab-classic__panel
Bordered heads with a colored bottom accent line on the active tab. Wraps on smaller screens, no-wrap on XL. Container: .tab-card__container → Heading: .tab-card__heading → Head: .tab-card__head → Body: .tab-card__body → Panel: .tab-card__panel
Similar to V2 but with horizontal scroll, a margin gap between heading and body, and wrap at LG breakpoint. Container: .tab-scroll__container → Heading: .tab-scroll__heading → Head: .tab-scroll__head → Body: .tab-scroll__body → Panel: .tab-scroll__panel
Modern segment-control style. Active tab gets a filled primary background with shadow. Hover shows primary color text. Container: .tab-capsule__container → Heading: .tab-capsule__heading → Head: .tab-capsule__head → Body: .tab-capsule__body → Panel: .tab-capsule__panel
Uses .tab-capsule__panel--neutral for a borderless, background-free panel. Useful when the content itself provides its own container styling.
By default, the first tab is active. Add .is-default to a tab head to override this. Unlike .is-active, this class has no CSS styling and causes no visual flash on page load.
Add data-tab-hash to each tab head. When selected, the browser URL updates to #value via history.replaceState() (no history pollution). Refreshing the page restores the active tab from the hash. In-page links like <a href="#security"> also activate the matching tab. Selecting the first tab clears the hash from the URL.
When the URL hash matches an element id inside a tab panel, that tab is automatically activated and the page scrolls to the element with a flash effect. This also works when clicking in-page <a href="#element-id"> links.
Some content before the target...
Buttons outside the tab group can activate a specific tab using data-tab-target. The panel is automatically scrolled into view.
The .tab-capsule__panel--inner class creates bordered, rounded sections inside a neutral panel. Stacked inner panels get automatic top margin via the adjacent sibling selector .tab-capsule__panel--inner + .tab-capsule__panel--inner.
Name, email, and avatar settings. This is the first .tab-capsule__panel--inner block.
Language, timezone, and theme preferences. This second inner panel receives automatic spacing from the adjacent sibling rule.
Credit card and billing address configuration.
Past invoices and receipts displayed here.
Session persistence is opt-in. Add data-tab-persist to the heading container to enable it. When enabled, the active tab is saved to sessionStorage on every tab change. When the page reloads, the previously active tab is restored — unless a URL hash takes higher priority. The storage key format is sdrzm-tab:{pathname}:{groupIndex}, so each tab group on each page has its own independent memory.
Without data-tab-persist, the tab group always starts from the hash, .is-default, or the first tab. Session data persists until the browser tab is closed.
Tabs follow the WAI-ARIA Tabs pattern for screen readers and keyboard navigation. All ARIA attributes are applied automatically on initialization and removed on destroy().
| Feature | Details |
|---|---|
| ARIA roles | role="tablist" on heading container, role="tab" on each head, role="tabpanel" on each panel. |
| ARIA attributes | aria-selected and aria-controls on tab heads; aria-labelledby on panels. |
| Focus management | Active tab: tabindex="0". Inactive tabs: tabindex="-1". Only the active tab is in the natural tab order. |
| Keyboard |
ArrowRight / ArrowDown — next tab (cycles). ArrowLeft / ArrowUp — previous tab (cycles). Home — first tab. End — last tab. |
| Cleanup | destroy() removes all ARIA attributes, roles, and keyboard listeners. |
All four variants share the same JS module. Only the CSS class prefix changes.
| Variant | Prefix | Style | Key Feature |
|---|---|---|---|
| Classic | .tab-classic__* | Classic | Top border, active removes bottom border |
| Card | .tab-card__* | Border + underline accent | Responsive wrap, 3px bottom accent on active |
| Scroll | .tab-scroll__* | Horizontal scroll | Scrollable heading, detached body with margin gap |
| Capsule | .tab-capsule__* | Capsule / segment | Filled active background, transition animation, shadow |
Available via the global Sadrazam.Tabs object.