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 |
| Keywords | data-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 | 类型 | Default | Description |
|---|---|---|---|
page-size | number | 10 | Rows per page (kept in sync with select[data-dt-page-size]). |
label | string | Accessible name for the table (aria-label) if it has none. | |
mode | client | server | client | client: 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. |
total | number | Server 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. | |
loading | boolean | Set 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-selecting | boolean | Set while at least one row is selected. | |
data-cards | boolean | Set by the element when at least one th has data-card: rows become stacked cards in containers narrower than 40rem. |
Properties
| Name | 类型 | Description |
|---|---|---|
selectedRows | HTMLTableRowElement[] | Selected rows present in the DOM (client mode: across all pages). |
selectedIds | string[] | 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. |
page | number | Current 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). |
strings | object | Override 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
| Name | Description |
|---|---|
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
| Name | Description |
|---|---|
mv-request | Server 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-action | A [data-dt-action] button was clicked. detail: { action, rows, ids }. |
mv-sort | Sort changed. detail: { key, direction }. |
mv-page | The user changed page. detail: { page, pageSize }. |
mv-columns | Column visibility changed. detail: { key, visible }. |
mv-render | After each render. detail: { total, page, pages, rows } (client mode: rows matching the search and filters, all pages; server mode: the loaded rows). |
Content structure
| Name | Description |
|---|---|
table.mv-table | Plain 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. |
tfoot | Totals 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-pagination | Controlled pagination (hidden when there is only one page, unless data-dt-always). |
.mv-data-table-bulk | Floating bulk-actions bar, shown as soon as a row is checked; [data-dt-bulk-count], [data-dt-action], [data-dt-clear]. |
CSS classes
| Name | Description |
|---|---|
mv-data-table-toolbar / -toolbar-end / -footer | Bars above and below the table. |
[data-dt-label] | Button label visually hidden in narrow containers (still the accessible name). |
mv-data-table-person / -mono | Avatar + 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.