Components · Navigation
Tabs
Switch between sibling content panels from a tab bar — line, primary or card, in two sizes, with per-tab icons.
Examples
Type
Size
Position
Content
Tabs · line · a leading icon · a disabled tab · click or arrow-key (probe value=0)
Tabs · line · tab-position=left — a vertical left rail, side active border, up/down arrow-key (probe value=0)
<!-- Paste-and-run: save as .html and open in a browser. No build step.
<aha-tabs> is the SAME shared custom element React and Vue consume.
Each panel child carries its label in data-tab (optional data-icon / data-disabled);
type = line | primary | card, size = default | small. Emits a composed `change` event. -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/tokens.css">
<script type="module">
import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/icons.js'; // registers <aha-icon> (leading glyphs)
import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-tabs.js'; // registers <aha-tabs>
</script>
<aha-tabs id="tabs" value="0" type="line">
<section data-tab="Overview" data-icon="system-info">Overview panel content.</section>
<section data-tab="Activity" data-icon="system-chart-bar">Activity panel content.</section>
<section data-tab="Settings" data-icon="system-gear">Settings panel content.</section>
<section data-tab="Archived" data-disabled>Archived (disabled).</section>
</aha-tabs>
<!-- Vertical left rail: tab-position="left" — tabs stack down the side, panel to the right,
active indicator becomes a 2px side border, arrow-key nav flips to up/down. -->
<aha-tabs value="0" type="line" tab-position="left">
<section data-tab="Overview" data-icon="system-info">Overview panel content.</section>
<section data-tab="Activity" data-icon="system-chart-bar">Activity panel content.</section>
<section data-tab="Settings" data-icon="system-gear">Settings panel content.</section>
</aha-tabs>
<script>
// Plain DOM — no framework. Read e.detail.value (index) off the composed CustomEvent.
document.getElementById('tabs').addEventListener('change', (e) => {
console.log('active tab:', e.detail.value);
});
</script>
import '@ahaslides-product/design/aha-tabs'; // registers <aha-tabs>
import '@ahaslides-product/design/icons'; // registers <aha-icon> for leading glyphs
import { useRef, useEffect } from 'react';
// React 18 needs a thin wrapper; React 19 can use <aha-tabs> directly.
function AhaTabs({ value = 0, type, size, tabPosition, onChange, children }) {
const ref = useRef();
useEffect(() => {
const el = ref.current;
el.value = value;
const h = (e) => onChange?.(e.detail.value); // composed CustomEvent
el.addEventListener('change', h);
return () => el.removeEventListener('change', h);
});
return <aha-tabs ref={ref} type={type} size={size} tab-position={tabPosition}>{children}</aha-tabs>;
}
// usage — each panel carries its label in data-tab (+ optional data-icon / data-disabled)
<AhaTabs value={tab} type="line" onChange={setTab}>
<section data-tab="Overview" data-icon="system-info">Overview panel content.</section>
<section data-tab="Activity" data-icon="system-chart-bar">Activity panel content.</section>
<section data-tab="Archived" data-disabled>Archived (disabled).</section>
</AhaTabs>
// vertical left rail — tab-position="left": tabs down the side, panel to the right,
// side active border, up/down arrow-key nav
<AhaTabs value={tab} type="line" tabPosition="left" onChange={setTab}>
<section data-tab="Overview" data-icon="system-info">Overview panel content.</section>
<section data-tab="Activity" data-icon="system-chart-bar">Activity panel content.</section>
<section data-tab="Settings" data-icon="system-gear">Settings panel content.</section>
</AhaTabs>
// main.ts — register the elements + mark aha-* as custom elements
import '@ahaslides-product/design/aha-tabs';
import '@ahaslides-product/design/icons'; // registers <aha-icon> for leading glyphs
app.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('aha-');
// Component.vue
<script setup>
import { ref } from 'vue';
const tab = ref(0);
</script>
<template>
<!-- type = line | primary | card, size = default | small, tab-position = top | left -->
<aha-tabs :value.prop="tab" type="line" @change="tab = $event.detail.value">
<section data-tab="Overview" data-icon="system-info">Overview panel content.</section>
<section data-tab="Activity" data-icon="system-chart-bar">Activity panel content.</section>
<section data-tab="Archived" data-disabled>Archived (disabled).</section>
</aha-tabs>
<!-- vertical left rail: tab-position="left" — side active border, up/down arrow-key nav -->
<aha-tabs :value.prop="tab" type="line" tab-position="left" @change="tab = $event.detail.value">
<section data-tab="Overview" data-icon="system-info">Overview panel content.</section>
<section data-tab="Activity" data-icon="system-chart-bar">Activity panel content.</section>
<section data-tab="Settings" data-icon="system-gear">Settings panel content.</section>
</aha-tabs>
</template>
API
| Prop | Type | Default | Notes |
|---|---|---|---|
value | number | 0 | Zero-based index of the active tab |
type | 'line' | 'primary' | 'card' | line | Underline (line), emphasised Bold underline (primary), or filled bg-accent segment (card) |
size | 'default' | 'small' | default | Bar height / label size — small is a tighter, shorter bar |
tab-position | 'top' | 'left' | top | Orientation — top (horizontal bar, default) or left (vertical rail with the panel to the right; active indicator becomes a side border, arrow-key nav flips to up/down) |
items | JSON array | — | Label-only bar (no panels): [{key?,label,icon?,disabled?}]. Omit to use slotted panels instead |
[data-tab] | attribute on each panel child | — | The tab label for that panel; optional data-icon (icon name) + data-disabled |
change | CustomEvent<{value}> | — | Composed event; read e.detail.value (index) |
Install
# .npmrc — once: point the @ahaslides-product scope at GitHub Packages
@ahaslides-product:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} # a GitHub token with read:packages
npm i @ahaslides-product/design
import '@ahaslides-product/design/tokens.css'; // once, at the app root
import '@ahaslides-product/design/aha-tabs'; // registers <aha-tabs>
Agent feed for this component (absolute, fetchable anywhere): tabs.agent.json · tabs.md · tabs.llms.txt
When to use
When to use
- Tabs — several peer content panels the user moves between within one screen
- Segmented — switching a compact view or mode, not larger panels
- Select — the choices are many or space is tight
Keep labels to one or two words, sentence case. Order tabs by expected use, most common first, and don't hide critical content behind a rarely-picked tab. Use line/primary for in-page view switching; reach for card when the bar sits on a tinted surface and needs a filled active chip.
Surfaces
editor dashboard settings
Spec
Bar gap 24, 1px border bottom (border #E3E3E3) — card drops the rule, gap 8 · Tab SemiBold 600 14 / line-height 21, pad 8 0, 8px gap to a 16px leading icon · Line (default) active color-primary #6A1EBB text + 2px color-primary underline · Primary same underline, active label Bold 700 · Card active in a bg-accent #F9F5FF pill, radius-sm 6, no underline; hover bg-hover · Inactive text-secondary #4A4A4A → text-default on hover · Disabled text-disabled #B5B5B5, not-allowed, skipped by arrow-key nav · Small 13px label, tighter padding + gap · Left rail tab-position=left — vertical rail, right-edge 1px rule; active moves to a 2px color-primary side border (line/primary), card keeps its pill; up/down arrow-key nav