{
  "$schema": "https://mian.ro/schema/v2/registry.json",
  "schemaVersion": "2.0.0",
  "generatedAt": "2026-10-02T13:14:06.957Z",
  "id": "table",
  "name": "Table",
  "category": "Data display",
  "description": "Data tables with hover, striping, compact density, selection, numeric alignment and sortable columns.",
  "tags": [
    "table",
    "data table",
    "grid",
    "rows",
    "sort",
    "datagrid"
  ],
  "requiresJs": true,
  "license": "LicenseRef-MIAN-1.0",
  "composition": {
    "role": null,
    "intents": [
      "data table",
      "sortable table",
      "admin table",
      "transactions list",
      "comparison table",
      "dense data"
    ],
    "prominence": null,
    "density": null,
    "worksAfter": [],
    "worksBefore": [],
    "alternatives": [
      "list",
      "description-list"
    ],
    "avoid": null
  },
  "url": "https://mian.ro/components/table",
  "markdownUrl": "https://mian.ro/ai/components/table.md",
  "classes": [
    {
      "name": "mn-table-wrap",
      "description": "Border and horizontal scroll."
    },
    {
      "name": "mn-table",
      "description": "The table."
    },
    {
      "name": "mn-table-hover · mn-table-striped · mn-table-compact",
      "description": "Modifiers."
    },
    {
      "name": "mn-num",
      "description": "Right-aligned tabular numbers."
    },
    {
      "name": "th[aria-sort] > button",
      "description": "Sortable column: mian.js sorts the rows and updates aria-sort."
    }
  ],
  "accessibility": [
    "Use real <th scope> headers and a <caption>. Sort state is exposed through aria-sort on the header."
  ],
  "examples": [
    {
      "title": "Sortable",
      "description": null,
      "html": "<div class=\"mn-table-wrap\">\n<table class=\"mn-table mn-table-hover\">\n  <caption>Recent invoices</caption>\n  <thead><tr>\n    <th scope=\"col\" aria-sort=\"none\"><button>Invoice</button></th>\n    <th scope=\"col\">Status</th>\n    <th scope=\"col\" aria-sort=\"none\"><button>Method</button></th>\n    <th scope=\"col\" class=\"mn-num\" aria-sort=\"none\"><button>Amount</button></th>\n  </tr></thead>\n  <tbody>\n    <tr><td>INV-001</td><td><span class=\"mn-badge mn-badge-soft mn-success\">Paid</span></td><td>Card</td><td class=\"mn-num\">€250.00</td></tr>\n    <tr><td>INV-002</td><td><span class=\"mn-badge mn-badge-soft mn-warning\">Pending</span></td><td>Transfer</td><td class=\"mn-num\">€150.00</td></tr>\n    <tr><td>INV-003</td><td><span class=\"mn-badge mn-badge-soft mn-danger\">Overdue</span></td><td>PayPal</td><td class=\"mn-num\">€350.00</td></tr>\n    <tr><td>INV-004</td><td><span class=\"mn-badge mn-badge-soft mn-success\">Paid</span></td><td>Card</td><td class=\"mn-num\">€450.00</td></tr>\n  </tbody>\n  <tfoot><tr><td colspan=\"3\">Total</td><td class=\"mn-num\">€1,200.00</td></tr></tfoot>\n</table>\n</div>"
    },
    {
      "title": "Striped and compact",
      "description": null,
      "html": "<div class=\"mn-table-wrap\">\n<table class=\"mn-table mn-table-striped mn-table-compact\">\n  <thead><tr><th scope=\"col\">Noodle</th><th scope=\"col\">Origin</th><th scope=\"col\">Radius</th></tr></thead>\n  <tbody><tr><td>Udon</td><td>Japan</td><td>12px</td></tr><tr><td>Pho</td><td>Vietnam</td><td>8px</td></tr><tr><td>Mee</td><td>Malaysia</td><td>pill</td></tr><tr><td>Shirataki</td><td>Japan</td><td>0</td></tr></tbody>\n</table>\n</div>"
    }
  ],
  "css": ".mn-table-wrap { position: relative; width: 100%; overflow-x: auto; border: var(--mn-border-w) solid var(--mn-border); border-radius: var(--mn-radius-box); background: var(--mn-card); }\n.mn-table { width: 100%; border-collapse: collapse; font: 400 var(--mn-text-sm)/1.45 var(--mn-font); color: var(--mn-fg); }\n.mn-table caption { padding: .75rem 1rem; caption-side: bottom; color: var(--mn-muted); font-size: calc(var(--mn-text-sm) - 1px); text-align: start; }\n.mn-table th, .mn-table td { padding: .7rem 1rem; text-align: start; vertical-align: middle; border-bottom: var(--mn-border-w) solid var(--mn-border); }\n.mn-table thead th { height: 2.6rem; font-weight: 500; color: var(--mn-muted); white-space: nowrap; background: color-mix(in oklab, var(--mn-surface-2) 55%, transparent); }\n.mn-table tbody tr:last-child > * { border-bottom: 0; }\n.mn-table tfoot td, .mn-table tfoot th { font-weight: 600; border-top: var(--mn-border-w) solid var(--mn-border); border-bottom: 0; }\n.mn-table-hover tbody tr { transition: background-color var(--mn-duration) var(--mn-ease); }\n.mn-table-hover tbody tr:hover { background: var(--mn-hover); }\n.mn-table-striped tbody tr:nth-child(even) { background: color-mix(in oklab, var(--mn-surface-2) 50%, transparent); }\n.mn-table-compact th, .mn-table-compact td { padding: .4rem .75rem; }\n.mn-table tr[aria-selected=\"true\"] { background: color-mix(in oklab, var(--mn-primary) 8%, transparent); }\n.mn-table .mn-num { text-align: end; font-variant-numeric: tabular-nums; }\n.mn-table th[aria-sort] button { display: inline-flex; align-items: center; gap: .3rem; padding: 0; border: 0; background: none; color: inherit; font: inherit; cursor: pointer; }\n.mn-table th[aria-sort] button::after { content: \"↕\"; opacity: .45; font-size: .85em; }\n.mn-table th[aria-sort=\"ascending\"] button::after { content: \"↑\"; opacity: 1; }\n.mn-table th[aria-sort=\"descending\"] button::after { content: \"↓\"; opacity: 1; }",
  "js": "register('table', (root) => {\n  each(root, '.mn-table th[aria-sort] button', 'Sort', (btn) => {\n    btn.addEventListener('click', () => {\n      const th = btn.closest('th'), table = th.closest('table'), tbody = table.tBodies[0];\n      const col = [...th.parentElement.children].indexOf(th);\n      const dir = th.getAttribute('aria-sort') === 'ascending' ? 'descending' : 'ascending';\n      table.querySelectorAll('th[aria-sort]').forEach((h) => h.setAttribute('aria-sort', 'none'));\n      th.setAttribute('aria-sort', dir);\n      const val = (r) => { const t = (r.children[col]?.dataset.sort ?? r.children[col]?.textContent ?? '').trim(); const n = parseFloat(t.replace(/[^0-9.\\-]/g, '')); return isNaN(n) || /[a-z]/i.test(t.replace(/[€$£%,.\\s]/g, '')) ? t.toLowerCase() : n; };\n      const rows = [...tbody.rows].sort((a, b) => { const x = val(a), y = val(b); return (x > y ? 1 : x < y ? -1 : 0) * (dir === 'ascending' ? 1 : -1); });\n      rows.forEach((r) => tbody.appendChild(r));\n    });\n  });\n});",
  "install": {
    "css": "https://mian.ro/dist/mian.css",
    "js": "https://mian.ro/dist/mian.js",
    "registryItem": "https://mian.ro/r/table.json"
  }
}
