{
  "name": "table",
  "title": {
    "es": "Tabla",
    "en": "Table"
  },
  "summary": {
    "es": "Tabla con orden por columna y desplazamiento horizontal en móvil.",
    "en": "Table with column sorting and horizontal scroll on mobile."
  },
  "category": "data",
  "status": "stable",
  "nivel": "componentes",
  "updated": "2026-09-29",
  "license": "MIT",
  "page": "https://componentes-lovat.vercel.app/c/table/",
  "requires": [
    "badge"
  ],
  "files": [
    {
      "type": "html",
      "url": "https://componentes-lovat.vercel.app/raw/table/table.html",
      "content": "<div class=\"c-table-wrap\" role=\"region\" aria-labelledby=\"tbl-caption\" tabindex=\"0\">\n  <table class=\"c-table\" data-c-sortable>\n    <caption class=\"c-table__caption c-sr-only\" id=\"tbl-caption\">Facturas de septiembre 2026</caption>\n    <thead>\n      <tr>\n        <th class=\"c-table__head\" scope=\"col\" aria-sort=\"descending\"><button class=\"c-table__sort\" type=\"button\" data-key=\"0\">Factura <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m18 15-6-6-6 6\"/></svg></button></th>\n        <th class=\"c-table__head\" scope=\"col\"><button class=\"c-table__sort\" type=\"button\" data-key=\"1\">Cliente <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m18 15-6-6-6 6\"/></svg></button></th>\n        <th class=\"c-table__head\" scope=\"col\">Estado</th>\n        <th class=\"c-table__head c-table__head--num\" scope=\"col\"><button class=\"c-table__sort\" type=\"button\" data-key=\"3\" data-type=\"num\">Monto <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m18 15-6-6-6 6\"/></svg></button></th>\n      </tr>\n    </thead>\n    <tbody>\n      <tr class=\"c-table__row\"><td class=\"c-table__cell\">INV-0914</td><td class=\"c-table__cell\">Northwind</td><td class=\"c-table__cell\"><span class=\"c-badge c-badge--success\"><span class=\"c-badge__dot\" aria-hidden=\"true\"></span>Pagado</span></td><td class=\"c-table__cell c-table__cell--num\" data-value=\"89\">S/ 89.00</td></tr>\n      <tr class=\"c-table__row\"><td class=\"c-table__cell\">INV-0913</td><td class=\"c-table__cell\">Vela Labs</td><td class=\"c-table__cell\"><span class=\"c-badge c-badge--success\"><span class=\"c-badge__dot\" aria-hidden=\"true\"></span>Pagado</span></td><td class=\"c-table__cell c-table__cell--num\" data-value=\"249\">S/ 249.00</td></tr>\n      <tr class=\"c-table__row\"><td class=\"c-table__cell\">INV-0912</td><td class=\"c-table__cell\">Tallyworks</td><td class=\"c-table__cell\"><span class=\"c-badge c-badge--warning\"><span class=\"c-badge__dot\" aria-hidden=\"true\"></span>Pendiente</span></td><td class=\"c-table__cell c-table__cell--num\" data-value=\"29\">S/ 29.00</td></tr>\n      <tr class=\"c-table__row\"><td class=\"c-table__cell\">INV-0911</td><td class=\"c-table__cell\">Harbor &amp; Co</td><td class=\"c-table__cell\"><span class=\"c-badge c-badge--error\"><span class=\"c-badge__dot\" aria-hidden=\"true\"></span>Vencido</span></td><td class=\"c-table__cell c-table__cell--num\" data-value=\"101\">S/ 101.00</td></tr>\n    </tbody>\n  </table>\n</div>\n<p class=\"c-sr-only\" role=\"status\" data-c-table-status></p>\n"
    },
    {
      "type": "css",
      "url": "https://componentes-lovat.vercel.app/raw/table/table.css",
      "content": "/* table · Componentes (MIRÓ Labs). Diseño de coss ui (MIT, © coss.com, antes Origin UI).\n   Generado por tools/build_css.py desde table.tw.json. No editar a mano. Necesita tokens.css. */\n.c-table-wrap{position:relative}\n.c-table__row{position:relative}\n.c-table__caption{margin-top:calc(var(--spacing)*4)}\n.c-table__head{height:calc(var(--spacing)*10)}\n.c-table-wrap{width:100%}\n.c-table{width:100%;caption-side:bottom}\n.c-table-wrap{overflow-x:auto}\n.c-table thead tr{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}\n.c-table__row{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}\n.c-table__cell{background-clip:padding-box;padding:calc(var(--spacing)*2.5)}\n.c-table__head{padding-inline:calc(var(--spacing)*2.5);text-align:left;vertical-align:middle}\n.c-table__cell{vertical-align:middle}\n.c-table{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}\n.c-table__caption{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}\n.c-table__head{--tw-leading:1;line-height:1}\n.c-table__cell{--tw-leading:1;line-height:1}\n.c-table__head{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);white-space:nowrap}\n.c-table__cell{white-space:nowrap}\n.c-table__head{color:var(--muted-foreground)}\n.c-table__caption{color:var(--muted-foreground)}\n/* propio */\n.c-table{border-collapse:collapse}\n.c-table tbody tr:last-child{border-bottom:0}\n.c-table__cell--num,.c-table__head--num{text-align:right;font-variant-numeric:tabular-nums}\n.c-table__sort{display:inline-flex;align-items:center;gap:.25rem;margin:0 -.375rem;padding:.25rem .375rem;border:0;border-radius:calc(var(--radius) - 4px);background:transparent;font:inherit;color:inherit;cursor:pointer}\n.c-table__sort:hover{color:var(--foreground);background:var(--accent)}\n.c-table__sort:focus-visible{outline:2px solid var(--ring);outline-offset:1px}\n.c-table__sort svg{width:.875rem;height:.875rem;opacity:.5}\n[aria-sort=ascending] .c-table__sort svg,[aria-sort=descending] .c-table__sort svg{opacity:1}\n[aria-sort=descending] .c-table__sort svg{rotate:180deg}\n.c-table-wrap{border:1px solid var(--border);border-radius:calc(var(--radius) + 4px)}\n.c-table__head:first-child,.c-table__cell:first-child{padding-left:.875rem}\n.c-table__head:last-child,.c-table__cell:last-child{padding-right:.875rem}\n.c-table tbody .c-table__row:hover{background-color:color-mix(in srgb,var(--background),var(--color-black) 2%)}\n.dark .c-table tbody .c-table__row:hover{background-color:color-mix(in srgb,var(--background),var(--color-white) 2%)}\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/table/table.js",
      "content": "/* Tabla ordenable: el botón del encabezado ordena y marca aria-sort (ascendente o descendente). */\n(function () {\n  const C = (window.Componentes = window.Componentes || {});\n  function setup(table) {\n    const body = table.tBodies[0];\n    table.querySelectorAll('.c-table__sort').forEach((btn) => btn.addEventListener('click', () => {\n      const th = btn.closest('th'), i = +btn.dataset.key, num = btn.dataset.type === 'num';\n      const dir = th.getAttribute('aria-sort') === 'ascending' ? 'descending' : 'ascending';\n      table.querySelectorAll('th[aria-sort]').forEach((h) => h.removeAttribute('aria-sort'));\n      th.setAttribute('aria-sort', dir);\n      const val = (tr) => { const td = tr.cells[i]; return num ? parseFloat(td.dataset.value ?? td.textContent) : td.textContent.trim(); };\n      const rows = [...body.rows].sort((a, b) => { const x = val(a), y = val(b); const r = num ? x - y : String(x).localeCompare(String(y), 'es'); return dir === 'ascending' ? r : -r; });\n      rows.forEach((r) => body.append(r));\n      const st = document.querySelector('[data-c-table-status]');\n      if (st) st.textContent = `Ordenado por ${btn.textContent.trim()}, ${dir === 'ascending' ? 'ascendente' : 'descendente'}.`;\n    }));\n  }\n  function init(root) {\n    (root || document).querySelectorAll('table[data-c-sortable]:not([data-c-ready])').forEach((t) => { t.dataset.cReady = ''; try { setup(t); } catch (e) { console.warn('[table]', e); } });\n  }\n  C.table = { init };\n  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => init()); else init();\n})();\n"
    },
    {
      "type": "standalone",
      "url": "https://componentes-lovat.vercel.app/r/table.html"
    }
  ],
  "tokensFile": "https://componentes-lovat.vercel.app/tokens.css",
  "darkTheme": "add class 'dark' to <html>",
  "contexto": "herramienta",
  "densidad": "alta",
  "tono": "sobria",
  "states": [
    "rest",
    "hover",
    "sorted"
  ],
  "use": {
    "es": [
      "Para datos que se comparan por filas y columnas."
    ],
    "en": [
      "For data compared by rows and columns."
    ]
  },
  "avoid": {
    "es": [
      "Para maquetar: usa grid."
    ],
    "en": [
      "For layout: use grid."
    ]
  },
  "a11y": {
    "roles": [
      "table",
      "columnheader",
      "button"
    ],
    "keyboard": [
      {
        "keys": "Tab",
        "es": "Llega a los botones de orden",
        "en": "Reaches the sort buttons"
      },
      {
        "keys": "Enter / Espacio",
        "es": "Ordena la columna",
        "en": "Sorts the column"
      }
    ],
    "notes": {
      "es": [
        "Para ordenar, un botón dentro del encabezado y aria-sort en la columna.",
        "El contenedor se desplaza con teclado (tabindex=0 y nombre)."
      ],
      "en": [
        "To sort, a button inside the header and aria-sort on the column.",
        "The wrapper scrolls with the keyboard (tabindex=0 and a name)."
      ]
    },
    "test": {
      "es": [
        "Pulsa «Monto»: se ordena ascendente y se anuncia."
      ],
      "en": [
        "Press “Amount”: it sorts ascending and is announced."
      ]
    }
  },
  "issues": {
    "es": [],
    "en": []
  },
  "credits": [
    {
      "source": "coss ui (MIT) · table.tsx",
      "url": "https://coss.com/ui"
    },
    {
      "source": "Adrian Roselli, Sortable Table Columns",
      "url": "https://adrianroselli.com/2021/04/sortable-table-columns.html"
    }
  ],
  "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/table.html"
}
