Via the amazing capabilities braught to us by Svelte 5 — a performant, dynamic, flexible, feature rich table. It's as simple, or as flexible as you need it to be.
Simple example on Svelte 5 Playground
Fledged out example on Svelte 5 Playground
On top of that, the library API is extensive, so the table can meet your needs.
bun add -D svelte-tably
[!NOTE]
If you do SSR, set Node version to 20 or higher
<script lang='ts'>
    import Table from 'svelte-tably'
    const data = $state([
        { name: 'Giraffe', age: 26, email: '[email protected]' },
        { name: 'Shiboo', age: 21, email: '[email protected]' }
    ])
    let activePanel = $state('columns') as string | undefined
    let selected = $state([]) as typeof data
</script>
<!-- Auto: Generate Columns based on data properties -->
<Table auto {data} resizeable={false} filters={[...]} />
<Table {data} panel={activePanel} select bind:selected>
    {#snippet content({ Column, Panel, Expandable, Row, state, table })}
        <Column id='name' sticky sort value={r => r.name} filter={v => v.includes('Giraffe')}>
            {#snippet header(ctx)}
                Name
            {/snippet}
            {#snippet row(row, ctx)}
                {row.name}
            {/snippet}
            {#snippet statusbar(ctx)}
                {table.data.length}
            {/snippet}
        </Column>
        
        <!-- Simplified -->
        <Column id='age' header='Age' value={r => r.age} sort={(a,b) => a - b} />
        <Expandable click={false}>
            {#snippet content(item, ctx)}
                ...
            {/snippet}
        </Expandable>
        <Row onclick={...} oncontextmenu={...}>
            {#snippet contextHeader()}
                <button ...> <Icon icon='add' /> </button>
            {/snippet}
            {#snippet context(item, ctx)}
                <button ...> <Icon icon='menu' /> </button>
            {/snippet}
        </Row>
        <Panel id='columns'>
            <!-- Anything you might like -->
        </Panel>
        <Panel ... backdrop={false}>
            ...
        </Panel>
    {/snippet}
</Table>
For quick styling
| CSS Variable | Description | Default | 
|---|---|---|
| --tably-bg | Background color | hsl(0, 0%, 100%) | 
| --tably-color | Text color | hsl(0, 0%, 0%) | 
| --tably-border | Border for sticky columns and header | hsl(0, 0%, 90%) | 
| --tably-border-grid | Border for the table-grid | hsl(0, 0%, 98%) | 
| --tably-statusbar | background-color for the statusbar | hsl(0, 0%, 98%) | 
| --tably-padding-y | Padding above/below each column | .5rem | 
| --tably-padding-x | Padding left of each column | 1rem | 
| --tably-radius | Table radius | .25rem | 
[!TIP]
For the CSS variables, apply them to:global(:root) { ... }
[!NOTE]
Advanced styling can be done via:global(.svelte-tably)table > thead > tr > th, table > tbody > tr > td, table > tfoot > tr > td
All components except Table are meant to be children of the Table component.
However, you can safely create a Component.svelte and use these components,
and then provide <Component/> as a child to <Table>. 
import Table from 'svelte-tably'
The table component.
<Table auto {data} />
<Table {data} ...>
    {#snippet content?({ Column, Row, Expandable, Panel, table })}
        ...
    {/snippet}
</Table>
Where table is TableState<T> and the rest are typed; Component<T>.
| Attribute | Description | Type | 
|---|---|---|
| content? | The contents of the table | Snippet<[ctx: ContentCtx<T>]>? | 
| id? | The #idfor the table | string | 
| data | An array of objects for the table | T[] | 
| bind:selected? | The currently selected items | T[] | 
| bind:panel? | The currently open panel | string | 
| filters? | An array of filters applied to the table | ((item: T) => boolean)[] | 
| reorderable? | Whether the rows can be re-ordered (via runic-reorder) | boolean | 
| resizeable? | Whether or not the columns can be resized | boolean | 
| select? | Whether ot not the rows items can be selected | boolean | SelectOptions<T> | 
| auto? | Create missing columns automatically? | boolean | 
| Properties | Description | Type | 
|---|---|---|
| show? | When to show the row-select when not selected | 'hover' | 'always' | 'never' | 
| headerSnippet? | Custom snippet for the header select-input | Snippet<[context: HeaderSelectCtx]> | 
| rowSnippet? | Custom snippet for the row select-input | Snippet<[context: RowSelectCtx<T>]> | 
import { Column } from 'svelte-tably'
This component designates a column where options like sorting, filtering etc. are provided.
<Column id='...' header='...' value={row => row.value} />
<Column id='...' ...>
    {#snippet header?(ctx: HeaderCtx<T>)}
        ...
    {/snippet}
    {#snippet row?(item: T, ctx: RowColumnCtx<T>)}
        ...
    {/snippet}
    {#snippet statusbar?(ctx: StatusbarCtx<T>)}
        ...
    {/snippet}
</Column>
| Attribute | Description | Type | 
|---|---|---|
| header? | The header element/contents | string | Snippet<[ctx: HeaderCtx<T>]> | 
| row? | The row element. If not provided, value: Vwill be used. | Snippet<[item: T, ctx: RowColumnCtx<T, V>]> | 
| statusbar? | The statusbar element | Snippet<[ctx: StatusbarCtx<T>]> | 
| sticky? | Should be sticky by default | boolean | 
| show? | Should be visible by default | boolean | 
| sortby? | Should sort by this by default | boolean | 
| width? | Default width | number | 
| value? | The value this column contains | (item: T) => V | 
| sort? | A boolean ( localeCompare) or sorting function | boolean | ((a: V, b: V) => number) | 
| resizeable? | Whether this column is resizeable | boolean | 
| filter? | A filter for this columns value | (item: V) => boolean | 
| style? | Styling the td(row-column) element | string | 
| pad? | Apply padding to the child-element of td/thinstead of the column element itself | 'row' | 'header' | 'both' | 
| onclick? | When the column is clicked | (event: MouseEvent, ctx: RowColumnCtx<T, V>) => void | 
import { Row } from 'svelte-tably'
This component can add a context-menu on the side of each row, as well as provide event handlers to the row element.
<Row ... />
<Row ...>
    {#snippet context?(item: T, ctx: RowCtx<T>)}
        ...
    {/snippet}
    {#snippet contextHeader?()}
        ...
    {/snippet}
</Row>
| Attribute | Description | Type | 
|---|---|---|
| context? | A sticky column on the right for each row | Snippet<[item: T, ctx: RowCtx<T>]> | 
| contextHeader? | A sticky column on the right for the header | Snippet<[item: T, ctx: RowCtx<T>]> | 
| contextOptions? | Options for the Context-column | ContextOptions<T> | 
| onclick? | When row is clicked | (event: MouseEvent, ctx: RowCtx<T>) => void | 
| oncontextmenu? | When row is right-clicked | (event: MouseEvent, ctx: RowCtx<T>) => void | 
| Properties | Description | Type | 
|---|---|---|
| hover? | Only show when hovering? | boolean | 
| width? | The width for the context-column | string | 
import { Expandable } from 'svelte-tably'
This component gives your rows the ability to be expanded.
<Expandable ...>
    {#snippet content(item: T, ctx: RowCtx<T>)}
        ...
    {/snippet}
</Expandable>
| Attribute | Description | Type | 
|---|---|---|
| content | The contents of the expanded row. | Snippet<[item: T, ctx: RowCtx<T>]> | 
| slide? | Options for sliding the expanding part | { duration?: number, easing?: EasingFunction } | 
| click? | Whether you can click on a row to expand/collapse it | boolean | 
| chevron? | Whether to show the chevron on the left fixed column | 'always' | 'hover' | 'never' | 
| multiple? | Can multiple rows be open at the same time? | boolean | 
import { Panel } from 'svelte-tably'
This component creates a panel that can be opened on the side of the table.
<Panel id='...' ...>
    {#snippet children(ctx: PanelCtx<T>)}
        ...
    {/snippet}
</Panel>
| Attribute | Description | Type | 
|---|---|---|
| children | The contents of the panel | Snippet<[ctx: PanelCtx<T>]> | 
| id | The id for the panel that determines whether it's open or closed, from the Table attribute | string | 
| backdrop? | Whether there should be a backdrop or not | boolean |