{
  "generatedFrom": "tabs.contract.json",
  "component": "Tabs",
  "slug": "tabs",
  "group": "Navigation",
  "tier": "leaf-lit",
  "summary": "Switch between sibling content panels from a tab bar — line, primary or card, in two sizes, with per-tab icons.",
  "install": {
    "package": "@ahaslides-product/design",
    "registry": "https://npm.pkg.github.com",
    "scope": "@ahaslides-product",
    "auth": "Published to GitHub Packages — needs a GitHub token with read:packages. Configure the @ahaslides-product scope in .npmrc before installing.",
    "npmrc": "@ahaslides-product:registry=https://npm.pkg.github.com\n//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}   # a GitHub token with read:packages",
    "command": "npm i @ahaslides-product/design",
    "tokenLayer": "import '@ahaslides-product/design/tokens.css';",
    "import": "import '@ahaslides-product/design/aha-tabs';",
    "element": "aha-tabs",
    "registers": "aha-tabs"
  },
  "feeds": {
    "doc": "https://ahaslides-product.github.io/ahaslides-design/tabs/index.html",
    "md": "https://ahaslides-product.github.io/ahaslides-design/tabs/tabs.md",
    "agentJson": "https://ahaslides-product.github.io/ahaslides-design/tabs.agent.json",
    "llms": "https://ahaslides-product.github.io/ahaslides-design/tabs.llms.txt",
    "index": "https://ahaslides-product.github.io/ahaslides-design/llms.txt"
  },
  "recommendedSnippet": "html",
  "vibeCode": "Emit the HTML snippet by default — <aha-tabs> is a standard custom element that renders on open — React/Vue are thin adapters over the same element. Paste into an .html file and it renders with no build step.",
  "frameworks": {
    "react": {
      "via": "web-component",
      "ref": "aha-tabs"
    },
    "vue": {
      "via": "web-component",
      "ref": "aha-tabs"
    },
    "html": {
      "via": "web-component",
      "ref": "aha-tabs",
      "runnable": "paste-and-run — no build step, renders on open"
    }
  },
  "props": [
    {
      "name": "value",
      "type": "number",
      "default": "0",
      "desc": "Zero-based index of the active tab"
    },
    {
      "name": "type",
      "type": "'line' | 'primary' | 'card'",
      "default": "line",
      "desc": "Underline (line), emphasised Bold underline (primary), or filled bg-accent segment (card)"
    },
    {
      "name": "size",
      "type": "'default' | 'small'",
      "default": "default",
      "desc": "Bar height / label size — small is a tighter, shorter bar"
    },
    {
      "name": "tab-position",
      "type": "'top' | 'left'",
      "default": "top",
      "desc": "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)"
    },
    {
      "name": "items",
      "type": "JSON array",
      "default": "—",
      "desc": "Label-only bar (no panels): [{key?,label,icon?,disabled?}]. Omit to use slotted panels instead"
    },
    {
      "name": "[data-tab]",
      "type": "attribute on each panel child",
      "default": "—",
      "desc": "The tab label for that panel; optional data-icon (icon name) + data-disabled"
    },
    {
      "name": "change",
      "type": "CustomEvent<{value}>",
      "default": "—",
      "desc": "Composed event; read e.detail.value (index)"
    }
  ],
  "tokens": [
    "border",
    "text-secondary",
    "text-default",
    "text-disabled",
    "color-primary",
    "bg-accent",
    "bg-hover",
    "border-focus",
    "radius-xs",
    "radius-sm",
    "font-product",
    "motion-mid",
    "ease-in-out"
  ],
  "spec": [
    {
      "label": "Bar",
      "value": "gap 24, 1px border bottom (border #E3E3E3) — card drops the rule, gap 8"
    },
    {
      "label": "Tab",
      "value": "SemiBold 600 14 / line-height 21, pad 8 0, 8px gap to a 16px leading icon"
    },
    {
      "label": "Line (default)",
      "value": "active color-primary #6A1EBB text + 2px color-primary underline"
    },
    {
      "label": "Primary",
      "value": "same underline, active label Bold 700"
    },
    {
      "label": "Card",
      "value": "active in a bg-accent #F9F5FF pill, radius-sm 6, no underline; hover bg-hover"
    },
    {
      "label": "Inactive",
      "value": "text-secondary #4A4A4A → text-default on hover"
    },
    {
      "label": "Disabled",
      "value": "text-disabled #B5B5B5, not-allowed, skipped by arrow-key nav"
    },
    {
      "label": "Small",
      "value": "13px label, tighter padding + gap"
    },
    {
      "label": "Left rail",
      "value": "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"
    }
  ],
  "opinion": {
    "whenToUse": [
      {
        "what": "Tabs",
        "when": "several peer content panels the user moves between within one screen"
      },
      {
        "what": "Segmented",
        "when": "switching a compact view or mode, not larger panels"
      },
      {
        "what": "Select",
        "when": "the choices are many or space is tight"
      }
    ],
    "note": "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.",
    "dsv3": "COVERED — enumerated from the DS V3 reference contract, which carries two type families: `tabs|…` (the filled CARD look — active in a bg-accent pill, radius-sm 6, no rule) and `tabsPrimary|…` (the underline LINE look — active color-primary #6A1EBB text + 2px color-primary underline, inactive-hover lifting to color-primary), each with active / inactive / inactive|hover. Mapped to `type` = line (default underline) · primary (Bold 700 underline) · card (bg-accent pill). Both sizes (default / small) and per-tab leading icon + disabled supported. `tab-position` = top (default) · left adds the vertical left-rail orientation: the bar's bottom rule becomes a right-edge rule and the 2px color-primary underline rotates to a 2px color-primary side border on the active edge (card keeps its pill), roving nav flipping to up/down — same tokens, same persistent-node animation. Token layer: border #E3E3E3, text-secondary inactive, color-primary active, bg-accent card fill, text-disabled disabled, radius-sm card. Colour, underline/side-border and pill animate on persistent nodes via the shared motion tokens — no subtree rebuild on switch."
  },
  "surfaces": [
    "editor",
    "dashboard",
    "settings"
  ],
  "snippets": {
    "html": "<!-- Paste-and-run: save as .html and open in a browser. No build step.\n     <aha-tabs> is the SAME shared custom element React and Vue consume.\n     Each panel child carries its label in data-tab (optional data-icon / data-disabled);\n     type = line | primary | card, size = default | small. Emits a composed `change` event. -->\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/tokens.css\">\n<script type=\"module\">\n  import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/icons.js';      // registers <aha-icon> (leading glyphs)\n  import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-tabs.js';   // registers <aha-tabs>\n</script>\n\n<aha-tabs id=\"tabs\" value=\"0\" type=\"line\">\n  <section data-tab=\"Overview\" data-icon=\"system-info\">Overview panel content.</section>\n  <section data-tab=\"Activity\" data-icon=\"system-chart-bar\">Activity panel content.</section>\n  <section data-tab=\"Settings\" data-icon=\"system-gear\">Settings panel content.</section>\n  <section data-tab=\"Archived\" data-disabled>Archived (disabled).</section>\n</aha-tabs>\n\n<!-- Vertical left rail: tab-position=\"left\" — tabs stack down the side, panel to the right,\n     active indicator becomes a 2px side border, arrow-key nav flips to up/down. -->\n<aha-tabs value=\"0\" type=\"line\" tab-position=\"left\">\n  <section data-tab=\"Overview\" data-icon=\"system-info\">Overview panel content.</section>\n  <section data-tab=\"Activity\" data-icon=\"system-chart-bar\">Activity panel content.</section>\n  <section data-tab=\"Settings\" data-icon=\"system-gear\">Settings panel content.</section>\n</aha-tabs>\n\n<script>\n  // Plain DOM — no framework. Read e.detail.value (index) off the composed CustomEvent.\n  document.getElementById('tabs').addEventListener('change', (e) => {\n    console.log('active tab:', e.detail.value);\n  });\n</script>\n",
    "react": "import '@ahaslides-product/design/aha-tabs';   // registers <aha-tabs>\nimport '@ahaslides-product/design/icons';      // registers <aha-icon> for leading glyphs\nimport { useRef, useEffect } from 'react';\n\n// React 18 needs a thin wrapper; React 19 can use <aha-tabs> directly.\nfunction AhaTabs({ value = 0, type, size, tabPosition, onChange, children }) {\n  const ref = useRef();\n  useEffect(() => {\n    const el = ref.current;\n    el.value = value;\n    const h = (e) => onChange?.(e.detail.value);   // composed CustomEvent\n    el.addEventListener('change', h);\n    return () => el.removeEventListener('change', h);\n  });\n  return <aha-tabs ref={ref} type={type} size={size} tab-position={tabPosition}>{children}</aha-tabs>;\n}\n\n// usage — each panel carries its label in data-tab (+ optional data-icon / data-disabled)\n<AhaTabs value={tab} type=\"line\" onChange={setTab}>\n  <section data-tab=\"Overview\" data-icon=\"system-info\">Overview panel content.</section>\n  <section data-tab=\"Activity\" data-icon=\"system-chart-bar\">Activity panel content.</section>\n  <section data-tab=\"Archived\" data-disabled>Archived (disabled).</section>\n</AhaTabs>\n\n// vertical left rail — tab-position=\"left\": tabs down the side, panel to the right,\n// side active border, up/down arrow-key nav\n<AhaTabs value={tab} type=\"line\" tabPosition=\"left\" onChange={setTab}>\n  <section data-tab=\"Overview\" data-icon=\"system-info\">Overview panel content.</section>\n  <section data-tab=\"Activity\" data-icon=\"system-chart-bar\">Activity panel content.</section>\n  <section data-tab=\"Settings\" data-icon=\"system-gear\">Settings panel content.</section>\n</AhaTabs>\n",
    "vue": "// main.ts — register the elements + mark aha-* as custom elements\nimport '@ahaslides-product/design/aha-tabs';\nimport '@ahaslides-product/design/icons';   // registers <aha-icon> for leading glyphs\napp.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('aha-');\n\n// Component.vue\n<script setup>\nimport { ref } from 'vue';\nconst tab = ref(0);\n</script>\n\n<template>\n  <!-- type = line | primary | card, size = default | small, tab-position = top | left -->\n  <aha-tabs :value.prop=\"tab\" type=\"line\" @change=\"tab = $event.detail.value\">\n    <section data-tab=\"Overview\" data-icon=\"system-info\">Overview panel content.</section>\n    <section data-tab=\"Activity\" data-icon=\"system-chart-bar\">Activity panel content.</section>\n    <section data-tab=\"Archived\" data-disabled>Archived (disabled).</section>\n  </aha-tabs>\n\n  <!-- vertical left rail: tab-position=\"left\" — side active border, up/down arrow-key nav -->\n  <aha-tabs :value.prop=\"tab\" type=\"line\" tab-position=\"left\" @change=\"tab = $event.detail.value\">\n    <section data-tab=\"Overview\" data-icon=\"system-info\">Overview panel content.</section>\n    <section data-tab=\"Activity\" data-icon=\"system-chart-bar\">Activity panel content.</section>\n    <section data-tab=\"Settings\" data-icon=\"system-gear\">Settings panel content.</section>\n  </aha-tabs>\n</template>\n"
  }
}
