Zero-dependency tabs that enhance the markup you already have — full ARIA pattern, arrow-key navigation, a sliding indicator, and automatic light/dark. Every card below is live.
<script src="https://cdn.jsdelivr.net/gh/vanilla-ui-kit/components/tabs/tabs.js"></script>
Canonical markup: strip first, panels after. Arrow keys move and select.
Overview. First child is the tab strip, following siblings are the panels. That's the whole contract.
Features. Roving tabindex, wrap-around arrows, Home/End, hidden panels.
Pricing. Free — it's one file, MIT licensed.
Active: 0
new Tabs('#basic', {
onChange: (i) => console.log(i)
})
Arrows only move focus; Enter or Space selects.
Editor. Use activation: 'manual' when switching panels is expensive.
Preview. Focus me with ArrowRight, then press Enter.
History. Nothing loads until you commit.
new Tabs('#manual', { activation: 'manual' })
Rail on the left, ArrowUp/ArrowDown, indicator on the rail edge.
Profile. The tablist gets aria-orientation="vertical".
Security. The ink bar slides along the rail instead of under the tabs.
Billing. Same markup, one option.
new Tabs('#vertical', { vertical: true })
No markup — labels and content are text-safe by default.
new Tabs('#built', {
tabs: [
{ label: 'One', content: 'Plain text' },
{ label: 'Two', content: '<b>html</b>',
html: true }
]
})
Plain disabled attribute; arrows skip it.
Inbox. Press ArrowRight — Drafts is skipped.
Never shown.
Archive. Landed here directly.
<button disabled>Drafts</button>
Zero JS: data-vtb containers init themselves.
HTML. No script written for this card.
Attributes. data-vtb-active chose this tab; options ride along as data-vertical, data-activation, …
<div data-vtb>…canonical markup…</div>