Sortable data table — derived() sorts a copy of the prop rows, each/key renders rows, click headers to toggle direction.
propstatederivedactioneachkeyon:clickinterpolationThis recipe doesn't have a hydrated island in the demo gallery yet. An in-browser, WASM-compiled playground (edit this source and re-render live) is planned as a follow-up — read the real source below.
// data-table — prop rows + action sort(col), derived sortedRows, each with key
@state {
const rows = prop({
default: [
{ id: '1', name: 'Alice', role: 'Engineer', score: 92 },
{ id: '2', name: 'Bob', role: 'Designer', score: 78 },
{ id: '3', name: 'Carol', role: 'Manager', score: 85 },
{ id: '4', name: 'Dave', role: 'Engineer', score: 91 },
],
})
let sortCol = state('name')
let sortDir = state(1)
const sortedRows = derived(() => [...rows].sort((a, b) => {
const av = String(a[sortCol] ?? '')
const bv = String(b[sortCol] ?? '')
return av.localeCompare(bv) * sortDir
}))
const sort = action((col: string) => {
if (sortCol === col) {
sortDir = sortDir === 1 ? -1 : 1
} else {
sortCol = col
sortDir = 1
}
})
}
@template {
<div class="data-table-wrapper">
<table class="data-table">
<thead>
<tr>
<th on:click={() => sort('name')} class={['col-hdr', sortCol === 'name' && 'sorted']}>Name {sortCol === 'name' ? (sortDir === 1 ? '▲' : '▼') : ''}</th>
<th on:click={() => sort('role')} class={['col-hdr', sortCol === 'role' && 'sorted']}>Role {sortCol === 'role' ? (sortDir === 1 ? '▲' : '▼') : ''}</th>
<th on:click={() => sort('score')} class={['col-hdr', sortCol === 'score' && 'sorted']}>Score {sortCol === 'score' ? (sortDir === 1 ? '▲' : '▼') : ''}</th>
</tr>
</thead>
<tbody>
<tr each={row of sortedRows} key={row.id} class="data-row">
<td>{row.name}</td>
<td>{row.role}</td>
<td class="score">{row.score}</td>
</tr>
</tbody>
</table>
</div>
}
@style {
.data-table-wrapper { overflow-x: auto; max-width: 100%; }
.data-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.col-hdr { padding: 0.5rem 0.75rem; text-align: left; background: var(--table-hdr, #f5f5f5); border-bottom: 2px solid var(--border, #ccc); cursor: pointer; user-select: none; white-space: nowrap; }
.col-hdr.sorted { color: var(--accent, #0066cc); }
.data-row td { padding: 0.4rem 0.75rem; border-bottom: 1px solid var(--border, #eee); }
.data-row:hover td { background: var(--hover-bg, #fafafa); }
.score { font-variant-numeric: tabular-nums; font-weight: 600; }
}state