J3W1 / DESIGN SYSTEM
Filterable table
A list screen composed from a toolbar with search and filter, removable chips, a selectable sortable data-table with status badges, a count and pagination; with empty and loading variants.
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-filterable-table id="example-filterable-table-default"><section class="filterable-table" aria-label="Project records"><div class="filterable-table-toolbar"><j3w1-search-field><div class="search-field"><label class="search-field-label" for="example-filterable-table-default-records-search">Search projects</label><div class="search-field-root"><input class="search-field-input" id="example-filterable-table-default-records-search" type="search"><button class="search-field-clear" type="button" aria-label="Clear search">Clear</button></div></div></j3w1-search-field><label for="example-filterable-table-default-records-status">Status<select class="select-control" id="example-filterable-table-default-records-status" data-status-filter="" name="example-filterable-table-default-records-status"><option value="all">All statuses</option><option>Ready</option><option>In progress</option><option>Review</option></select></label></div><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-filterable-table-default-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-filterable-table-default-record-name-0" name="example-filterable-table-default-value"></td><td class="data-table-cell" id="example-filterable-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-filterable-table-default-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-filterable-table-default-record-name-1" name="example-filterable-table-default-value"></td><td class="data-table-cell" id="example-filterable-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-filterable-table-default-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-filterable-table-default-record-name-2" name="example-filterable-table-default-value"></td><td class="data-table-cell" id="example-filterable-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-filterable-table-default-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-filterable-table-default-record-name-3" name="example-filterable-table-default-value"></td><td class="data-table-cell" id="example-filterable-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="5">No rows match.</td></tr><tr class="data-table-loading" hidden=""><td colspan="5">⋯ Loading rows</td></tr></tbody></table></div><p class="filterable-table-count" role="status" data-table-status=""></p></section></j3w1-filterable-table>empty
| No rows match. | ||||
| ⋯ Loading rows | ||||
<j3w1-filterable-table id="example-filterable-table-empty"><section class="filterable-table" aria-label="Project records"><div class="filterable-table-toolbar"><j3w1-search-field><div class="search-field"><label class="search-field-label" for="example-filterable-table-empty-records-search">Search projects</label><div class="search-field-root"><input class="search-field-input" id="example-filterable-table-empty-records-search" type="search"><button class="search-field-clear" type="button" aria-label="Clear search">Clear</button></div></div></j3w1-search-field><label for="example-filterable-table-empty-records-status">Status<select class="select-control" id="example-filterable-table-empty-records-status" data-status-filter="" name="example-filterable-table-empty-records-status"><option value="all">All statuses</option><option>Ready</option><option>In progress</option><option>Review</option></select></label></div><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-empty"><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><p class="filterable-table-count" role="status" data-table-status=""></p></section></j3w1-filterable-table>loading
| 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-filterable-table id="example-filterable-table-loading"><section class="filterable-table" aria-label="Project records"><div class="filterable-table-toolbar"><j3w1-search-field><div class="search-field"><label class="search-field-label" for="example-filterable-table-loading-records-search">Search projects</label><div class="search-field-root"><input class="search-field-input" id="example-filterable-table-loading-records-search" type="search"><button class="search-field-clear" type="button" aria-label="Clear search">Clear</button></div></div></j3w1-search-field><label for="example-filterable-table-loading-records-status">Status<select class="select-control" id="example-filterable-table-loading-records-status" data-status-filter="" name="example-filterable-table-loading-records-status"><option value="all">All statuses</option><option>Ready</option><option>In progress</option><option>Review</option></select></label></div><div class="table-scroller" tabindex="0" role="region" aria-label="Project records"><table class="data-table" aria-busy="true"><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-filterable-table-loading-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-filterable-table-loading-record-name-0" name="example-filterable-table-loading-value"></td><td class="data-table-cell" id="example-filterable-table-loading-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-filterable-table-loading-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-filterable-table-loading-record-name-1" name="example-filterable-table-loading-value"></td><td class="data-table-cell" id="example-filterable-table-loading-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-filterable-table-loading-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-filterable-table-loading-record-name-2" name="example-filterable-table-loading-value"></td><td class="data-table-cell" id="example-filterable-table-loading-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-filterable-table-loading-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-filterable-table-loading-record-name-3" name="example-filterable-table-loading-value"></td><td class="data-table-cell" id="example-filterable-table-loading-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"><td colspan="5">⋯ Loading rows</td></tr></tbody></table></div><p class="filterable-table-count" role="status" data-table-status=""></p></section></j3w1-filterable-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/filterable-table.css';
import '@j3w1/ui/register/filterable-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 filterable-table --out ./vendor/j3w1/filterable-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: search-field. Style dependencies: data-table, menu, select, table, search-field.
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 list screen that proves the navigation, display and feedback components compose around one data-table: find, filter, select, page. It exists to show the states of a whole list screen, including nothing matching and still loading, not to prescribe a product.
Anatomy
The region landmark; the toolbar on color.surface.chrome with the
search-field, the Filter menu button, the Columns toggle, a separator and the
primary New button; a row of removable chips (priority: R1, maturity:
stable); the scroller around the data-table, whose Name header carries
aria-sort, whose rows carry aria-selected and a checkbox, and whose last
column is a status badge (✓ Tested on color.status.success.fill, i
Demonstrated on color.status.info.fill); the count in color.text.muted
and the compact pagination. The empty variant adds the empty-state block;
the loading variant marks the table aria-busy. Every part is the
component’s own markup and classes.
States
| State | Visual | Non-colour channel |
|---|---|---|
| default | rest tokens of every component; Name sorted ascending with its ▲ glyph; Prev disabled | aria-sort; the glyph |
| hover | the pointed row takes color.interaction.hover.bg; the toolbar controls and chips keep their own hover on their own pointer events only | cursor |
| focus-visible | the dashed color.interaction.focus.ring on the row holding focus (shown on the third row); the toolbar and chips stay at rest | the ring |
| selected | two rows take color.interaction.selection.bg with color.interaction.selection.text and a checked box; the select-all reports mixed with the – glyph | aria-selected; ✓ and – glyphs; aria-checked="mixed" |
| no-results | the body is the “No components match” row, the count reads 0 of 43, the search field’s status line announces it; the chips stay so the cause is visible | text; role="status" |
The composed components keep their own pressed, sorted, disabled, loading and open treatments; this specimen declares only the states the whole screen adds to them.
Keyboard
Landmarks first: the region, the toolbar’s search landmark and the pagination navigation are offered by name. The toolbar is one stop with arrow movement between the search field, Filter, Columns and New (the toolbar contract); Escape clears the search. Each chip’s Remove button is its own stop. The table follows the data-table contract: the first row is the stop, arrows move between rows, Space on a row’s checkbox toggles it, Enter on a header sorts. Next is the last stop; a disabled Prev is not focusable.
Accessibility
The search field’s visible label is replaced by a visually hidden one so the
toolbar stays one row; its status line is visually hidden too and announces
“No components match” through role="status". The count is a second status
region and changes once per filter. Rows are named by their Name cell
through aria-labelledby on the row checkbox; the select-all is a
checkbox-role button whose mixed state carries the – glyph. Badges carry
their glyphs so status survives without colour. Contrast: toolbar text
8.60:1 on the chrome surface, boundaries 4.42:1, headers 10.10:1, badges
7.08:1 and 7.13:1 on their fills, the ring 7.48:1 on the selection fill.
Portability
Native table semantics, a toolbar, a menu button and a status region; the
roving tabindex and the menu need the host’s script, every state renders
without one from the root hooks data-state-selected and
data-state-no-results and the table’s own aria-busy. Hosts without
container queries wrap the toolbar at the 600px viewport breakpoint. The
56rem width and the 44rem table minimum are layout of this specimen and not
rules; the specimen proves composition, not a product.
Non-examples
A rounded search pill with a magnifier inside a shadowed toolbar. Filter chips drawn as pills with a coloured fill per category. Zebra-striped rows. A selected row shown by a coloured left bar and no fill. A blue Material checkbox. A skeleton shimmer while loading. An infinite scroll with no count and no pages.