Enterprise-grade tab component with O(1) event delegation, deep nesting support, and hash-based deep linking. Built on Yai Event Hub architecture.
๐ฏ More Than Tabs - Itโs an Event Hub!
YaiTabs doubles as a powerful application event hub. Add listeners for ANY event type (click, input, change, submit, etc.) and handle them through hooks. All listeners are internally delegated and shared from root level as well. Perfect for building complete SPAs within a single tab component. You can throw needed elements into the tab, and remove them without headache. No manual register/unregister - Thatโs the power of Event Delegation - zero manual lifecycle management while providing max flexibility!
๐ Performance
๐ฏ Tab Component Capabilities
โก Event Hub Superpowers (The Secret Sauce)
๐จ Customization
โฅ Event Delegation Hierarchy
Root Component โ 2 listeners (click, keydown)
โโ Nested L2 โ 0 listeners (shares root's)
โ โโ Nested L3 โ 0 listeners (shares root's)
โ โโ Dynamic โ 0 listeners (shares root's)
โโ Sibling โ 0 listeners (shares root's)
๐ฎ Try It Now
Want to try it first? JSFiddle Demo โ
The examples further below will build up on this quick start.
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@yaijs/core@latest/tabs/yai-tabs.css">
</head>
<body>
<!-- Default Tab Markup -->
<div data-yai-tabs data-theme="default">
<nav data-controller>
<button data-tab-action="open" data-open="1">Tab 1</button>
<button data-tab-action="open" data-open="2">Tab 2</button>
</nav>
<div data-content>
<div data-tab="1">Content 1</div>
<div data-tab="2">Content 2</div>
</div>
</div>
<!-- YaiJS (pure ESM) -->
<script type="module">
import {
YaiTabs
} from 'https://cdn.jsdelivr.net/npm/@yaijs/core@latest/dist/yai-bundle.js';
const tabs = new YaiTabs();
</script>
</body>
</html>
Thatโs all you need to have ARIA-compliant YaiTabs up and running. It uses three default listeners added to [data-yai-tabs] initially: click, keydown, and hashchange.
Accessing Events via Hooks
These events automatically become available via the YaiTabs hook system, scoped at the component level. Nested components are completely isolated with their own scope, while sharing the listeners added at root level.
Add a test button.
<!-- YaiTabs adds data-attributes for the added events to a whitelist -->
<div data-tab="1"> <button data-click="callHandler">Test</button> </div>
And hook in. The scope based architecture makes sure, all events are only fired, when the content section is in focus or active. Opening tabs make them automatically focused, so keydown events are accessible immediately and can be used to navigate the tabs with arrows.
// ...
const tabs = new YaiTabs();
tabs
.hook('eventClick', ({ event, target, container, action, context }) => {
console.log('Click:', action, context.config)
})
.hook('eventKeydown', ({ event, target, container, context }) => {
console.log('Key:', event.key)
})
.hook('eventHashchange', ({ event, context }) => {
console.log('Hash changed', event, context)
})
It could look familar to you, because we are mixing up all the goods we like in popular Frameworks.
Customize YaiTabs behavior by passing options to the constructor:
const tabs = new YaiTabs({
// Animation behavior for tab transitions
defaultBehavior: 'fade', // Options: fade, slide-up, zoom, instant, etc.
// Enable close buttons on tabs
closable: true,
// Automatically focus opened tabs (enables keyboard navigation)
autoFocus: true,
// Add event listeners for additional interactions
events: {
setListener: {
'[data-yai-tabs]': ['click', 'keydown', 'input', 'change']
},
// Use attributes: `<button data-click>`, `<input data-input>`, `<input data-change>`
// Those attibutes are auto generated for common events. Check `tabs.events.actionableConfig`
}
});
| Option | Type | Default | Description |
|---|---|---|---|
closable |
boolean |
false |
Enable close buttons - clicking an active tab button closes the tab |
defaultBehavior |
string |
'fade' |
Default animation behavior: fade, slide-down, slide-up, slide-left, slide-right, blur, zoom, flip, instant |
autoFocus |
boolean |
true |
Automatically focus the first containerโs active tab on initialization |
autoAccessibility |
boolean |
true |
Enable comprehensive ARIA accessibility setup (roles, labels, states) |
autoDisambiguate |
boolean |
false |
Automatically make identical data-open/data-tab values unique to prevent cross-contamination |
lazyNestedComponents |
boolean |
true |
On init, marks nested tab components as lazy with data-yai-tabs-lazy |
events.setListener |
object |
See example | Define which events to listen for and on which selectors |
events.actionableAttributes |
array |
['data-tab-action'] |
Override auto generated attributes that trigger event hooks (e.g., data-click, data-input) |
events.customAttributes |
array |
[] |
Add any attributes to auto generated whitelist attributes |
YaiTabs comes with built-in themes and extensive customization options.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@yaijs/core@latest/tabs/yai-tabs.css">
Apply themes using data attributes:
<div data-yai-tabs data-theme="default" data-color-scheme="dark">
<!-- Your tabs -->
</div>
Available Options:
data-theme: default (feature-rich) or minimal (lightweight)data-color-scheme: light or darkdata-color-accent: primary, secondary, success, warning, dangerThemes cascade down to nested components unless explicitly overridden:
<!-- Parent: dark theme -->
<div data-yai-tabs data-theme="default" data-color-scheme="dark">
<nav data-controller>...</nav>
<div data-content>
<!-- Child: inherits dark theme -->
<div data-tab="1">
<div data-yai-tabs>
<!-- Uses parent's dark theme -->
</div>
</div>
<!-- Child: overrides to light -->
<div data-tab="2">
<div data-yai-tabs data-color-scheme="light">
<!-- Uses light theme -->
</div>
</div>
</div>
</div>
Style tab buttons with data-variant:
<nav data-controller data-variant="primary">
<button data-tab-action="open" data-open="1">Important</button>
</nav>
Variants: primary, secondary, success, warning, danger
For advanced customization, override CSS variables:
:root {
--yai-primary-color: #007bff;
--yai-border-radius: 8px;
--yai-tabs-loader-speed: 1.1s;
}
Check the CSS for all available variables: tabs/yai-tabs.css
<div data-yai-tabs
data-theme="default"
data-color-scheme="dark"
data-color-accent="primary"
data-behavior="fade"
data-nav="top"
data-ref-path="main-tabs"
data-accessibility="false">
<!-- tabs content -->
</div>
| Attribute | Values | Description |
|---|---|---|
data-yai-tabs |
โ | Required component marker |
data-theme |
default, minimal |
Visual theme variant |
data-color-scheme |
light, dark |
Layout color scheme |
data-color-accent |
primary, secondary, success, warning, danger |
Button accent color |
data-behavior |
fade, slide-down, slide-up, slide-left, slide-right, blur, zoom, flip, instant |
Animation effect |
data-nav |
top, right, bottom, left |
Navigation position |
data-ref-path |
string | Hash parameter key for routing |
data-accessibility |
false |
Disables auto ARIA attributes when set |
Semantic Color System:
data-color-scheme - Applies to entire layout (background, text, surfaces)data-color-accent - Applies to active buttons only<nav data-controller
data-align="center"
data-variant="success"
data-grow
aria-label="Main Tabs">
| Attribute | Values | Description |
|---|---|---|
data-controller |
โ | Required nav marker |
data-align |
start, center, end |
Button alignment |
data-variant |
primary, secondary, success, warning, danger |
Inverts color/background for active button |
data-grow |
โ | Enable flex-grow on buttons |
<button data-tab-action="open"
data-open="1"
data-default
data-url="/content.html"
data-url-refresh
data-delay="500"
data-min-loading="800">
| Attribute | Values | Description |
|---|---|---|
data-tab-action |
open |
Required action type |
data-open |
string/number | Target panel ID |
data-default |
โ | Initially active tab |
data-inview-default |
โ | Initially active tab alternate for nested tabs |
data-url |
URL | Dynamic content source |
data-url-refresh |
โ | Always reload content |
data-delay |
ms | Pre-fetch delay |
data-min-loading |
ms | Minimum loading duration |
<div data-content>
<div data-tab="1" data-spaceless>
<!-- Panel content -->
</div>
</div>
| Attribute | Values | Description |
|---|---|---|
data-content |
โ | Required wrapper |
data-tab |
string/number | Panel ID (matches data-open) |
data-spaceless |
โ | Remove default padding |
| Scenario | Components | Listeners | LCP | Memory |
|---|---|---|---|---|
| Basic | 1 root | 2 | ~0.10s | ~50KB |
| Nested | 20 components | 2 | ~0.10s | ~120KB |
| Deep | 70+ components | 2 | ~0.10s | ~350KB |
tablist, tab, tabpanelinert attribute for hidden panelsLighthouse Score: 100/100 for Accessibility, SEO and Best Practices
License: MIT