{
  "name": "button",
  "title": {
    "es": "Botón",
    "en": "Button"
  },
  "summary": {
    "es": "Dispara una acción. Variantes primaria, contorno, secundaria, fantasma, peligro y enlace.",
    "en": "Triggers an action. Primary, outline, secondary, ghost, destructive and link variants."
  },
  "category": "actions",
  "status": "stable",
  "nivel": "primitivos",
  "updated": "2026-10-08",
  "license": "MIT",
  "page": "https://componentes-lovat.vercel.app/c/button/",
  "requires": [],
  "files": [
    {
      "type": "html",
      "url": "https://componentes-lovat.vercel.app/raw/button/button.html",
      "content": "<div class=\"c-btn-row\">\n  <button class=\"c-btn c-btn--primary\" type=\"button\">Guardar</button>\n  <button class=\"c-btn c-btn--outline\" type=\"button\">Cancelar</button>\n  <button class=\"c-btn c-btn--secondary\" type=\"button\">Duplicar</button>\n  <button class=\"c-btn c-btn--ghost\" type=\"button\">Omitir</button>\n  <button class=\"c-btn c-btn--destructive\" type=\"button\">Eliminar</button>\n  <a class=\"c-btn c-btn--link\" href=\"#docs\">Ver documentación</a>\n</div>\n<div class=\"c-btn-row\">\n  <button class=\"c-btn c-btn--outline\" type=\"button\">\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M12 15V3\"/><path d=\"m7 10 5 5 5-5\"/><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/></svg>\n    Descargar\n  </button>\n  <button class=\"c-btn c-btn--outline c-btn--icon\" type=\"button\" aria-label=\"Agregar\">\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M5 12h14\"/><path d=\"M12 5v14\"/></svg>\n  </button>\n  <button class=\"c-btn c-btn--primary\" type=\"button\" data-c-loading-demo>\n    Publicar\n    <svg class=\"c-btn__spinner\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" aria-hidden=\"true\"><path d=\"M21 12a9 9 0 1 1-6.219-8.56\"/></svg>\n  </button>\n  <button class=\"c-btn c-btn--primary\" type=\"button\" disabled>No disponible</button>\n  <button class=\"c-btn c-btn--outline c-btn--sm\" type=\"button\">Pequeño</button>\n  <button class=\"c-btn c-btn--outline c-btn--lg\" type=\"button\">Grande</button>\n</div>\n<p class=\"c-sr-only\" role=\"status\" data-c-btn-status></p>\n"
    },
    {
      "type": "css",
      "url": "https://componentes-lovat.vercel.app/raw/button/button.css",
      "content": "/* button · Componentes (MIRÓ Labs). Diseño de coss ui (MIT, © coss.com, antes Origin UI).\n   Generado por tools/build_css.py desde button.tw.json. No editar a mano. Necesita tokens.css. */\n.c-btn__spinner{pointer-events:none;position:absolute}\n.c-btn{position:relative;display:inline-flex}\n.c-btn--icon{width:calc(var(--spacing)*9);height:calc(var(--spacing)*9)}\n.c-btn--sm{height:calc(var(--spacing)*8)}\n.c-btn{height:calc(var(--spacing)*9)}\n.c-btn--lg{height:calc(var(--spacing)*10)}\n.c-btn{flex-shrink:0}\n.c-btn__spinner{animation:var(--animate-spin)}\n.c-btn{cursor:pointer;align-items:center;justify-content:center}\n.c-btn--sm{gap:calc(var(--spacing)*1.5)}\n.c-btn{gap:calc(var(--spacing)*2);border-radius:var(--radius);border-style:var(--tw-border-style);border-width:1px}\n.c-btn--destructive{border-color:var(--destructive)}\n.c-btn--outline{border-color:var(--input)}\n.c-btn--primary{border-color:var(--primary)}\n.c-btn--secondary{border-color:#0000}\n.c-btn--ghost{border-color:#0000}\n.c-btn--link{border-color:#0000}\n.c-btn--destructive{background-color:var(--destructive)}\n.c-btn--outline{background-color:var(--popover)}\n.c-btn--primary{background-color:var(--primary)}\n.c-btn--secondary{background-color:var(--secondary)}\n.c-btn--icon{padding-inline:calc(var(--spacing)*0)}\n.c-btn--sm{padding-inline:calc(calc(var(--spacing)*2.5) - 1px)}\n.c-btn{padding-inline:calc(calc(var(--spacing)*3) - 1px)}\n.c-btn--lg{padding-inline:calc(calc(var(--spacing)*3.5) - 1px)}\n.c-btn{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);white-space:nowrap}\n.c-btn--outline{color:var(--foreground)}\n.c-btn--ghost{color:var(--foreground)}\n.c-btn--link{color:var(--foreground)}\n.c-btn--primary{color:var(--primary-foreground)}\n.c-btn--secondary{color:var(--secondary-foreground)}\n.c-btn--destructive{color:var(--color-white)}\n.c-btn--link{text-underline-offset:4px}\n.c-btn--outline{--tw-shadow-alpha:5%;--tw-shadow:0 1px 2px 0 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-btn--outline{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,lab(0% 0 0/.05))}\n}\n.c-btn--primary{--tw-shadow:0 1px 2px 0 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.c-btn--destructive{--tw-shadow:0 1px 2px 0 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);--tw-shadow-color:var(--destructive)}\n@supports (color:color-mix(in lab, red, red)){\n.c-btn--destructive{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--destructive)24%,transparent)var(--tw-shadow-alpha),transparent)}\n}\n.c-btn--primary{--tw-shadow-color:var(--primary)}\n@supports (color:color-mix(in lab, red, red)){\n.c-btn--primary{--tw-shadow-color:color-mix(in oklab,color-mix(in oklab,var(--primary)24%,transparent)var(--tw-shadow-alpha),transparent)}\n}\n.c-btn{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-outline-style:none;outline-style:none}\n.c-btn--primary:not(:disabled){--tw-inset-shadow:inset 0 1px var(--tw-inset-shadow-color,#ffffff29)}\n.c-btn--destructive:not(:disabled){--tw-inset-shadow:inset 0 1px var(--tw-inset-shadow-color,#ffffff29)}\n@supports (color:color-mix(in lab, red, red)){\n.c-btn--primary:not(:disabled){--tw-inset-shadow:inset 0 1px var(--tw-inset-shadow-color,color-mix(in oklab,var(--color-white)16%,transparent))}\n.c-btn--destructive:not(:disabled){--tw-inset-shadow:inset 0 1px var(--tw-inset-shadow-color,color-mix(in oklab,var(--color-white)16%,transparent))}\n}\n.c-btn--primary:not(:disabled){box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}\n.c-btn--destructive:not(:disabled){box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}\n.c-btn--outline:not(:is(.dark *)){background-clip:padding-box}\n.c-btn:before{content:var(--tw-content);pointer-events:none;content:var(--tw-content);position:absolute;content:var(--tw-content);inset:calc(var(--spacing)*0);content:var(--tw-content);border-radius:calc(var(--radius-lg) - 1px)}\n.c-btn--outline:not(:disabled):not(:active):not([data-pressed]):before{content:var(--tw-content);--tw-shadow:0 1px var(--tw-shadow-color,#0000000a)}\n@supports (color:color-mix(in lab, red, red)){\n.c-btn--outline:not(:disabled):not(:active):not([data-pressed]):before{--tw-shadow:0 1px var(--tw-shadow-color,color-mix(in oklab,var(--color-black)4%,transparent))}\n}\n.c-btn--outline:not(:disabled):not(:active):not([data-pressed]):before{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}\n@media (hover:hover){\n.c-btn--outline:hover{background-color:var(--accent)}\n.c-btn--ghost:hover{background-color:var(--accent)}\n}\n@media (hover:hover){\n@supports (color:color-mix(in lab, red, red)){\n.c-btn--outline:hover{background-color:color-mix(in oklab,var(--accent)50%,transparent)}\n}}\n@media (hover:hover){\n.c-btn--destructive:hover{background-color:var(--destructive)}\n}\n@media (hover:hover){\n@supports (color:color-mix(in lab, red, red)){\n.c-btn--destructive:hover{background-color:color-mix(in oklab,var(--destructive)90%,transparent)}\n}}\n@media (hover:hover){\n.c-btn--primary:hover{background-color:var(--primary)}\n}\n@media (hover:hover){\n@supports (color:color-mix(in lab, red, red)){\n.c-btn--primary:hover{background-color:color-mix(in oklab,var(--primary)90%,transparent)}\n}}\n@media (hover:hover){\n.c-btn--secondary:hover{background-color:var(--secondary)}\n}\n@media (hover:hover){\n@supports (color:color-mix(in lab, red, red)){\n.c-btn--secondary:hover{background-color:color-mix(in oklab,var(--secondary)90%,transparent)}\n}}\n@media (hover:hover){\n.c-btn--link:hover{text-decoration-line:underline}\n}\n.c-btn: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);--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-ring-offset-color:var(--background)}\n.c-btn:disabled{pointer-events:none;opacity:.64}\n.c-btn[data-loading]{color:#0000;-webkit-user-select:none;user-select:none}\n.c-btn--outline[data-pressed]{background-color:var(--accent)}\n.c-btn--ghost[data-pressed]{background-color:var(--accent)}\n@supports (color:color-mix(in lab, red, red)){\n.c-btn--outline[data-pressed]{background-color:color-mix(in oklab,var(--accent)50%,transparent)}\n}\n.c-btn--destructive[data-pressed]{background-color:var(--destructive)}\n@supports (color:color-mix(in lab, red, red)){\n.c-btn--destructive[data-pressed]{background-color:color-mix(in oklab,var(--destructive)90%,transparent)}\n}\n.c-btn--primary[data-pressed]{background-color:var(--primary)}\n@supports (color:color-mix(in lab, red, red)){\n.c-btn--primary[data-pressed]{background-color:color-mix(in oklab,var(--primary)90%,transparent)}\n}\n.c-btn--secondary[data-pressed]{background-color:var(--secondary)}\n@supports (color:color-mix(in lab, red, red)){\n.c-btn--secondary[data-pressed]{background-color:color-mix(in oklab,var(--secondary)90%,transparent)}\n}\n.c-btn--link[data-pressed]{text-decoration-line:underline}\n@media (min-width:40rem){\n.c-btn--icon{width:calc(var(--spacing)*8);height:calc(var(--spacing)*8)}\n.c-btn--sm{height:calc(var(--spacing)*7)}\n.c-btn{height:calc(var(--spacing)*8)}\n.c-btn--lg{height:calc(var(--spacing)*9)}\n.c-btn{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}\n}\n.c-btn--outline:is(.dark *){background-color:var(--input)}\n@supports (color:color-mix(in lab, red, red)){\n.c-btn--outline:is(.dark *){background-color:color-mix(in oklab,var(--input)32%,transparent)}\n}\n.c-btn--outline:is(.dark *):not(:disabled):before{content:var(--tw-content);--tw-shadow:0 -1px var(--tw-shadow-color,#ffffff05)}\n@supports (color:color-mix(in lab, red, red)){\n.c-btn--outline:is(.dark *):not(:disabled):before{--tw-shadow:0 -1px var(--tw-shadow-color,color-mix(in oklab,var(--color-white)2%,transparent))}\n}\n.c-btn--outline:is(.dark *):not(:disabled):before{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}\n.c-btn--outline:is(.dark *):not(:disabled):not(:active):not([data-pressed]):before{content:var(--tw-content);--tw-shadow:0 -1px var(--tw-shadow-color,#ffffff0f)}\n@supports (color:color-mix(in lab, red, red)){\n.c-btn--outline:is(.dark *):not(:disabled):not(:active):not([data-pressed]):before{--tw-shadow:0 -1px var(--tw-shadow-color,color-mix(in oklab,var(--color-white)6%,transparent))}\n}\n.c-btn--outline:is(.dark *):not(:disabled):not(:active):not([data-pressed]):before{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}\n@media (hover:hover){\n.c-btn--outline:is(.dark *):hover{background-color:var(--input)}\n}\n@media (hover:hover){\n@supports (color:color-mix(in lab, red, red)){\n.c-btn--outline:is(.dark *):hover{background-color:color-mix(in oklab,var(--input)64%,transparent)}\n}}\n.c-btn--outline:is(.dark *)[data-pressed]{background-color:var(--input)}\n@supports (color:color-mix(in lab, red, red)){\n.c-btn--outline:is(.dark *)[data-pressed]{background-color:color-mix(in oklab,var(--input)64%,transparent)}\n}\n@media (pointer:coarse){\n.c-btn:after{content:var(--tw-content);position:absolute;content:var(--tw-content);width:100%;height:100%;content:var(--tw-content);min-height:calc(var(--spacing)*11);content:var(--tw-content);min-width:calc(var(--spacing)*11)}\n}\n.c-btn svg{pointer-events:none;margin-inline:calc(var(--spacing)*-.5);flex-shrink:0}\n.c-btn svg:not([class*=opacity-]){opacity:.8}\n.c-btn svg:not([class*=size-]){width:calc(var(--spacing)*4.5);height:calc(var(--spacing)*4.5)}\n@media (min-width:40rem){\n.c-btn svg:not([class*=size-]){width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}\n}\n.c-btn--secondary:is(:active,[data-pressed]){background-color:var(--secondary)}\n@supports (color:color-mix(in lab, red, red)){\n.c-btn--secondary:is(:active,[data-pressed]){background-color:color-mix(in oklab,var(--secondary)80%,transparent)}\n}\n.c-btn--primary:is(:active,[data-pressed]){--tw-inset-shadow:inset 0 1px var(--tw-inset-shadow-color,#00000014)}\n.c-btn--destructive:is(:active,[data-pressed]){--tw-inset-shadow:inset 0 1px var(--tw-inset-shadow-color,#00000014)}\n@supports (color:color-mix(in lab, red, red)){\n.c-btn--primary:is(:active,[data-pressed]){--tw-inset-shadow:inset 0 1px var(--tw-inset-shadow-color,color-mix(in oklab,var(--color-black)8%,transparent))}\n.c-btn--destructive:is(:active,[data-pressed]){--tw-inset-shadow:inset 0 1px var(--tw-inset-shadow-color,color-mix(in oklab,var(--color-black)8%,transparent))}\n}\n.c-btn--primary:is(:active,[data-pressed]){box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}\n.c-btn--destructive:is(:active,[data-pressed]){box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}\n.c-btn--primary:is(:disabled,:active,[data-pressed]){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}\n.c-btn--destructive:is(:disabled,:active,[data-pressed]){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}\n.c-btn--outline:is(:disabled,:active,[data-pressed]){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}\n/* propio */\n.c-btn__spinner{display:none;color:var(--foreground)}\n.c-btn[data-loading] .c-btn__spinner{display:block}\n.c-btn--primary .c-btn__spinner{color:var(--primary-foreground)}\n.c-btn-row{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem}\n.c-btn[aria-disabled=true]{cursor:progress}\n.c-sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}\n"
    },
    {
      "type": "js",
      "url": "https://componentes-lovat.vercel.app/raw/button/button.js",
      "content": "/* Botón: estado de carga. Componentes.button.setLoading(boton, true|false, textoParaLector) */\n(function () {\n  const C = (window.Componentes = window.Componentes || {});\n\n  function setLoading(btn, on, message) {\n    try {\n      if (on) { btn.setAttribute('data-loading', ''); btn.setAttribute('aria-disabled', 'true'); }\n      else { btn.removeAttribute('data-loading'); btn.removeAttribute('aria-disabled'); }\n      const status = document.querySelector('[data-c-btn-status]');\n      if (status && message) status.textContent = message;\n    } catch (e) { console.warn('[button]', e); }\n  }\n\n  // Con aria-disabled el botón conserva el foco, pero no debe hacer nada.\n  document.addEventListener('click', (e) => {\n    const btn = e.target.closest?.('.c-btn[aria-disabled=\"true\"]');\n    if (btn) { e.preventDefault(); e.stopImmediatePropagation(); }\n  }, true);\n\n  function init(root) {\n    (root || document).querySelectorAll('[data-c-loading-demo]:not([data-c-ready])').forEach((btn) => {\n      btn.dataset.cReady = '';\n      btn.addEventListener('click', () => {\n        setLoading(btn, true, 'Publicando…');\n        setTimeout(() => setLoading(btn, false, 'Publicado.'), 1600);\n      });\n    });\n  }\n\n  C.button = { setLoading, init };\n  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => init());\n  else init();\n})();\n"
    },
    {
      "type": "standalone",
      "url": "https://componentes-lovat.vercel.app/r/button.html"
    }
  ],
  "tokensFile": "https://componentes-lovat.vercel.app/tokens.css",
  "darkTheme": "add class 'dark' to <html>",
  "contexto": "ambos",
  "densidad": "baja",
  "tono": "sobria",
  "states": [
    "rest",
    "hover",
    "focus",
    "active",
    "disabled",
    "loading"
  ],
  "use": {
    "es": [
      "Para acciones en la página: guardar, abrir, copiar.",
      "La variante enlace sirve para navegar con aspecto de botón de texto."
    ],
    "en": [
      "For on-page actions: save, open, copy.",
      "The link variant navigates with a text-button look."
    ]
  },
  "avoid": {
    "es": [
      "Para ir a otra página con aspecto de botón lleno: usa <a class=\"c-btn\">.",
      "Más de un primario por zona."
    ],
    "en": [
      "To navigate with a filled look: use <a class=\"c-btn\">.",
      "More than one primary per area."
    ]
  },
  "a11y": {
    "roles": [
      "button",
      "link"
    ],
    "keyboard": [
      {
        "keys": "Enter / Espacio",
        "es": "Activa el botón",
        "en": "Activates the button"
      }
    ],
    "notes": {
      "es": [
        "Cargando usa data-loading y aria-disabled: conserva el foco y se anuncia en una región role=status.",
        "En pantallas táctiles la zona de toque crece a 44 px sin cambiar el tamaño visible.",
        "Un botón solo con icono lleva aria-label."
      ],
      "en": [
        "Loading uses data-loading and aria-disabled: focus stays and it is announced in a role=status region.",
        "On touch screens the hit area grows to 44 px without changing the visible size.",
        "Icon-only buttons carry aria-label."
      ]
    },
    "test": {
      "es": [
        "Pulsa «Publicar» con Espacio: el foco se queda y se anuncia «Publicado»."
      ],
      "en": [
        "Press “Publish” with Space: focus stays and “Published” is announced."
      ]
    }
  },
  "issues": {
    "es": [],
    "en": []
  },
  "credits": [
    {
      "source": "coss ui (MIT) · button.tsx",
      "url": "https://coss.com/ui"
    },
    {
      "source": "W3C WAI-ARIA APG, Button",
      "url": "https://www.w3.org/WAI/ARIA/apg/patterns/button/"
    }
  ],
  "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"
    },
    {
      "date": "2026-10-08",
      "es": "Partes editables declaradas (data-parte) para el Banco de Infinite Canvas.",
      "en": "Editable parts declared (data-parte) for the Infinite Canvas workbench."
    }
  ],
  "partes": [
    {
      "id": "accion",
      "tipo": "accion",
      "nombre": {
        "es": "Botón",
        "en": "Button"
      }
    }
  ],
  "pieza": "https://componentes-lovat.vercel.app/r/button.pieza.html"
}
