npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

svelte-tably

v1.6.1

Published

A high performant dynamic table for Svelte 5

Readme

Svelte Tably

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

  • [x] Columns
    • [x] Sticky
    • [x] Show/hide
    • [x] Re-order
    • [x] Resize
  • [x] Data manipulation
    • [x] "Virtual" data
    • [x] Sorting
    • [x] Select
    • [x] Filtering
    • [x] Reorderable
  • [x] Statusbar
  • [x] Panels
  • [x] Row context
  • [x] Expandable rows
  • [x] Virtual rendering
  • [x] To CSV
  • [x] Auto: Create columns based on data

On top of that, the library API is extensive, so the table can meet your needs.

Usage Notes

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, 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>

[!NOTE] Animations (panel/expandable) respect prefers-reduced-motion.

[!TIP] To export CSV, you can either bind:table to get the TableState instance (which exposes table.toCSV(...)), or bind:this and call the component export toCSV(...).

Styling

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

Components

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'

Table

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 #id for 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 |

SelectOptions

| 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>]> |

Column

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: V will 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/th instead of the column element itself | 'row' \| 'header' \| 'both' | | onclick? | When the column is clicked | (event: MouseEvent, ctx: RowColumnCtx<T, V>) => void |

Row

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 |

ContextOptions

| Properties | Description | Type | | - | - | - | | hover? | Only show when hovering? | boolean | | width? | The width for the context-column | string | | alignHeaderToRows? | If enabled, the header and row context cells share the same measured width | boolean |

Expandable

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 |

Panel

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 |