{
  "$schema": "https://mian.ro/schema/v2/registry.json",
  "schemaVersion": "2.0.0",
  "generatedAt": "2026-10-02T13:14:06.957Z",
  "id": "tabs",
  "name": "Tabs",
  "category": "Navigation",
  "description": "Switch between views. Underlined, boxed (segmented), pill and vertical variants; full WAI-ARIA keyboard support.",
  "tags": [
    "tabs",
    "tab bar",
    "segmented",
    "views",
    "panels"
  ],
  "requiresJs": true,
  "license": "LicenseRef-MIAN-1.0",
  "composition": {
    "role": null,
    "intents": [
      "tabs",
      "switch views",
      "segmented tabs",
      "settings sections"
    ],
    "prominence": null,
    "density": null,
    "worksAfter": [],
    "worksBefore": [],
    "alternatives": [
      "toggle-group"
    ],
    "avoid": null
  },
  "url": "https://mian.ro/components/tabs",
  "markdownUrl": "https://mian.ro/ai/components/tabs.md",
  "classes": [
    {
      "name": "mn-tabs",
      "description": "Container."
    },
    {
      "name": "mn-tab-list · mn-tab · mn-tab-panel",
      "description": "Parts. mian.js adds the roles and ids."
    },
    {
      "name": "mn-tabs-boxed · mn-tabs-pills · mn-tabs-vertical",
      "description": "Variants."
    }
  ],
  "accessibility": [
    "role=\"tablist\", \"tab\", \"tabpanel\" with aria-controls, aria-labelledby and aria-selected. Arrow keys, Home and End move and activate; Tab moves into the panel."
  ],
  "examples": [
    {
      "title": "Underlined",
      "description": null,
      "html": "<div class=\"mn-tabs\">\n  <div class=\"mn-tab-list\" aria-label=\"Account\">\n    <button class=\"mn-tab\" aria-selected=\"true\">Account</button>\n    <button class=\"mn-tab\">Password</button>\n    <button class=\"mn-tab\">Notifications</button>\n  </div>\n  <div class=\"mn-tab-panel\">Make changes to your account here.</div>\n  <div class=\"mn-tab-panel\">Change your password here.</div>\n  <div class=\"mn-tab-panel\">Choose what we email you about.</div>\n</div>"
    },
    {
      "title": "Boxed",
      "description": null,
      "html": "<div class=\"mn-tabs mn-tabs-boxed\">\n  <div class=\"mn-tab-list\" aria-label=\"Period\">\n    <button class=\"mn-tab\">Day</button><button class=\"mn-tab\" aria-selected=\"true\">Week</button><button class=\"mn-tab\">Month</button><button class=\"mn-tab\">Year</button>\n  </div>\n  <div class=\"mn-tab-panel\">Daily figures.</div><div class=\"mn-tab-panel\">Weekly figures.</div><div class=\"mn-tab-panel\">Monthly figures.</div><div class=\"mn-tab-panel\">Yearly figures.</div>\n</div>"
    },
    {
      "title": "Pills with icons",
      "description": null,
      "html": "<div class=\"mn-tabs mn-tabs-pills\">\n  <div class=\"mn-tab-list\" aria-label=\"Media\">\n    <button class=\"mn-tab\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M3 11 12 3l9 8\"/><path d=\"M5 10v10h14V10\"/></svg> Overview</button><button class=\"mn-tab\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M4 20V10M10 20V4M16 20v-7M22 20H2\"/></svg> Analytics</button><button class=\"mn-tab\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M14 3H6a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V8Z\"/><path d=\"M14 3v5h5\"/></svg> Reports</button>\n  </div>\n  <div class=\"mn-tab-panel\">Overview content.</div><div class=\"mn-tab-panel\">Analytics content.</div><div class=\"mn-tab-panel\">Reports content.</div>\n</div>"
    }
  ],
  "css": ".mn-tabs { display: flex; flex-direction: column; gap: 1rem; min-width: 0; }\n.mn-tab-list { position: relative; display: flex; gap: 0; overflow-x: auto; scrollbar-width: none; border-bottom: var(--mn-border-w) solid var(--mn-border); }\n.mn-tab-list::-webkit-scrollbar { display: none; }\n.mn-tab {\n  position: relative; display: inline-flex; align-items: center; gap: .45rem; flex: none;\n  height: var(--mn-size-md); padding: 0 .9rem;\n  border: 0; background: none; color: var(--mn-muted);\n  font: 500 var(--mn-text-sm)/1 var(--mn-font); white-space: nowrap; cursor: pointer;\n  transition: color var(--mn-duration) var(--mn-ease);\n}\n.mn-tab::after { content: \"\"; position: absolute; inset: auto .5rem calc(var(--mn-border-w) * -1); height: 2px; border-radius: 2px 2px 0 0; background: transparent; transition: background-color var(--mn-duration) var(--mn-ease); }\n.mn-tab:hover { color: var(--mn-fg); }\n.mn-tab[aria-selected=\"true\"] { color: var(--mn-fg); }\n.mn-tab[aria-selected=\"true\"]::after { background: var(--mn-fg); }\n.mn-tab:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--mn-ring); border-radius: var(--mn-radius-field); }\n.mn-tab svg { width: 1rem; height: 1rem; }\n@media (max-width: 26rem) { .mn-tab { padding-inline: .5rem; } }\n.mn-tab-panel:focus-visible { outline: none; box-shadow: var(--mn-focus); border-radius: var(--mn-radius-field); }\n.mn-tab-panel[hidden] { display: none; }\n/* Boxed variant: the segmented look. */\n.mn-tabs-boxed .mn-tab-list { display: inline-flex; align-self: flex-start; max-width: 100%; gap: 2px; padding: 3px; border: 0; border-radius: calc(var(--mn-radius-field) + 3px); background: var(--mn-surface-2); }\n.mn-tabs-boxed .mn-tab { height: calc(var(--mn-size-md) - 6px); border-radius: var(--mn-radius-field); }\n.mn-tabs-boxed .mn-tab::after { display: none; }\n.mn-tabs-boxed .mn-tab[aria-selected=\"true\"] { background: var(--mn-surface); box-shadow: 0 1px 2px rgb(0 0 0 / .08), 0 0 0 1px var(--mn-border); }\n.mn-tabs-pills .mn-tab-list { border: 0; gap: .25rem; }\n.mn-tabs-pills .mn-tab { border-radius: 999px; height: var(--mn-size-sm); }\n.mn-tabs-pills .mn-tab::after { display: none; }\n.mn-tabs-pills .mn-tab[aria-selected=\"true\"] { background: var(--mn-primary); color: var(--mn-primary-fg); }\n.mn-tabs-vertical { flex-direction: row; }\n.mn-tabs-vertical .mn-tab-list { flex-direction: column; border-bottom: 0; border-inline-end: var(--mn-border-w) solid var(--mn-border); min-width: 10rem; }\n.mn-tabs-vertical .mn-tab { justify-content: flex-start; }\n.mn-tabs-vertical .mn-tab::after { inset: .4rem calc(var(--mn-border-w) * -1) .4rem auto; width: 2px; height: auto; }",
  "js": "/* WAI-ARIA tabs with automatic activation and a roving tabindex. */\nregister('tabs', (root) => {\n  each(root, '.mn-tabs', 'Tabs', (el) => {\n    const list = el.querySelector('.mn-tab-list');\n    if (!list) return;\n    list.setAttribute('role', 'tablist');\n    const vertical = el.classList.contains('mn-tabs-vertical');\n    if (vertical) list.setAttribute('aria-orientation', 'vertical');\n    const tabs = [...list.querySelectorAll('.mn-tab')];\n    const panels = [...el.querySelectorAll(':scope > .mn-tab-panel, :scope > * > .mn-tab-panel')];\n    tabs.forEach((t, i) => {\n      t.setAttribute('role', 'tab');\n      const p = t.getAttribute('aria-controls') ? document.getElementById(t.getAttribute('aria-controls')) : panels[i];\n      if (p) {\n        p.setAttribute('role', 'tabpanel');\n        t.setAttribute('aria-controls', id(p, 'mn-panel'));\n        p.setAttribute('aria-labelledby', id(t, 'mn-tab'));\n        if (!p.hasAttribute('tabindex')) p.tabIndex = 0;\n      }\n    });\n    const select = (tab, focus) => {\n      tabs.forEach((t) => {\n        const on = t === tab;\n        t.setAttribute('aria-selected', String(on));\n        t.tabIndex = on ? 0 : -1;\n        const p = document.getElementById(t.getAttribute('aria-controls'));\n        if (p) p.hidden = !on;\n      });\n      if (focus) tab.focus();\n      measure(tab);\n      emit(el, 'change', { index: tabs.indexOf(tab), tab });\n    };\n    // Some noodles draw one indicator that glides between tabs.\n    const measure = (tab) => {\n      list.style.setProperty('--_ix', tab.offsetLeft + 'px');\n      list.style.setProperty('--_iw', tab.offsetWidth + 'px');\n      list.style.setProperty('--_iy', tab.offsetTop + 'px');\n      list.style.setProperty('--_ih', tab.offsetHeight + 'px');\n    };\n    if ('ResizeObserver' in window) new ResizeObserver(() => { const t = tabs.find((x) => x.getAttribute('aria-selected') === 'true'); if (t) measure(t); }).observe(list);\n    select(tabs.find((t) => t.getAttribute('aria-selected') === 'true') || tabs[0], false);\n    list.addEventListener('click', (e) => { const t = e.target.closest('.mn-tab'); if (t && !t.disabled) select(t, false); });\n    list.addEventListener('keydown', (e) => {\n      const i = tabs.indexOf(document.activeElement);\n      if (i < 0) return;\n      const next = vertical ? 'ArrowDown' : 'ArrowRight', prev = vertical ? 'ArrowUp' : 'ArrowLeft';\n      const live = tabs.filter((t) => !t.disabled);\n      const j = live.indexOf(tabs[i]);\n      let n = null;\n      if (e.key === next) n = live[(j + 1) % live.length];\n      else if (e.key === prev) n = live[(j - 1 + live.length) % live.length];\n      else if (e.key === 'Home') n = live[0];\n      else if (e.key === 'End') n = live[live.length - 1];\n      if (n) { e.preventDefault(); select(n, true); }\n    });\n  });\n});",
  "install": {
    "css": "https://mian.ro/dist/mian.css",
    "js": "https://mian.ro/dist/mian.js",
    "registryItem": "https://mian.ro/r/tabs.json"
  }
}
