J3W1 / DESIGN SYSTEM
Data table
A native table with controls: a selection column with a mixed select-all, sortable headers, row focus with the ring on the fill, and static resize handles.
stable · LIGHT DOM · 1.1.0
Light DOM retains static content, native semantics, label relationships and host composition without duplicating controls.
Live variants
default
| Atlas library | Avery Kim | Ready | 24 |
| Orion workspace | Jordan Lee | In progress | 12 |
| Field notes | Morgan Noor | Ready | 38 |
| Signal monitor | Riley Stone | Review | 6 |
| No rows match. | |||
| ⋯ Loading rows | |||
<j3w1-data-table id="example-data-table-default"><div class="table-scroller" tabindex="0" role="region" aria-label="Project records"><table class="data-table"><caption class="table-caption">Project records · synthetic fixture</caption><thead class="data-table-head"><tr><th class="data-table-header" scope="col"><button class="data-table-sort" type="button">Project<span class="data-table-sort-glyph" aria-hidden="true">↕</span></button></th><th class="data-table-header" scope="col"><button class="data-table-sort" type="button">Owner<span class="data-table-sort-glyph" aria-hidden="true">↕</span></button></th><th class="data-table-header" scope="col"><button class="data-table-sort" type="button">Status<span class="data-table-sort-glyph" aria-hidden="true">↕</span></button></th><th class="data-table-header" scope="col"><button class="data-table-sort" type="button">Scope<span class="data-table-sort-glyph" aria-hidden="true">↕</span></button></th></tr></thead><tbody class="data-table-body"><tr class="data-table-row" id="example-data-table-default-record-0" data-status="Ready" aria-selected="false" tabindex="0"><td class="data-table-cell" id="example-data-table-default-record-name-0">Atlas library</td><td class="data-table-cell">Avery Kim</td><td class="data-table-cell">Ready</td><td class="data-table-cell">24</td></tr><tr class="data-table-row" id="example-data-table-default-record-1" data-status="In progress" aria-selected="false" tabindex="-1"><td class="data-table-cell" id="example-data-table-default-record-name-1">Orion workspace</td><td class="data-table-cell">Jordan Lee</td><td class="data-table-cell">In progress</td><td class="data-table-cell">12</td></tr><tr class="data-table-row" id="example-data-table-default-record-2" data-status="Ready" aria-selected="false" tabindex="-1"><td class="data-table-cell" id="example-data-table-default-record-name-2">Field notes</td><td class="data-table-cell">Morgan Noor</td><td class="data-table-cell">Ready</td><td class="data-table-cell">38</td></tr><tr class="data-table-row" id="example-data-table-default-record-3" data-status="Review" aria-selected="false" tabindex="-1"><td class="data-table-cell" id="example-data-table-default-record-name-3">Signal monitor</td><td class="data-table-cell">Riley Stone</td><td class="data-table-cell">Review</td><td class="data-table-cell">6</td></tr><tr class="data-table-empty" hidden=""><td class="data-table-empty-cell" colspan="4">No rows match.</td></tr><tr class="data-table-loading" hidden=""><td colspan="4">⋯ Loading rows</td></tr></tbody></table></div></j3w1-data-table>selectable
| Atlas library | Avery Kim | Ready | 24 | |
| Orion workspace | Jordan Lee | In progress | 12 | |
| Field notes | Morgan Noor | Ready | 38 | |
| Signal monitor | Riley Stone | Review | 6 | |
| No rows match. | ||||
| ⋯ Loading rows | ||||
<j3w1-data-table id="example-data-table-selectable"><div class="table-scroller" tabindex="0" role="region" aria-label="Project records"><table class="data-table"><caption class="table-caption">Project records · synthetic fixture</caption><thead class="data-table-head"><tr><th class="data-table-header data-table-header-select" scope="col"><button class="data-table-select-all" type="button" role="checkbox" aria-checked="false" aria-label="Select all rows"><span class="data-table-check-glyph" aria-hidden="true">✓</span></button></th><th class="data-table-header" scope="col"><button class="data-table-sort" type="button">Project<span class="data-table-sort-glyph" aria-hidden="true">↕</span></button></th><th class="data-table-header" scope="col"><button class="data-table-sort" type="button">Owner<span class="data-table-sort-glyph" aria-hidden="true">↕</span></button></th><th class="data-table-header" scope="col"><button class="data-table-sort" type="button">Status<span class="data-table-sort-glyph" aria-hidden="true">↕</span></button></th><th class="data-table-header" scope="col"><button class="data-table-sort" type="button">Scope<span class="data-table-sort-glyph" aria-hidden="true">↕</span></button></th></tr></thead><tbody class="data-table-body"><tr class="data-table-row" id="example-data-table-selectable-record-0" data-status="Ready" aria-selected="false" tabindex="0"><td class="data-table-cell data-table-cell-select"><input class="data-table-checkbox" type="checkbox" aria-labelledby="example-data-table-selectable-record-name-0" name="example-data-table-selectable-value"></td><td class="data-table-cell" id="example-data-table-selectable-record-name-0">Atlas library</td><td class="data-table-cell">Avery Kim</td><td class="data-table-cell">Ready</td><td class="data-table-cell">24</td></tr><tr class="data-table-row" id="example-data-table-selectable-record-1" data-status="In progress" aria-selected="false" tabindex="-1"><td class="data-table-cell data-table-cell-select"><input class="data-table-checkbox" type="checkbox" aria-labelledby="example-data-table-selectable-record-name-1" name="example-data-table-selectable-value"></td><td class="data-table-cell" id="example-data-table-selectable-record-name-1">Orion workspace</td><td class="data-table-cell">Jordan Lee</td><td class="data-table-cell">In progress</td><td class="data-table-cell">12</td></tr><tr class="data-table-row" id="example-data-table-selectable-record-2" data-status="Ready" aria-selected="false" tabindex="-1"><td class="data-table-cell data-table-cell-select"><input class="data-table-checkbox" type="checkbox" aria-labelledby="example-data-table-selectable-record-name-2" name="example-data-table-selectable-value"></td><td class="data-table-cell" id="example-data-table-selectable-record-name-2">Field notes</td><td class="data-table-cell">Morgan Noor</td><td class="data-table-cell">Ready</td><td class="data-table-cell">38</td></tr><tr class="data-table-row" id="example-data-table-selectable-record-3" data-status="Review" aria-selected="false" tabindex="-1"><td class="data-table-cell data-table-cell-select"><input class="data-table-checkbox" type="checkbox" aria-labelledby="example-data-table-selectable-record-name-3" name="example-data-table-selectable-value"></td><td class="data-table-cell" id="example-data-table-selectable-record-name-3">Signal monitor</td><td class="data-table-cell">Riley Stone</td><td class="data-table-cell">Review</td><td class="data-table-cell">6</td></tr><tr class="data-table-empty" hidden=""><td class="data-table-empty-cell" colspan="5">No rows match.</td></tr><tr class="data-table-loading" hidden=""><td colspan="5">⋯ Loading rows</td></tr></tbody></table></div></j3w1-data-table>resizable
| Atlas library | Avery Kim | Ready | 24 | |
| Orion workspace | Jordan Lee | In progress | 12 | |
| Field notes | Morgan Noor | Ready | 38 | |
| Signal monitor | Riley Stone | Review | 6 | |
| No rows match. | ||||
| ⋯ Loading rows | ||||
<j3w1-data-table id="example-data-table-resizable"><div class="table-scroller" tabindex="0" role="region" aria-label="Project records"><table class="data-table"><caption class="table-caption">Project records · synthetic fixture</caption><thead class="data-table-head"><tr><th class="data-table-header data-table-header-select" scope="col"><button class="data-table-select-all" type="button" role="checkbox" aria-checked="false" aria-label="Select all rows"><span class="data-table-check-glyph" aria-hidden="true">✓</span></button></th><th class="data-table-header" scope="col"><button class="data-table-sort" type="button">Project<span class="data-table-sort-glyph" aria-hidden="true">↕</span></button><span class="data-table-resize" aria-hidden="true"></span><j3w1-menu><div class="menu"><button class="menu-button" type="button" id="example-data-table-resizable-width-trigger-1" aria-haspopup="menu" aria-expanded="false" aria-controls="example-data-table-resizable-width-menu-1">Width: Project</button><ul class="menu-list" role="menu" id="example-data-table-resizable-width-menu-1" aria-labelledby="example-data-table-resizable-width-trigger-1" hidden=""><li role="none"><button class="menu-item" type="button" role="menuitem" tabindex="-1" data-action="resize:1:narrow">Narrower</button></li><li role="none"><button class="menu-item" type="button" role="menuitem" tabindex="-1" data-action="resize:1:widen">Wider</button></li><li role="none"><button class="menu-item" type="button" role="menuitem" tabindex="-1" data-action="resize:1:reset">Reset width</button></li></ul></div></j3w1-menu></th><th class="data-table-header" scope="col"><button class="data-table-sort" type="button">Owner<span class="data-table-sort-glyph" aria-hidden="true">↕</span></button><span class="data-table-resize" aria-hidden="true"></span><j3w1-menu><div class="menu"><button class="menu-button" type="button" id="example-data-table-resizable-width-trigger-2" aria-haspopup="menu" aria-expanded="false" aria-controls="example-data-table-resizable-width-menu-2">Width: Owner</button><ul class="menu-list" role="menu" id="example-data-table-resizable-width-menu-2" aria-labelledby="example-data-table-resizable-width-trigger-2" hidden=""><li role="none"><button class="menu-item" type="button" role="menuitem" tabindex="-1" data-action="resize:2:narrow">Narrower</button></li><li role="none"><button class="menu-item" type="button" role="menuitem" tabindex="-1" data-action="resize:2:widen">Wider</button></li><li role="none"><button class="menu-item" type="button" role="menuitem" tabindex="-1" data-action="resize:2:reset">Reset width</button></li></ul></div></j3w1-menu></th><th class="data-table-header" scope="col"><button class="data-table-sort" type="button">Status<span class="data-table-sort-glyph" aria-hidden="true">↕</span></button><span class="data-table-resize" aria-hidden="true"></span><j3w1-menu><div class="menu"><button class="menu-button" type="button" id="example-data-table-resizable-width-trigger-3" aria-haspopup="menu" aria-expanded="false" aria-controls="example-data-table-resizable-width-menu-3">Width: Status</button><ul class="menu-list" role="menu" id="example-data-table-resizable-width-menu-3" aria-labelledby="example-data-table-resizable-width-trigger-3" hidden=""><li role="none"><button class="menu-item" type="button" role="menuitem" tabindex="-1" data-action="resize:3:narrow">Narrower</button></li><li role="none"><button class="menu-item" type="button" role="menuitem" tabindex="-1" data-action="resize:3:widen">Wider</button></li><li role="none"><button class="menu-item" type="button" role="menuitem" tabindex="-1" data-action="resize:3:reset">Reset width</button></li></ul></div></j3w1-menu></th><th class="data-table-header" scope="col"><button class="data-table-sort" type="button">Scope<span class="data-table-sort-glyph" aria-hidden="true">↕</span></button><span class="data-table-resize" aria-hidden="true"></span><j3w1-menu><div class="menu"><button class="menu-button" type="button" id="example-data-table-resizable-width-trigger-4" aria-haspopup="menu" aria-expanded="false" aria-controls="example-data-table-resizable-width-menu-4">Width: Scope</button><ul class="menu-list" role="menu" id="example-data-table-resizable-width-menu-4" aria-labelledby="example-data-table-resizable-width-trigger-4" hidden=""><li role="none"><button class="menu-item" type="button" role="menuitem" tabindex="-1" data-action="resize:4:narrow">Narrower</button></li><li role="none"><button class="menu-item" type="button" role="menuitem" tabindex="-1" data-action="resize:4:widen">Wider</button></li><li role="none"><button class="menu-item" type="button" role="menuitem" tabindex="-1" data-action="resize:4:reset">Reset width</button></li></ul></div></j3w1-menu></th></tr></thead><tbody class="data-table-body"><tr class="data-table-row" id="example-data-table-resizable-record-0" data-status="Ready" aria-selected="false" tabindex="0"><td class="data-table-cell data-table-cell-select"><input class="data-table-checkbox" type="checkbox" aria-labelledby="example-data-table-resizable-record-name-0" name="example-data-table-resizable-value"></td><td class="data-table-cell" id="example-data-table-resizable-record-name-0">Atlas library</td><td class="data-table-cell">Avery Kim</td><td class="data-table-cell">Ready</td><td class="data-table-cell">24</td></tr><tr class="data-table-row" id="example-data-table-resizable-record-1" data-status="In progress" aria-selected="false" tabindex="-1"><td class="data-table-cell data-table-cell-select"><input class="data-table-checkbox" type="checkbox" aria-labelledby="example-data-table-resizable-record-name-1" name="example-data-table-resizable-value"></td><td class="data-table-cell" id="example-data-table-resizable-record-name-1">Orion workspace</td><td class="data-table-cell">Jordan Lee</td><td class="data-table-cell">In progress</td><td class="data-table-cell">12</td></tr><tr class="data-table-row" id="example-data-table-resizable-record-2" data-status="Ready" aria-selected="false" tabindex="-1"><td class="data-table-cell data-table-cell-select"><input class="data-table-checkbox" type="checkbox" aria-labelledby="example-data-table-resizable-record-name-2" name="example-data-table-resizable-value"></td><td class="data-table-cell" id="example-data-table-resizable-record-name-2">Field notes</td><td class="data-table-cell">Morgan Noor</td><td class="data-table-cell">Ready</td><td class="data-table-cell">38</td></tr><tr class="data-table-row" id="example-data-table-resizable-record-3" data-status="Review" aria-selected="false" tabindex="-1"><td class="data-table-cell data-table-cell-select"><input class="data-table-checkbox" type="checkbox" aria-labelledby="example-data-table-resizable-record-name-3" name="example-data-table-resizable-value"></td><td class="data-table-cell" id="example-data-table-resizable-record-name-3">Signal monitor</td><td class="data-table-cell">Riley Stone</td><td class="data-table-cell">Review</td><td class="data-table-cell">6</td></tr><tr class="data-table-empty" hidden=""><td class="data-table-empty-cell" colspan="5">No rows match.</td></tr><tr class="data-table-loading" hidden=""><td colspan="5">⋯ Loading rows</td></tr></tbody></table></div></j3w1-data-table>Install and register
Use the exact version. If npm publication is pending, install the release tarball from the installation guide.
npm install --save-exact @j3w1/ui@1.1.0import '@j3w1/ui/tokens.css';
import '@j3w1/ui/styles/data-table.css';
import '@j3w1/ui/register/data-table';Insert the maintained markup above after loading these imports. Its native children provide accessible names and form behavior; the empty custom tag alone does not create a control.
Copy the complete implementation
npx --package=@j3w1/ui@1.1.0 j3w1-ui copy data-table --out ./vendor/j3w1/data-tableThe CLI verifies every file before writing and refuses overwrites. Serve the output over HTTP and open index.html. Keep its modules, CSS, tokens and licence notices together.
Public API
attributes
| Name | Type / signature | Meaning |
|---|---|---|
disabled | CustomEvent | Disable the native controls owned by this instance. |
loading | CustomEvent | Mark the primary control busy and prevent duplicate activation. |
properties
| Name | Type / signature | Meaning |
|---|---|---|
disabled | boolean | Delegates disabled state to native children. |
name | string | Native control name; application form keys remain explicit. |
selectedIds | string[] | Stable selected row IDs. |
methods
| Name | Type / signature | Meaning |
|---|---|---|
refresh | (): void | Reconnect behavior after replacing native child structure. |
focus | (options?: FocusOptions): void | Focus the first usable native control. |
sort | (column: number, direction?: 'ascending' | 'descending'): void | Sort existing native rows by a column. |
filter | (query: string): void | Filter existing rows by literal text. |
events
| Name | Type / signature | Meaning |
|---|---|---|
j3w1-sort | CustomEvent | The user requested sorting. |
j3w1-selection | CustomEvent | Selected row identities changed. |
Dependencies and verification
Registered components: menu. Style dependencies: menu.
Package presence establishes an available implementation. Actual passes depend on the tested artifact and environment. Read specification execution evidence; read packed-consumer evidence; inspect the complete state matrix.
Full implementation manifest, API and token dependency closure
Canonical specification
Purpose
A table the user works in: selects rows, sorts, resizes columns. The plain table is for reading; this component adds the controls and the keyboard model. It stays a native table so its cells keep their header associations; hosts that need cell-level navigation promote it to a grid and record the change.
Anatomy
The table component’s structure with the same rule, dividers and fills,
plus: a selection column whose header holds a select-all with
aria-checked true, false or mixed, drawn on color.action.primary.bg with
the ✓ or – glyph in color.action.primary.text; native checkboxes in each
row on color.surface.input with a 1px color.border.control boundary;
rows that can take focus; and in the resizable variant a 1px handle in
color.border.default at the inline end of each header that turns
color.border.active under the pointer.
States
| State | Visual | Non-colour channel |
|---|---|---|
| default | rows on color.surface.default; header rule 1px color.border.strong | — |
| hover | row background → color.interaction.hover.bg; a resize handle → color.border.active | cursor: default on rows, col-resize on handles |
| focus-visible | ring 1px dashed color.interaction.focus.ring at −2px around the focused row or control | the ring |
| selected | row fill color.interaction.selection.bg; every cell color.interaction.selection.text; the row checkbox checked | aria-selected="true"; the checked box |
| selected+focus-visible | the fill and the ring in color.interaction.focus.ring-container | both visible at once |
| sorted | ▲ or ▼ in color.text.bright on the sorted header | aria-sort; the glyph |
| mixed | the select-all on color.action.primary.bg with the – glyph | aria-checked="mixed"; the glyph |
| empty | one full-width cell in color.text.muted: “No rows match.”; the header and controls stay | the message text |
| loading | one full-width cell with the static ⋯ glyph; the header stays | aria-busy="true"; the glyph; no animation |
Precedence: loading > empty; selected > hover; focus-visible is always drawn. Selection is a fill; focus is a ring; they never borrow each other’s form.
Keyboard
Header controls first: the select-all, then each sort button. Rows use a roving tabindex so the body costs one tab stop; Up and Down move, Space toggles, Shift extends, Ctrl+A selects all. Controls inside the focused row are reached with Tab and left with Shift+Tab. Column widths are set from a header menu or by dragging the handle; the handle itself is never a tab stop.
Accessibility
Every row checkbox is labelled by the row’s first data cell
(aria-labelledby); the select-all is named “Select all rows” and exposes
aria-checked="mixed" when only some are selected. aria-selected on the
row mirrors its checkbox. Sorted headers carry aria-sort; the table
carries aria-busy while loading; the count of selected rows is announced
through a live region owned by the surrounding toolbar. Contrast: text
8.43:1, header 10.10:1, selected text 12.47:1 on the fill, ring 4.57:1 on the
surface and 7.48:1 on the fill, checkbox boundary 4.33:1, check glyph 9.28:1
on the primary fill. Checkboxes and handles are at least 24 CSS pixels tall
in compact density.
Portability
The table component’s drawing plus a checkbox column and a header checkbox; the handle is a 1px rule. Toolkits with native table views map row selection to their selection model and the mixed state to their tristate checkbox. Hosts without row focus fall back to the checkbox as the only row control and record it.
Non-examples
A grid role on a table that is not navigated cell by cell. Zebra stripes.
A select-all that is a native checkbox with a script-only indeterminate
state and no aria-checked. Rows selected by clicking anywhere with no
checkbox alternative. A selected row shown by a border alone. A resize
handle that is a tab stop. An animated loading skeleton. Rounded checkboxes.
Text truncated with no full-value affordance.