{
  "name": "tabs",
  "title": {
    "es": "Pestañas",
    "en": "Tabs"
  },
  "summary": {
    "es": "Cambia entre vistas; el indicador se desliza bajo la pestaña activa.",
    "en": "Switches views; the indicator slides under the active tab."
  },
  "category": "navigation",
  "status": "stable",
  "nivel": "componentes",
  "updated": "2026-09-29",
  "license": "MIT",
  "page": "https://componentes-lovat.vercel.app/c/tabs/",
  "requires": [],
  "files": [
    {
      "type": "html",
      "url": "https://componentes-lovat.vercel.app/raw/tabs/tabs.html",
      "content": "<div class=\"c-tabs\" data-c-tabs>\n  <div class=\"c-tabs__list\" role=\"tablist\" aria-label=\"Cuenta\">\n    <button class=\"c-tabs__tab\" role=\"tab\" type=\"button\" id=\"tabs-account\" aria-controls=\"tabs-account-panel\" aria-selected=\"true\">Cuenta</button>\n    <button class=\"c-tabs__tab\" role=\"tab\" type=\"button\" id=\"tabs-password\" aria-controls=\"tabs-password-panel\" aria-selected=\"false\" tabindex=\"-1\">Contraseña</button>\n    <button class=\"c-tabs__tab\" role=\"tab\" type=\"button\" id=\"tabs-team\" aria-controls=\"tabs-team-panel\" aria-selected=\"false\" tabindex=\"-1\">Equipo</button>\n    <span class=\"c-tabs__indicator\" aria-hidden=\"true\"></span>\n  </div>\n  <div class=\"c-tabs__panel\" role=\"tabpanel\" id=\"tabs-account-panel\" aria-labelledby=\"tabs-account\" tabindex=\"0\"><p>Cambia tu nombre, tu foto y tu correo.</p></div>\n  <div class=\"c-tabs__panel\" role=\"tabpanel\" id=\"tabs-password-panel\" aria-labelledby=\"tabs-password\" tabindex=\"0\" hidden><p>Actualiza tu contraseña y activa la verificación en dos pasos.</p></div>\n  <div class=\"c-tabs__panel\" role=\"tabpanel\" id=\"tabs-team-panel\" aria-labelledby=\"tabs-team\" tabindex=\"0\" hidden><p>Invita a tu equipo y define quién puede editar.</p></div>\n</div>\n"
    },
    {
      "type": "css",
      "url": "https://componentes-lovat.vercel.app/raw/tabs/tabs.css",
      "content": "/* tabs · Componentes (MIRÓ Labs). Diseño de coss ui (MIT, © coss.com, antes Origin UI).\n   Generado por tools/build_css.py desde tabs.tw.json. No editar a mano. Necesita tokens.css. */\n.c-tabs__indicator{position:absolute}\n.c-tabs__list{position:relative}\n.c-tabs__tab{position:relative}\n.c-tabs__indicator{bottom:calc(var(--spacing)*0);left:calc(var(--spacing)*0);z-index:calc(1*-1)}\n.c-tabs__list{z-index:0}\n.c-tabs{display:flex}\n.c-tabs__list{display:flex}\n.c-tabs__tab{display:flex}\n.c-tabs__indicator{height:var(--active-tab-height)}\n.c-tabs__tab{height:calc(var(--spacing)*8.5)}\n.c-tabs__indicator{width:var(--active-tab-width)}\n.c-tabs__list{width:fit-content}\n.c-tabs__panel{flex:1}\n.c-tabs__tab{flex-shrink:0;flex-grow:1}\n.c-tabs__indicator{--tw-translate-x:var(--active-tab-left);translate:var(--tw-translate-x)var(--tw-translate-y);--tw-translate-y:calc(var(--active-tab-bottom)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}\n.c-tabs__tab{cursor:pointer}\n.c-tabs{flex-direction:column}\n.c-tabs__list{align-items:center}\n.c-tabs__tab{align-items:center}\n.c-tabs__list{justify-content:center}\n.c-tabs__tab{justify-content:center;gap:calc(var(--spacing)*1.5)}\n.c-tabs{gap:calc(var(--spacing)*2)}\n.c-tabs__list{column-gap:calc(var(--spacing)*.5);border-radius:var(--radius)}\n.c-tabs__indicator{border-radius:calc(var(--radius) - 2px)}\n.c-tabs__tab{border-radius:calc(var(--radius) - 2px);border-style:var(--tw-border-style);border-width:1px;border-color:#0000}\n.c-tabs__indicator{background-color:var(--background)}\n.c-tabs__list{background-color:var(--muted);padding:calc(var(--spacing)*.5)}\n.c-tabs__tab{padding-inline:calc(calc(var(--spacing)*2.5) - 1px);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}\n.c-tabs__panel{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}\n.c-tabs__tab{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);white-space:nowrap}\n.c-tabs__list{color:var(--muted-foreground)}\n.c-tabs__panel{color:var(--muted-foreground)}\n.c-tabs__list{color:var(--muted-foreground)}\n@supports (color:color-mix(in lab, red, red)){\n.c-tabs__list{color:color-mix(in oklab,var(--muted-foreground)72%,transparent)}\n}\n.c-tabs__indicator{--tw-shadow-alpha:5%;--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000000d),0 1px 2px -1px var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}\n@supports (color:lab(0% 0 0)){\n.c-tabs__indicator{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,lab(0% 0 0/.05)),0 1px 2px -1px var(--tw-shadow-color,lab(0% 0 0/.05))}\n}\n.c-tabs__tab{transition-property:color,background-color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}\n.c-tabs__indicator{transition-property:width,translate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;transition-duration:.2s;--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}\n.c-tabs__tab{--tw-outline-style:none;outline-style:none}\n.c-tabs__panel{--tw-outline-style:none;outline-style:none}\n@media (hover:hover){\n.c-tabs__tab:hover{color:var(--muted-foreground)}\n}\n.c-tabs__tab:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--ring)}\n.c-tabs__tab[aria-selected=true]{color:var(--foreground)}\n.c-tabs__tab:disabled{pointer-events:none;opacity:.64}\n@media (min-width:40rem){\n.c-tabs__tab{height:calc(var(--spacing)*7.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}\n}\n.c-tabs__indicator:is(.dark *){background-color:var(--input)}\n.c-tabs__tab svg{pointer-events:none;flex-shrink:0}\n.c-tabs__tab svg:not([class*=opacity-]){opacity:.8}\n.c-tabs__tab svg:not([class*=size-]){width:calc(var(--spacing)*4.5);height:calc(var(--spacing)*4.5)}\n@media (min-width:40rem){\n.c-tabs__tab svg:not([class*=size-]){width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}\n}\n/* propio */\n.c-tabs__tab{background:transparent}\n.c-tabs__panel:focus-visible{outline:2px solid var(--ring);outline-offset:2px;border-radius:var(--radius)}\n.c-tabs__indicator:not([style]){display:none}\n.c-tabs__panel p{max-width:60ch;padding:.5rem .25rem}\n"
    },
    {
      "type": "js",
      "url": "https://componentes-lovat.vercel.app/raw/tabs/tabs.js",
      "content": "/* Pestañas (APG, activación automática) con el indicador que se desliza bajo la pestaña activa. */\n(function () {\n  const C = (window.Componentes = window.Componentes || {});\n\n  function place(list, tab) {\n    const ind = list.querySelector('.c-tabs__indicator');\n    if (!ind || !tab) return;\n    const lr = list.getBoundingClientRect(), tr = tab.getBoundingClientRect();\n    ind.style.setProperty('--active-tab-left', (tr.left - lr.left - list.clientLeft) + 'px');\n    ind.style.setProperty('--active-tab-width', tr.width + 'px');\n    ind.style.setProperty('--active-tab-height', tr.height + 'px');\n    ind.style.setProperty('--active-tab-bottom', (lr.bottom - tr.bottom - list.clientTop) + 'px');\n  }\n\n  function setup(root) {\n    const list = root.querySelector('[role=\"tablist\"]');\n    const tabs = [...list.querySelectorAll('[role=\"tab\"]')];\n    if (!tabs.length) return;\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 panel = document.getElementById(t.getAttribute('aria-controls'));\n        if (panel) panel.hidden = !on;\n      });\n      if (focus) tab.focus();\n      place(list, tab);\n    };\n    select(tabs.find((t) => t.getAttribute('aria-selected') === 'true') || tabs[0], false);\n    list.addEventListener('click', (e) => { const t = e.target.closest('[role=\"tab\"]'); if (t && !t.disabled) select(t, false); });\n    list.addEventListener('keydown', (e) => {\n      const enabled = tabs.filter((t) => !t.disabled);\n      const i = enabled.indexOf(document.activeElement);\n      if (i < 0) return;\n      const next = { ArrowRight: i + 1, ArrowLeft: i - 1, Home: 0, End: enabled.length - 1 }[e.key];\n      if (next === undefined) return;\n      e.preventDefault();\n      select(enabled[(next + enabled.length) % enabled.length], true);\n    });\n    new ResizeObserver(() => place(list, tabs.find((t) => t.getAttribute('aria-selected') === 'true'))).observe(list);\n  }\n\n  function init(root) {\n    (root || document).querySelectorAll('[data-c-tabs]:not([data-c-ready])').forEach((el) => {\n      el.dataset.cReady = '';\n      try { setup(el); } catch (e) { console.warn('[tabs]', e); }\n    });\n  }\n\n  C.tabs = { init };\n  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => init());\n  else init();\n})();\n"
    },
    {
      "type": "standalone",
      "url": "https://componentes-lovat.vercel.app/r/tabs.html"
    }
  ],
  "tokensFile": "https://componentes-lovat.vercel.app/tokens.css",
  "darkTheme": "add class 'dark' to <html>",
  "contexto": "ambos",
  "densidad": "media",
  "tono": "sobria",
  "states": [
    "rest",
    "hover",
    "focus",
    "selected",
    "disabled"
  ],
  "use": {
    "es": [
      "Para 2 a 6 vistas del mismo tema."
    ],
    "en": [
      "For 2 to 6 views of one topic."
    ]
  },
  "avoid": {
    "es": [
      "Si cada pestaña cambia la URL: son enlaces."
    ],
    "en": [
      "If each tab changes the URL: those are links."
    ]
  },
  "a11y": {
    "roles": [
      "tablist",
      "tab",
      "tabpanel"
    ],
    "keyboard": [
      {
        "keys": "← / →",
        "es": "Pestaña anterior o siguiente",
        "en": "Previous or next tab"
      },
      {
        "keys": "Inicio / Fin",
        "es": "Primera o última",
        "en": "First or last"
      }
    ],
    "notes": {
      "es": [
        "Patrón de la W3C con activación automática y tabindex roving."
      ],
      "en": [
        "W3C pattern with automatic activation and roving tabindex."
      ]
    },
    "test": {
      "es": [
        "Tab entra en «Cuenta»; → pasa a «Contraseña»."
      ],
      "en": [
        "Tab lands on “Account”; → moves to “Password”."
      ]
    }
  },
  "issues": {
    "es": [],
    "en": []
  },
  "credits": [
    {
      "source": "coss ui (MIT) · tabs.tsx",
      "url": "https://coss.com/ui"
    },
    {
      "source": "W3C WAI-ARIA APG, Tabs",
      "url": "https://www.w3.org/WAI/ARIA/apg/patterns/tabs/"
    }
  ],
  "changes": [
    {
      "date": "2026-09-29",
      "es": "Diseño de coss ui en HTML, CSS y JS sin dependencias",
      "en": "coss ui design in dependency-free HTML, CSS and JS"
    }
  ],
  "partes": [],
  "pieza": "https://componentes-lovat.vercel.app/r/tabs.html"
}
