Data · Table

Table

Headless tabular data with sorting, filtering, selection, pagination, column pinning, sizing and row-detail disclosure panels — wrapped by an eidos visual recipe (variant / size / color / striped / sticky header). Use Table for flat data; reach for TreeGrid when rows are hierarchical, or GridList when rows carry inline interactive children.

parts{compiled.parts.order.length} events{events.length} sizes5 apg table ↗ rows {table.filteredRowCount} / {PEOPLE.length} selected {selectedCount} locale {locale} dir {dir}
setGlobalFilter((e.currentTarget as HTMLInputElement).value)} style="padding: 0.375rem 0.625rem; border: 1px solid var(--color-border-default); border-radius: var(--radius-md); inline-size: 18rem; background: var(--color-surface-default); color: var(--color-content-primary); font: inherit;" />
{#each table.headers as header (header.id)} {@const h = header as unknown as TableHeader} {header.label} {/each} {#each table.rows as row (row.id)} {@const r = row as unknown as TableRow} {#each row.cells as cell, i (cell.id)} {#if cell.column.id === 'status'} {fmtStatus(String(cell.value))} {:else if cell.column.id === 'score'} {cell.value} {:else if cell.column.id === 'joined'} {fmtDate(String(cell.value))} {:else} {cell.value} {/if} {/each}
{row.original.name}
{row.original.bio}
id{row.original.id} team{row.original.team} joined{fmtDate(row.original.joined)}
{/each}
Page {pageIndex + 1} of {pageCount} · showing {table.rows.length} of {table.filteredRowCount} table.setPageIndex(p - 1)} > {#snippet children({ pages })} « Prev {#each pages as item, i (i)} {#if item.type === 'page'} {item.value} {:else} … {/if} {/each} Next » {/snippet}
trace {#if trace.length === 0} click sort, select a row, expand a detail panel… {:else} {#each trace.slice(0, 3) as entry} {entry.event} · {entry.family}{entry.intent ? ' · ' + entry.intent : ''} {fmtTime(entry.at)} {/each} {/if} sort {table.sorting.length === 0 ? '—' : table.sorting.map((s) => `${s.id}:${s.desc ? '↓' : '↑'}`).join(' · ')}
{#if tab === 'live'}

Controls

Soma owns sort / filter / select / paginate / pin / row-detail state and ARIA wiring. Eidos adds visual chrome (variant / size / color), striping, hover feedback and the optional sticky header + scroll region. `dir` and `locale` are demo-driven for verification — Table forwards `dir` to the soma Provider and `locale` is used by the demo for date formatting.

soma i18n
soma feature controls
eidos visual props
eidos visual · createTable + Table compound svelte
{eidosSnippet}
{/if} {#if tab === 'api'}

API reference

9 soma parts (Provider · Header · Body · Footer · ColumnHeader · Row · Cell · RowDetail · RowDetailTrigger) plus the eidos-only SortButton ergonomic helper. The table instance carries every imperative method (toggleSort, setGlobalFilter, toggleRowSelection, pinColumn, setPageIndex, toggleRowDetail…).

Provider (Table)
PropTypeDefaultDescription
tableTableInstancerequiredReturned by createTable.
labelstring'Data table'Accessible name. Translated.
dir soma'ltr' | 'rtl'from Soma prefsText direction.
variant eidos'surface' | 'outline' | 'ghost''surface'Container chrome.
size eidos'xs' | 'sm' | 'md' | 'lg' | 'xl''md'Row height + cell padding + font.
color eidosColorRole'primary'Accent for selected rows + sort indicator + focus.
block eidosbooleantrueStretch root to container inline-size.
striped eidosbooleanfalseAlternate-row tint (skipped in ghost variant).
hoverable eidosbooleantrueHighlight rows on hover.
stickyHeader eidosbooleanfalsePins <thead> to the top of the scroll region.
maxHeight eidosstring—Wraps the table in a scrolling region with the given CSS block-size.
stableRows eidosnumber—Reserve vertical space for N rows + header. Keeps the wrapper height steady so pagination/footer don't jump when filters change row count. Ignored when maxHeight is set.
SortButton (eidos helper)
PropTypeDefaultDescription
headerTableHeaderrequiredFrom table.headers.
childrenSnippetheader.labelVisible label.
hideIndicatorbooleanfalseHide the tri-state chevron.
RowDetailTrigger
PropTypeDefaultDescription
rowTableRowrequiredFrom table.rows.
aria-labelstring'Show details' / 'Hide details'Translated swap.
variant · size · rounded · intent · color · iconOnly · iconButton propsghost · md · mdForwarded to underlying <Button>.
ColumnDef opt-in flags
FlagDefaultEffect
enableSortingfalseColumn shows up in getCanSort + aria-sort.
enableFilteringfalseColumn participates in global filter + per-column filters.
enableHidingtrueConsumer can call toggleColumnVisibility.
enableResizingfalseConsumer can call setColumnSize with constraints.
enablePinningfalseConsumer can call pinColumn(id, 'left' | 'right').
size · minSize · maxSize—Initial width in px + bounds.
sortDescFirstfalseFirst click sorts descending (numeric columns).
{/if} {#if tab === 'morfo'}

morfo · declarative contract

Source: src/uix/morfo/components/table.ts.

FieldValue
name"{tableMorfo.name}"
kebab"{tableMorfo.kebab}"
parts.length{tableMorfo.parts.length}
events.length{events.length}
Parts
{#each partsList as part (part.kebab)} {/each}
PartMarkerElementRoleArchetypeStatesOptional
{part.kebab} [{part.marker}] <{part.defaultElement}> {part.role ?? '—'} {part.archetype ?? '—'} {part.states.length ? part.states.join(' | ') : '—'} {part.optional ? 'yes' : 'no'}
Events declaration
{#each events as action (action.name)} {@const sem = action.semantic} {@const intentDecl = 'intent' in sem ? sem.intent : undefined} {@const intentStr = typeof intentDecl === 'string' ? intentDecl : intentDecl ? `fromProp:${(intentDecl as { fromProp?: string }).fromProp ?? '?'}` : '—'} {/each}
namefamilyverbsequenceintenttarget
{action.name} {sem.family} {sem.verb ?? '—'} {sem.sequence ?? 'pre'} {intentStr} {action.target}
{/if} {#if tab === 'sema'}

sema · events

Soma raises shift-sort on column-header click, commit-select on row toggle and shift-expand-row when the detail panel toggles. Use Play to replay each event against the first match in the stage.

{#each events as action (action.name)} {@const intentDecl = 'intent' in action.semantic ? action.semantic.intent : undefined} {@const effectiveIntent = typeof intentDecl === 'string' ? intentDecl : undefined} {/each}
NameFamilyVerbSequenceIntentPlay
{action.name} {action.semantic.family} {action.semantic.verb ?? '—'} {action.semantic.sequence ?? 'pre'} {effectiveIntent ?? '—'}
{/if} {#if tab === 'recipe'}

Eidos recipe

Selectors at src/uix/eidos/components/table/table.css. The visual data-attrs (data-size, data-variant, data-color, data-block, data-striped, data-hoverable, data-sticky-header, data-scrollable) live on the <div data-table-root> wrapper so the semantic <table> stays clean.

SelectorSourceWhat it paints
[data-table-root]eidosWrapper — radius, chrome, scroll cap.
[data-table-root][data-variant]eidosBorder + background per variant.
[data-table-root][data-size]eidosRow block-size, cell padding, font-size scale.
[data-table-root][data-color]eidosAccent token cascade.
[data-table-root][data-striped]eidosAlternate-row tint.
[data-table-root][data-hoverable]eidosRow hover highlight.
[data-table-root][data-sticky-header] [data-table-column-header]eidosSticky header inside scroll region.
[data-table-column-header][data-sortable]somaCursor + sort indicator hook.
[data-table-column-header][data-sorted]somaActive sort accent colour.
[data-table-row][data-selected]somaSelected-row accent-soft fill.
[data-table-cell][data-pinned]somaPinned-cell sticky offset + background.
[data-table-row-detail][data-state]somaDisclosure panel show/hide.
[data-table-sort-button]eidosHeader sort affordance — bare button + chevron.
{/if} {#if tab === 'a11y'}

Accessibility

Emits a native <table> with aria-label and per-column aria-sort. Rows expose aria-rowindex + aria-selected (when selection is enabled). Detail panels are wired with aria-expanded + aria-controls. Use TreeGrid for true hierarchical rows (arrow-key navigation) — Table is for flat tabular data only.

Keyboard
KeyAction
TabMove focus through sort buttons, selectable rows, detail triggers, pagination.
Enter / Space on rowToggle selection (when selectionMode ≠ 'none').
Enter / Space on RowDetailTriggerOpen / close the row's detail panel.
Click on sort buttonCycle asc → desc → none.
Shift+Click on sort buttonAdd column to multi-sort (when sorting.multi: true).
ARIA contract
PartAttributeValue
Provideraria-labeltranslated 'Data table'
ColumnHeaderrole"columnheader"
ColumnHeaderaria-sort"ascending" | "descending" | "none"
Rowrole / aria-rowindex"row" / 1-based
Rowaria-selected"true" | "false" (selection enabled)
Cellrole / aria-colindex"gridcell" / 1-based
RowDetailTriggeraria-expanded / aria-controlsboolean / id of sibling RowDetail
{/if}