Data table <mv-data-table>

围绕 .mv-table 构建的全功能数据表格:列排序、不区分重音的搜索、值筛选、列显示菜单、行选择(表头复选框支持半选状态,Shift + 点击可选择范围)及批量操作栏、可设置每页行数的分页、空状态、与各列对齐的合计页脚,并且在窄于 40rem 的容器中,行会变成堆叠卡片(标题、副标题、右侧金额)。

工作原理

围绕 .mv-table 构建的全功能数据表格:列排序、不区分重音的搜索、值筛选、列显示菜单、行选择(表头复选框支持半选状态,Shift + 点击可选择范围)及批量操作栏、可设置每页行数的分页、空状态、与各列对齐的合计页脚,并且在窄于 40rem 的容器中,行会变成堆叠卡片(标题、副标题、右侧金额)。可在浏览器中处理编写好的行,也可在 mode="server" 下工作,此时应用根据 sort、search 和 page 事件加载每一页,并提供总数。已知限制:卡片阈值固定为容器宽度 40rem;多个副标题或详情列会每行一个地堆叠;服务器模式下,只有带 data-id 的行才能跨页保持选中。

分类区块
类型Web Component(<mv-data-table>)
状态稳定版
同时安装checkbox, popover, menu, pagination, table
Keywordsdata-table, datagrid, table, sort, filter, search, pagination, selection, bulk-actions, columns, server-side, totals, tfoot, responsive, cards

When to use

  • Users must sort, search, filter and paginate a list of records in an admin or back-office screen
  • Rows need multi-selection with Shift + click ranges and a bulk-actions bar
  • Thousands of records live in an API and each page is fetched on demand, with the total known by the server
  • An invoice or statement shows subtotal, tax and total under the amount column and must read well on a phone

Avoid when

  • The data is a small static table that needs no selection or column controls → use Table instead
  • Users scroll through tens of thousands of rows without paging; the table renders every row of a page in the DOM

安装

node scripts/add.mjs data-table --out ./src/marvelous

使用 Marvelous UI MCP 服务器的 AI 智能体:install_components({ slugs: ["data-table"], target_dir: "<absolute path>/src/marvelous", framework: "react" })。

复制的文件(含依赖):tokens/tokens.css, core/base.css, components/checkbox/checkbox.css, core/dom.js, core/element.js, core/focus.js, core/motion.js, core/position.js, core/svg.js, components/popover/popover.js, components/popover/popover.css, core/dismiss.js, components/menu/menu.js, components/menu/menu.css, components/pagination/pagination.js, components/pagination/pagination.css, components/table/table.css, components/table/table.js, components/data-table/data-table.js, components/data-table/data-table.css。

用法

标准标记,可在此基础上通过属性、data-* 和 CSS 变量进行定制:

<div id="dt-demo" style="width:min(100%,64rem);margin-inline:auto">
<mv-data-table page-size="8" label="Recent orders">
  <div class="mv-data-table-toolbar">
    <div class="mv-input-group" data-size="sm">
      <label class="mv-input-group-addon" for="dt-demo-q"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg><span class="mv-sr-only">Search</span></label>
      <input id="dt-demo-q" class="mv-input" type="search" placeholder="Search orders or customers…" data-dt-search autocomplete="off">
    </div>
    <select class="mv-select" data-size="sm" data-dt-filter="status" aria-label="Filter by status">
      <option value="">All statuses</option>
      <option value="paid">Paid</option>
      <option value="pending">Pending</option>
      <option value="shipped">Shipped</option>
      <option value="refunded">Refunded</option>
      <option value="failed">Failed</option>
    </select>
    <div class="mv-data-table-toolbar-end">
      <mv-menu data-dt-columns placement="bottom-end">
        <button class="mv-button" data-variant="outline" data-size="sm" type="button"><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 6h16M4 12h16M4 18h16"/><path d="M9 4v16M15 4v16" opacity=".5"/></svg>Columns</button>
        <div data-content style="--mv-menu-width:12rem"></div>
      </mv-menu>
      <button class="mv-button" data-size="sm" type="button" data-dt-demo-export><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 3v12m-5-5 5 5 5-5M5 21h14"/></svg><span data-dt-label>Export</span></button>
    </div>
  </div>
  <div class="mv-table-wrap">
    <table class="mv-table">
      <thead>
        <tr>
          <th scope="col" data-dt-select></th>
          <th scope="col" data-key="id" data-sort="text" data-card="subtitle">Order</th>
          <th scope="col" data-key="client" data-sort="text" data-card="title">Customer</th>
          <th scope="col" data-key="status" data-sort="text" data-card="meta">Status</th>
          <th scope="col" data-key="channel" data-sort="text" data-card="hide">Channel</th>
          <th scope="col" data-key="date" data-sort="date" data-card="subtitle">Date</th>
          <th scope="col" data-key="items" data-sort="number" data-align="end" data-card="hide">Items</th>
          <th scope="col" data-key="amount" data-sort="number" data-align="end" data-card="amount">Amount</th>
          <th scope="col" data-card="action"><span class="mv-sr-only">Actions</span></th>
        </tr>
      </thead>
      <tbody>
        <tr data-id="ORD-4821">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4821</span></td>
          <td data-value="Camila Rossi"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:200" aria-hidden="true"><span class="mv-avatar-fallback">CR</span></span><div><b>Camila Rossi</b><small>[email protected]</small></div></div></td>
          <td data-value="paid"><span class="mv-badge" data-variant="success" data-size="sm">Paid</span></td>
          <td class="mv-table-muted">Online store</td>
          <td data-value="2026-09-22" class="mv-table-muted"><time datetime="2026-09-22">Sep 22, 2026</time></td>
          <td data-align="end" data-value="6">6</td>
          <td data-align="end" data-value="111.83"><b>$111.83</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4821" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4820">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4820</span></td>
          <td data-value="Isabel Moreno"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:280" aria-hidden="true"><span class="mv-avatar-fallback">IM</span></span><div><b>Isabel Moreno</b><small>[email protected]</small></div></div></td>
          <td data-value="paid"><span class="mv-badge" data-variant="success" data-size="sm">Paid</span></td>
          <td class="mv-table-muted">Online store</td>
          <td data-value="2026-09-22" class="mv-table-muted"><time datetime="2026-09-22">Sep 22, 2026</time></td>
          <td data-align="end" data-value="3">3</td>
          <td data-align="end" data-value="351.32"><b>$351.32</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4820" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4819">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4819</span></td>
          <td data-value="Kwame Boateng"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:160" aria-hidden="true"><span class="mv-avatar-fallback">KB</span></span><div><b>Kwame Boateng</b><small>[email protected]</small></div></div></td>
          <td data-value="refunded"><span class="mv-badge" data-variant="secondary" data-size="sm">Refunded</span></td>
          <td class="mv-table-muted">London store</td>
          <td data-value="2026-09-21" class="mv-table-muted"><time datetime="2026-09-21">Sep 21, 2026</time></td>
          <td data-align="end" data-value="1">1</td>
          <td data-align="end" data-value="151.54"><b>$151.54</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4819" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4818">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4818</span></td>
          <td data-value="Sofia Nguyen"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:320" aria-hidden="true"><span class="mv-avatar-fallback">SN</span></span><div><b>Sofia Nguyen</b><small>[email protected]</small></div></div></td>
          <td data-value="pending"><span class="mv-badge" data-variant="warning" data-size="sm">Pending</span></td>
          <td class="mv-table-muted">London store</td>
          <td data-value="2026-09-20" class="mv-table-muted"><time datetime="2026-09-20">Sep 20, 2026</time></td>
          <td data-align="end" data-value="5">5</td>
          <td data-align="end" data-value="167.69"><b>$167.69</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4818" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4817">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4817</span></td>
          <td data-value="Tom Grant"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:240" aria-hidden="true"><span class="mv-avatar-fallback">TG</span></span><div><b>Tom Grant</b><small>[email protected]</small></div></div></td>
          <td data-value="paid"><span class="mv-badge" data-variant="success" data-size="sm">Paid</span></td>
          <td class="mv-table-muted">Online store</td>
          <td data-value="2026-09-20" class="mv-table-muted"><time datetime="2026-09-20">Sep 20, 2026</time></td>
          <td data-align="end" data-value="2">2</td>
          <td data-align="end" data-value="369.71"><b>$369.71</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4817" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4816">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4816</span></td>
          <td data-value="Lena Fischer"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:20" aria-hidden="true"><span class="mv-avatar-fallback">LF</span></span><div><b>Lena Fischer</b><small>[email protected]</small></div></div></td>
          <td data-value="shipped"><span class="mv-badge" data-variant="info" data-size="sm">Shipped</span></td>
          <td class="mv-table-muted">Online store</td>
          <td data-value="2026-09-19" class="mv-table-muted"><time datetime="2026-09-19">Sep 19, 2026</time></td>
          <td data-align="end" data-value="2">2</td>
          <td data-align="end" data-value="264.74"><b>$264.74</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4816" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4815">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4815</span></td>
          <td data-value="Hugo Larsen"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:110" aria-hidden="true"><span class="mv-avatar-fallback">HL</span></span><div><b>Hugo Larsen</b><small>[email protected]</small></div></div></td>
          <td data-value="shipped"><span class="mv-badge" data-variant="info" data-size="sm">Shipped</span></td>
          <td class="mv-table-muted">Mobile app</td>
          <td data-value="2026-09-18" class="mv-table-muted"><time datetime="2026-09-18">Sep 18, 2026</time></td>
          <td data-align="end" data-value="4">4</td>
          <td data-align="end" data-value="100.83"><b>$100.83</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4815" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4814">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4814</span></td>
          <td data-value="Chloe Martin"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:350" aria-hidden="true"><span class="mv-avatar-fallback">CM</span></span><div><b>Chloe Martin</b><small>[email protected]</small></div></div></td>
          <td data-value="shipped"><span class="mv-badge" data-variant="info" data-size="sm">Shipped</span></td>
          <td class="mv-table-muted">Mobile app</td>
          <td data-value="2026-09-17" class="mv-table-muted"><time datetime="2026-09-17">Sep 17, 2026</time></td>
          <td data-align="end" data-value="5">5</td>
          <td data-align="end" data-value="373.11"><b>$373.11</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4814" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4813">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4813</span></td>
          <td data-value="Mehdi Haddad"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:60" aria-hidden="true"><span class="mv-avatar-fallback">MH</span></span><div><b>Mehdi Haddad</b><small>[email protected]</small></div></div></td>
          <td data-value="paid"><span class="mv-badge" data-variant="success" data-size="sm">Paid</span></td>
          <td class="mv-table-muted">Marketplace</td>
          <td data-value="2026-09-17" class="mv-table-muted"><time datetime="2026-09-17">Sep 17, 2026</time></td>
          <td data-align="end" data-value="2">2</td>
          <td data-align="end" data-value="373.29"><b>$373.29</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4813" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4812">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4812</span></td>
          <td data-value="Zoe Brooks"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:300" aria-hidden="true"><span class="mv-avatar-fallback">ZB</span></span><div><b>Zoe Brooks</b><small>[email protected]</small></div></div></td>
          <td data-value="paid"><span class="mv-badge" data-variant="success" data-size="sm">Paid</span></td>
          <td class="mv-table-muted">Online store</td>
          <td data-value="2026-09-16" class="mv-table-muted"><time datetime="2026-09-16">Sep 16, 2026</time></td>
          <td data-align="end" data-value="5">5</td>
          <td data-align="end" data-value="460.93"><b>$460.93</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4812" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4811">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4811</span></td>
          <td data-value="James Miller"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:190" aria-hidden="true"><span class="mv-avatar-fallback">JM</span></span><div><b>James Miller</b><small>[email protected]</small></div></div></td>
          <td data-value="shipped"><span class="mv-badge" data-variant="info" data-size="sm">Shipped</span></td>
          <td class="mv-table-muted">London store</td>
          <td data-value="2026-09-15" class="mv-table-muted"><time datetime="2026-09-15">Sep 15, 2026</time></td>
          <td data-align="end" data-value="5">5</td>
          <td data-align="end" data-value="326.77"><b>$326.77</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4811" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4810">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4810</span></td>
          <td data-value="Emma Park"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:140" aria-hidden="true"><span class="mv-avatar-fallback">EP</span></span><div><b>Emma Park</b><small>[email protected]</small></div></div></td>
          <td data-value="shipped"><span class="mv-badge" data-variant="info" data-size="sm">Shipped</span></td>
          <td class="mv-table-muted">Marketplace</td>
          <td data-value="2026-09-14" class="mv-table-muted"><time datetime="2026-09-14">Sep 14, 2026</time></td>
          <td data-align="end" data-value="4">4</td>
          <td data-align="end" data-value="307.6"><b>$307.60</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4810" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4809">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4809</span></td>
          <td data-value="Noah Reyes"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:230" aria-hidden="true"><span class="mv-avatar-fallback">NR</span></span><div><b>Noah Reyes</b><small>[email protected]</small></div></div></td>
          <td data-value="paid"><span class="mv-badge" data-variant="success" data-size="sm">Paid</span></td>
          <td class="mv-table-muted">Mobile app</td>
          <td data-value="2026-09-14" class="mv-table-muted"><time datetime="2026-09-14">Sep 14, 2026</time></td>
          <td data-align="end" data-value="6">6</td>
          <td data-align="end" data-value="512.1"><b>$512.10</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4809" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4808">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4808</span></td>
          <td data-value="Maya Das"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:10" aria-hidden="true"><span class="mv-avatar-fallback">MD</span></span><div><b>Maya Das</b><small>[email protected]</small></div></div></td>
          <td data-value="paid"><span class="mv-badge" data-variant="success" data-size="sm">Paid</span></td>
          <td class="mv-table-muted">Marketplace</td>
          <td data-value="2026-09-13" class="mv-table-muted"><time datetime="2026-09-13">Sep 13, 2026</time></td>
          <td data-align="end" data-value="4">4</td>
          <td data-align="end" data-value="204.75"><b>$204.75</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4808" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4807">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4807</span></td>
          <td data-value="Yusuf Celik"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:260" aria-hidden="true"><span class="mv-avatar-fallback">YC</span></span><div><b>Yusuf Celik</b><small>[email protected]</small></div></div></td>
          <td data-value="shipped"><span class="mv-badge" data-variant="info" data-size="sm">Shipped</span></td>
          <td class="mv-table-muted">Online store</td>
          <td data-value="2026-09-12" class="mv-table-muted"><time datetime="2026-09-12">Sep 12, 2026</time></td>
          <td data-align="end" data-value="1">1</td>
          <td data-align="end" data-value="197.1"><b>$197.10</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4807" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4806">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4806</span></td>
          <td data-value="Mehdi Haddad"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:60" aria-hidden="true"><span class="mv-avatar-fallback">MH</span></span><div><b>Mehdi Haddad</b><small>[email protected]</small></div></div></td>
          <td data-value="pending"><span class="mv-badge" data-variant="warning" data-size="sm">Pending</span></td>
          <td class="mv-table-muted">London store</td>
          <td data-value="2026-09-11" class="mv-table-muted"><time datetime="2026-09-11">Sep 11, 2026</time></td>
          <td data-align="end" data-value="2">2</td>
          <td data-align="end" data-value="488.94"><b>$488.94</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4806" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4805">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4805</span></td>
          <td data-value="Hugo Larsen"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:110" aria-hidden="true"><span class="mv-avatar-fallback">HL</span></span><div><b>Hugo Larsen</b><small>[email protected]</small></div></div></td>
          <td data-value="paid"><span class="mv-badge" data-variant="success" data-size="sm">Paid</span></td>
          <td class="mv-table-muted">Marketplace</td>
          <td data-value="2026-09-11" class="mv-table-muted"><time datetime="2026-09-11">Sep 11, 2026</time></td>
          <td data-align="end" data-value="5">5</td>
          <td data-align="end" data-value="433.63"><b>$433.63</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4805" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4804">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4804</span></td>
          <td data-value="Lena Fischer"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:20" aria-hidden="true"><span class="mv-avatar-fallback">LF</span></span><div><b>Lena Fischer</b><small>[email protected]</small></div></div></td>
          <td data-value="shipped"><span class="mv-badge" data-variant="info" data-size="sm">Shipped</span></td>
          <td class="mv-table-muted">London store</td>
          <td data-value="2026-09-10" class="mv-table-muted"><time datetime="2026-09-10">Sep 10, 2026</time></td>
          <td data-align="end" data-value="5">5</td>
          <td data-align="end" data-value="387.7"><b>$387.70</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4804" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4803">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4803</span></td>
          <td data-value="Isabel Moreno"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:280" aria-hidden="true"><span class="mv-avatar-fallback">IM</span></span><div><b>Isabel Moreno</b><small>[email protected]</small></div></div></td>
          <td data-value="shipped"><span class="mv-badge" data-variant="info" data-size="sm">Shipped</span></td>
          <td class="mv-table-muted">Online store</td>
          <td data-value="2026-09-09" class="mv-table-muted"><time datetime="2026-09-09">Sep 9, 2026</time></td>
          <td data-align="end" data-value="4">4</td>
          <td data-align="end" data-value="605.59"><b>$605.59</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4803" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4802">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4802</span></td>
          <td data-value="Camila Rossi"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:200" aria-hidden="true"><span class="mv-avatar-fallback">CR</span></span><div><b>Camila Rossi</b><small>[email protected]</small></div></div></td>
          <td data-value="shipped"><span class="mv-badge" data-variant="info" data-size="sm">Shipped</span></td>
          <td class="mv-table-muted">London store</td>
          <td data-value="2026-09-08" class="mv-table-muted"><time datetime="2026-09-08">Sep 8, 2026</time></td>
          <td data-align="end" data-value="5">5</td>
          <td data-align="end" data-value="210.58"><b>$210.58</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4802" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4801">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4801</span></td>
          <td data-value="Tom Grant"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:240" aria-hidden="true"><span class="mv-avatar-fallback">TG</span></span><div><b>Tom Grant</b><small>[email protected]</small></div></div></td>
          <td data-value="shipped"><span class="mv-badge" data-variant="info" data-size="sm">Shipped</span></td>
          <td class="mv-table-muted">Online store</td>
          <td data-value="2026-09-08" class="mv-table-muted"><time datetime="2026-09-08">Sep 8, 2026</time></td>
          <td data-align="end" data-value="3">3</td>
          <td data-align="end" data-value="569.74"><b>$569.74</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4801" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4800">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4800</span></td>
          <td data-value="Chloe Martin"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:350" aria-hidden="true"><span class="mv-avatar-fallback">CM</span></span><div><b>Chloe Martin</b><small>[email protected]</small></div></div></td>
          <td data-value="paid"><span class="mv-badge" data-variant="success" data-size="sm">Paid</span></td>
          <td class="mv-table-muted">Online store</td>
          <td data-value="2026-09-07" class="mv-table-muted"><time datetime="2026-09-07">Sep 7, 2026</time></td>
          <td data-align="end" data-value="4">4</td>
          <td data-align="end" data-value="397.99"><b>$397.99</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4800" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4799">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4799</span></td>
          <td data-value="Sofia Nguyen"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:320" aria-hidden="true"><span class="mv-avatar-fallback">SN</span></span><div><b>Sofia Nguyen</b><small>[email protected]</small></div></div></td>
          <td data-value="shipped"><span class="mv-badge" data-variant="info" data-size="sm">Shipped</span></td>
          <td class="mv-table-muted">London store</td>
          <td data-value="2026-09-06" class="mv-table-muted"><time datetime="2026-09-06">Sep 6, 2026</time></td>
          <td data-align="end" data-value="2">2</td>
          <td data-align="end" data-value="98.45"><b>$98.45</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4799" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4798">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4798</span></td>
          <td data-value="Hugo Larsen"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:110" aria-hidden="true"><span class="mv-avatar-fallback">HL</span></span><div><b>Hugo Larsen</b><small>[email protected]</small></div></div></td>
          <td data-value="refunded"><span class="mv-badge" data-variant="secondary" data-size="sm">Refunded</span></td>
          <td class="mv-table-muted">London store</td>
          <td data-value="2026-09-05" class="mv-table-muted"><time datetime="2026-09-05">Sep 5, 2026</time></td>
          <td data-align="end" data-value="2">2</td>
          <td data-align="end" data-value="326.83"><b>$326.83</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4798" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4797">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4797</span></td>
          <td data-value="Hugo Larsen"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:110" aria-hidden="true"><span class="mv-avatar-fallback">HL</span></span><div><b>Hugo Larsen</b><small>[email protected]</small></div></div></td>
          <td data-value="shipped"><span class="mv-badge" data-variant="info" data-size="sm">Shipped</span></td>
          <td class="mv-table-muted">Marketplace</td>
          <td data-value="2026-09-05" class="mv-table-muted"><time datetime="2026-09-05">Sep 5, 2026</time></td>
          <td data-align="end" data-value="4">4</td>
          <td data-align="end" data-value="567.46"><b>$567.46</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4797" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4796">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4796</span></td>
          <td data-value="Emma Park"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:140" aria-hidden="true"><span class="mv-avatar-fallback">EP</span></span><div><b>Emma Park</b><small>[email protected]</small></div></div></td>
          <td data-value="pending"><span class="mv-badge" data-variant="warning" data-size="sm">Pending</span></td>
          <td class="mv-table-muted">Mobile app</td>
          <td data-value="2026-09-04" class="mv-table-muted"><time datetime="2026-09-04">Sep 4, 2026</time></td>
          <td data-align="end" data-value="4">4</td>
          <td data-align="end" data-value="241.16"><b>$241.16</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4796" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
        <tr data-id="ORD-4795">
          <td></td>
          <td><span class="mv-data-table-mono">ORD-4795</span></td>
          <td data-value="Kwame Boateng"><div class="mv-data-table-person"><span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:160" aria-hidden="true"><span class="mv-avatar-fallback">KB</span></span><div><b>Kwame Boateng</b><small>[email protected]</small></div></div></td>
          <td data-value="paid"><span class="mv-badge" data-variant="success" data-size="sm">Paid</span></td>
          <td class="mv-table-muted">Mobile app</td>
          <td data-value="2026-09-03" class="mv-table-muted"><time datetime="2026-09-03">Sep 3, 2026</time></td>
          <td data-align="end" data-value="6">6</td>
          <td data-align="end" data-value="112.11"><b>$112.11</b></td>
          <td data-align="end"><button class="mv-button" data-variant="ghost" data-size="icon" type="button" aria-label="Actions for ORD-4795" data-dt-demo-row><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg></button></td>
        </tr>
      </tbody>
    </table>
  </div>
  <div class="mv-data-table-footer">
    <span data-dt-selection aria-live="polite"></span>
    <label>Rows per page
      <select class="mv-select" data-size="sm" data-dt-page-size>
        <option>5</option><option>8</option><option>10</option><option>20</option>
      </select>
    </label>
    <span data-dt-page-info></span>
    <mv-pagination data-size="sm" hide-labels siblings="0" data-dt-always label="Orders pagination"></mv-pagination>
  </div>
  <div class="mv-data-table-bulk" role="toolbar" aria-label="Bulk actions" hidden>
    <span data-dt-bulk-count></span>
    <button class="mv-button" data-variant="ghost" data-size="sm" type="button" data-dt-action="ship"><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 6h11v10H3zM14 10h4l3 3v3h-7"/><circle cx="7" cy="18" r="1.8"/><circle cx="17.5" cy="18" r="1.8"/></svg><span data-dt-label>Mark as shipped</span></button>
    <button class="mv-button" data-variant="ghost" data-size="sm" type="button" data-dt-action="export"><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 3v12m-5-5 5 5 5-5M5 21h14"/></svg><span data-dt-label>Export</span></button>
    <button class="mv-button" data-variant="ghost" data-size="sm" type="button" data-dt-action="delete" style="color:var(--mv-danger)"><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 7h16M10 11v6M14 11v6M6 7l1 13h10l1-13M9 7V4h6v3"/></svg><span data-dt-label>Delete</span></button>
    <button class="mv-button" data-variant="ghost" data-size="icon" type="button" data-dt-clear aria-label="Clear selection"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12"/></svg></button>
  </div>
</mv-data-table>
<p id="dt-demo-log" role="status" style="margin:.75rem 0 0;min-height:1.25rem;color:var(--mv-fg-muted);font-size:.75rem;text-align:center"></p>
</div>
<div id="dt-statement" style="width:min(100%,64rem);margin:3rem auto 0">
<p style="margin:0 0 .75rem;font-weight:600">Usage statement, September 2026</p>
<mv-data-table mode="server" page-size="5" label="Usage statement, September 2026" loading>
  <div class="mv-data-table-toolbar">
    <div class="mv-input-group" data-size="sm">
      <label class="mv-input-group-addon" for="dt-statement-q"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg><span class="mv-sr-only">Search</span></label>
      <input id="dt-statement-q" class="mv-input" type="search" placeholder="Search resources…" data-dt-search autocomplete="off">
    </div>
    <select class="mv-select" data-size="sm" data-dt-filter="service" aria-label="Filter by service">
      <option value="">All services</option>
      <option>Compute</option>
      <option>Storage</option>
      <option>Bandwidth</option>
      <option>Database</option>
      <option>Functions</option>
    </select>
  </div>
  <div class="mv-table-wrap">
    <table class="mv-table">
      <thead>
        <tr>
          <th scope="col" data-dt-select></th>
          <th scope="col" data-key="resource" data-sort="text" data-card="title">Resource</th>
          <th scope="col" data-key="service" data-sort="text" data-card="subtitle">Service</th>
          <th scope="col" data-key="date" data-sort="date" data-card="meta">Date</th>
          <th scope="col" data-key="usage" data-sort="number" data-align="end">Usage</th>
          <th scope="col" data-key="amount" data-sort="number" data-align="end" data-card="amount">Amount</th>
        </tr>
      </thead>
      <tbody></tbody>
      <tfoot>
        <tr><th scope="row">Subtotal</th><td data-key="amount" data-dt-statement="subtotal"></td></tr>
        <tr><th scope="row">Sales tax (8.875%)</th><td data-key="amount" data-dt-statement="tax"></td></tr>
        <tr><th scope="row">Total due</th><td data-key="amount" data-dt-statement="total"></td></tr>
      </tfoot>
    </table>
  </div>
  <div class="mv-data-table-footer">
    <span data-dt-selection aria-live="polite"></span>
    <label>Rows per page
      <select class="mv-select" data-size="sm" data-dt-page-size>
        <option>5</option><option>10</option><option>25</option>
      </select>
    </label>
    <span data-dt-page-info></span>
    <mv-pagination data-size="sm" hide-labels siblings="0" label="Statement pagination"></mv-pagination>
  </div>
</mv-data-table>
</div>
<script type="module">
  // A fake API: 312 usage lines, filtered, sorted and paged "on the server", 350 ms away.
  const root = document.getElementById("dt-statement");
  const table = root?.querySelector("mv-data-table");
  const SERVICES = [
    { name: "Compute", unit: "vCPU-hours", rate: 0.0416, resources: ["api-gateway-1", "api-gateway-2", "worker-us-east", "worker-eu-west", "batch-runner"] },
    { name: "Storage", unit: "GB-months", rate: 0.023, resources: ["media-bucket", "nightly-backups", "logs-archive"] },
    { name: "Bandwidth", unit: "GB", rate: 0.09, resources: ["cdn-edge-us", "cdn-edge-eu", "cdn-edge-apac"] },
    { name: "Database", unit: "instance-hours", rate: 0.171, resources: ["orders-db-primary", "orders-db-replica", "analytics-warehouse"] },
    { name: "Functions", unit: "million requests", rate: 0.2, resources: ["image-resize", "webhook-relay", "email-digest"] },
  ];
  let seed = 7;
  const rand = () => ((seed = (seed * 16807) % 2147483647) - 1) / 2147483646;
  const LINES = Array.from({ length: 312 }, (_, i) => {
    const s = SERVICES[Math.floor(rand() * SERVICES.length)];
    const day = 1 + Math.floor(rand() * 30);
    const usage = Math.round((40 + rand() * 1800) * 10) / 10;
    return { id: `LN-${String(i + 1).padStart(4, "0")}`, resource: s.resources[Math.floor(rand() * s.resources.length)], service: s.name, unit: s.unit, date: `2026-09-${String(day).padStart(2, "0")}`, usage, amount: Math.round(usage * s.rate * 100) / 100 };
  });
  const money = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" });
  const number = new Intl.NumberFormat("en-US");
  const day = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", year: "numeric", timeZone: "UTC" });

  function fakeApi({ page, pageSize, sort, direction, query, filters }) {
    const q = query.toLowerCase();
    let rows = LINES.filter((l) => (!q || `${l.resource} ${l.service}`.toLowerCase().includes(q)) && (!filters.service || l.service === filters.service));
    if (sort) {
      const sign = direction === "descending" ? -1 : 1;
      rows = [...rows].sort((a, b) => sign * (typeof a[sort] === "number" ? a[sort] - b[sort] : String(a[sort]).localeCompare(String(b[sort]))));
    }
    const subtotal = rows.reduce((sum, l) => sum + l.amount, 0);
    const start = (page - 1) * pageSize;
    return new Promise((done) => setTimeout(() => done({ rows: rows.slice(start, start + pageSize), total: rows.length, subtotal }), 350));
  }

  function cell(text, attrs = {}) {
    const td = document.createElement("td");
    for (const [k, v] of Object.entries(attrs)) td.setAttribute(k, v);
    td.append(text);
    return td;
  }
  function rowFor(line) {
    const tr = document.createElement("tr");
    tr.dataset.id = line.id;
    tr.dataset.dtName = `${line.resource}, ${day.format(new Date(line.date))}`;
    const resource = document.createElement("span");
    resource.className = "mv-data-table-mono";
    resource.style.color = "var(--mv-fg)";
    resource.textContent = line.resource;
    const amount = document.createElement("b");
    amount.textContent = money.format(line.amount);
    tr.append(
      cell(""),
      cell(resource),
      cell(line.service, { class: "mv-table-muted" }),
      cell(day.format(new Date(line.date)), { "data-value": line.date, class: "mv-table-muted" }),
      cell(`${number.format(line.usage)} ${line.unit}`, { "data-value": String(line.usage), "data-align": "end" }),
      cell(amount, { "data-value": String(line.amount), "data-align": "end" }),
    );
    return tr;
  }

  let ticket = 0;
  table?.addEventListener("mv-request", async (e) => {
    const mine = ++ticket;
    table.loading = true;
    const res = await fakeApi(e.detail);
    if (mine !== ticket) return; // a newer request is on its way
    table.setRows(res.rows.map(rowFor), res.total);
    const tax = Math.round(res.subtotal * 0.08875 * 100) / 100;
    root.querySelector('[data-dt-statement="subtotal"]').textContent = money.format(res.subtotal);
    root.querySelector('[data-dt-statement="tax"]').textContent = money.format(tax);
    root.querySelector('[data-dt-statement="total"]').textContent = money.format(res.subtotal + tax);
  });
  customElements.whenDefined("mv-data-table").then(() => table?.refresh());
</script>
<script type="module">
  const root = document.getElementById("dt-demo");
  const table = root?.querySelector("mv-data-table");
  const log = root?.querySelector("#dt-demo-log");
  table?.addEventListener("mv-action", (e) => {
    const { action, rows } = e.detail;
    const n = rows.length;
    const s = n > 1 ? "s" : "";
    if (action === "delete") {
      table.removeRows(rows);
      log.textContent = `${n} order${s} deleted.`;
    } else if (action === "ship") {
      for (const row of rows) {
        const cell = row.cells[3];
        cell.dataset.value = "shipped";
        cell.innerHTML = '<span class="mv-badge" data-variant="info" data-size="sm">Shipped</span>';
      }
      table.clearSelection();
      log.textContent = `${n} order${s} marked as shipped.`;
    } else if (action === "export") {
      log.textContent = `CSV export of ${n} order${s} ready (demo).`;
    }
  });
  root?.querySelector("[data-dt-demo-export]")?.addEventListener("click", () => { log.textContent = "Export of all orders ready (demo)."; });
  root?.addEventListener("click", (e) => {
    const b = e.target.closest("[data-dt-demo-row]");
    if (b) log.textContent = `Actions menu: ${b.closest("tr").dataset.id}`;
  });
</script>

API

Attributes

Name类型DefaultDescription
page-sizenumber10Rows per page (kept in sync with select[data-dt-page-size]).
labelstringAccessible name for the table (aria-label) if it has none.
modeclient | serverclientclient: sorts, searches, filters and pages the authored rows in the browser. server: the app owns the data; the element keeps the sort, page, search, filters, selection and visible columns, emits mv-request on each change and shows the rows the app puts in the tbody (or passes to setRows). Set it before the element connects.
totalnumberServer mode: total row count across all pages, from the app (drives the page count, the range text and “n of N rows selected”). Without it, the rows in the tbody are the total.
loadingbooleanSet by the app while a page loads: aria-busy on the table, dimmed rows, a thin progress bar, and a “Loading…” row when the tbody is empty. setRows() clears it.
data-selectingbooleanSet while at least one row is selected.
data-cardsbooleanSet by the element when at least one th has data-card: rows become stacked cards in containers narrower than 40rem.

Properties

Name类型Description
selectedRowsHTMLTableRowElement[]Selected rows present in the DOM (client mode: across all pages).
selectedIdsstring[]data-id of the selected rows. In server mode the selection is kept by data-id across pages, including rows no longer loaded. Settable, also before the element is defined.
pagenumberCurrent page (1-based). Setting it re-renders without emitting mv-request (restore a page from the URL, then load it).
state{ page, pageSize, sort, direction, query, filters }Read-only: what the app needs to load the current page (the mv-request detail without reason).
stringsobjectOverride any default text: selectAll, selectRow ({name}), columns, empty, emptyHint ({reset}), reset, loading, range ({start}, {end}, {total}), results ({ one, other } with {n}), selection ({ one, other } with {count}, {total}), bulkCount ({count}), sorted ({column}, {direction}), ascending, descending, unsorted. Numbers, plurals and text sorting follow the nearest lang (Intl). Can be set before the element is defined.

Methods

NameDescription
sortBy(key, direction?)Sorts by data-key ('ascending' | 'descending'); null clears the sort. Server mode: back to page 1 and mv-request.
setColumnVisible(key, visible)Shows or hides a column (header, rows, rows loaded later and the totals footer).
clearSelection()Clears the selection (every page).
resetFilters()Clears the search and filters.
setRows(rows, total?)Replaces the rows with new <tr> elements. Server mode: the current page, plus the total when given; clears loading. Client mode: the whole data set.
refresh()Server mode: emits mv-request again with the current state (first load, retry, after a bulk change).
removeRows(rows)Removes rows (e.g. after a bulk delete).
render()Re-renders after cells change.

Events

NameDescription
mv-requestServer mode: load this page. detail: { page, pageSize, sort, direction, query, filters, reason } with reason sort | page | pageSize | search (300 ms after typing stops) | filter | reset | refresh.
mv-actionA [data-dt-action] button was clicked. detail: { action, rows, ids }.
mv-sortSort changed. detail: { key, direction }.
mv-pageThe user changed page. detail: { page, pageSize }.
mv-columnsColumn visibility changed. detail: { key, visible }.
mv-renderAfter each render. detail: { total, page, pages, rows } (client mode: rows matching the search and filters, all pages; server mode: the loaded rows).

Content structure

NameDescription
table.mv-tablePlain HTML table: th[data-key] names the column, th[data-sort=text|number|date] makes it sortable, th[data-dt-select] adds the checkbox column (named after tr[data-dt-name], else the next cell); td[data-value] provides the raw value for sorting and filtering; tr[data-id] keys the selection in server mode.
th[data-card]Where the column goes when rows become cards (container under 40rem): title, subtitle (muted, under the title), amount (top right), meta (under the amount), action (far right), detail (label and value line, the default for columns without a role) or hide. One th[data-card] turns the layout on; the header row becomes a bar with select all and the sort buttons.
tfootTotals footer: each row has one label cell (th scope=row) then cells with data-key="column". The label spans the columns before the first value, each value sits under its column, gaps are filled and recomputed when columns are hidden; the last of several rows is styled as the grand total. In cards, label left and value right. The app writes the values.
[data-dt-search]Search field (matches row text, ignoring accents and case).
select[data-dt-filter="key"]Exact-match filter on a column value (empty option = all).
mv-menu[data-dt-columns]Menu whose [data-content] panel is filled with column checkboxes (th[data-dt-hideable=false] opts out).
[data-dt-selection] / [data-dt-page-info]Auto-filled text: “n of N rows selected”, “1-8 of 27”.
select[data-dt-page-size]Rows-per-page picker.
mv-paginationControlled pagination (hidden when there is only one page, unless data-dt-always).
.mv-data-table-bulkFloating bulk-actions bar, shown as soon as a row is checked; [data-dt-bulk-count], [data-dt-action], [data-dt-clear].

CSS classes

NameDescription
mv-data-table-toolbar / -toolbar-end / -footerBars above and below the table.
[data-dt-label]Button label visually hidden in narrow containers (still the accessible name).
mv-data-table-person / -monoAvatar + name + email cells, monospace IDs.

Accessibility

Sorting uses real buttons in the headers with aria-sort; checkboxes are named after their row (“Select ORD-4821”) and the header checkbox supports the indeterminate state; rows carry aria-selected; the result count is announced (role=status) after a search or filter, and so are header sorts (“Sorted by Amount, ascending”) and Shift + click ranges; the actions bar is a named role=toolbar; the column menu uses menuitemcheckbox items and stays open; pagination is keyboard navigable; when the bulk bar or the empty state disappears, focus moves back into the table instead of the page; forced colors get system focus outlines. Tables with card roles state role=table, rowgroup, row, columnheader, rowheader and cell explicitly, so the card layout keeps its table semantics in every browser; the column name shown before a detail value is generated content hidden from assistive tech (the cell header already names it). In server mode the table carries aria-busy while loading, and the result count is announced once the new data has arrived after a search or filter. Totals rows use a th scope=row label.

本页面由 AI 翻译。报告翻译问题