Svelte Table
For the Pagination and Td components, See Pagination.md and Td.md.

Install
npm i @isoftdata/svelte-table
A Note on Types
This component uses a generic type R to represent the type of a single row passed to the rows prop of this table. Then, R is extended by another type, IndexedRow, which includes the uuid and originalIndex properties. The original rows array you pass in will not be mutated, but the contents of filteredRows, sortedRows, and currentPageRows will be of type IndexedRow.
Props
| bordered | boolean | Whether the table is bordered or not. | true |
| class | string | Any extra classes to apply to the table. | "" |
| columnClickedMethod | (column: Column, direction: SortDirection) => void | Called when a column is clicked. Should update previousSortOrder, previousSortDirection, previousSortColumn, and sortedRows. | defaultColumnClicked |
| columnHidingEnabled | boolean | When enabled, the user will be able to right click a column header and hide/show it. | false |
| columnResizingEnabled | boolean | When enabled, the user will be able to drag on the edge of a column header and resize it. | false |
| columns | Array<Column> | The definition of the columns. See columns docs below. | Required |
| currentPageNumber | number | The current page number. | 1 |
| currentPageRows | Array<IndexedRow> | It's the current page of rows to show, computed based on sortedRows, currentPageNumber, and perPageCount. The table component will set this. | sortedRows.slice(0, perPageCount) |
| filter | string | The filter string. | "" |
| filterColumnClass | string | The class for the filter column in the header row. | "col-lg-2 col-md-4 col-sm-6 align-self-end" |
| filterDisabled | boolean | Whether the filter input is disabled or not. | false |
| filterEnabled | boolean | Whether to show the filter input above the table. | false |
| filterLabel | string | The label for the filter input. | "Filter" |
| filterMethod | (filter: string, rows: Array<R>, columns: Array<Column>) => Array<IndexedRow> | A function that filters the rows based on the given filter string. Should return the rows that match the filter, with uuid (if not already specified in rows) and originalIndex properties added. | defaultFilter |
| filterPlaceholder | string | The placeholder text for the filter input. | "Filter" |
| filterProps | Array<string> | false | An array of strings that represent the properties to filter on. If false, uses all column properties. | false |
| filterReadonly | boolean | Whether the filter input is readonly or not. | false |
| filteredRows | Array<IndexedRow> | An array of objects that represent the filtered table rows. You probably don't need to interact with this. | filterMethod("", rows, columns) |
| footers | Array<FooterValue> | Values to display in the footer. Calculated automatically by the Table component. | [] |
| rowMatchesFilterMethod | (filter: string, row: R, props: Array<RowProperties<R>>) => boolean | A function that determines whether the a given row matches the filter. Useful if you want to change how rows are matched by the filter without overriding the whole filterMethod. See Custom Filter Logic for more details | |
| headerColumnClass | string | When the filter is enabled, a header slot will be inserted to the area to the left of the filter input. This is the column class for that yield's parent div. | "col" |
| headerRowClass | string | The class for the table header row. | "form-row mr-auto" |
| hideButtonClass | string | Any other classes to add the the "hide" dropdown button. | "" |
| hover | boolean | Whether the table will highlight the hovered row. | true |
| idProp | K | "uuid" | Property on the objects in the rows Array that uniquely identifies the row. Used for lazy sorting. | "uuid" |
| lastClickedIdForRange | number | IndexedRow[keyof R | "uuid"] | undefined | The last clicked ID for the range selection mode. | undefined |
| lazySort | boolean | When true, the table will only be resorted when the user clicks on a column header (not in real time as data changes). See idProp. | false |
| multiSelectEnabled | boolean | Whether to allow selecting multiple rows at a time | true |
| localStorageKey | string | undefined | If specified, the localstorage key where user-modified column info such as visibility and resizing are saved | undefined |
| parentClass | string | CSS class(es) to apply to the <table>'s parent div. | "" |
| parentStyle | string | CSS style(s) to apply to the <table>'s parent div. | "" |
| perPageCount | number | The number of rows to display per page. Enables pagination if > 0 | 0 |
| responsive | boolean | Whether the table is responsive or not. | false |
| rows | Array<R> | An array of objects that represent the table rows. | Required |
| rowSelectionIdProp | K | "uuid" | The property to use as the unique identifier for each row in the row selection feature. | "uuid" |
| rowSelectionRequiresModKey | boolean | Whether the row selection feature requires a modifier key or not. | false |
| selectionEnabled | boolean | Whether the row selection feature is enabled or not. | false |
| selectionMode | "SINGLE" | "RANGE" | null | The selection mode for the row selection feature. When allowMultiSelect is true, both selection modes will allow you to select multiple rows. | null |
| showFilterLabel | boolean | Whether the filter label is shown or not. | false |
| showFooter | boolean | Defines if a <tfoot> should be shown. See footers slot below. | false |
| size | "sm" | "" | The size of the table. | "sm" |
| sortedRows | Array<IndexedRow> | The rows, but sorted based on sortColumn and sortDirection, and optionally filtered(See filter options below). The table component will set this. | filteredRows |
| sortDirection | "ASC" | "DESC" | The direction of the current sort. | "ASC" |
| stickyHeader | boolean | Whether the table header will stay at the top when scrolling the table. | false |
| striped | boolean | Whether the table is striped or not. | true |
| tableId | string | The unique identifier of the table. | uuid() |
| totalItemsCount | number | The total number of items in the data set. Necessary for serverside pagination and sorting since the rows array won't have every item for every page. | 0 |
| tree | boolean | When true, enables a bunch of tree-specific features. Required if you want to use TreeRows. | false |
| columnResizingEnabled | boolean | Whether the user is allowed to resizing table columns | false |
When columnResizingEnabled is true, any <Td> component children will have a css rule of overflow: hidden; applied. This makes it a column sized so small that the text doesn't fit causes the text to truncate instead of clipping outside the bounds of the cell. This is intended behavior and how most table's with resizable columns work.
Advanced Props
You usually won't have to use these, but they're here just in case.
| previousSortColumn | Column | undefined | The previous sort column. | undefined |
| previousSortDirection | "ASC" | "DESC" | undefined | The previous sort direction. | undefined |
| previousSortOrder | Array<IndexedRow & { order: number }> | An array of objects that represent the previous sort order. | [] |
| selectedRowIds | Array<string | number | R[K]> | An array of strings, numbers, or objects that represent the selected row IDs. | [] |
| sortColumn | Column | undefined | The current sort column. | undefined |
| columnInfo | Writable<Record<string, Column & { visible: boolean }>> | A store containing column metadata. Mostly used for tracking column visibility. Read-only. | Handled by the component |
Columns Definition
An Array of Objects, where the Objects have the following properties:
| align | "left" | "right" | "center" | Direction to align columns (and Tds in the column) |
| class | string | Sets the class of the column header. |
| defaultSortColumn | boolean | When Svelte initalizes the view, this is the column that will be sorted by. |
| defaultSortDirection | "ASC" | "DESC" | 'ASC' or 'DESC' are the only valid options. Defaults to 'ASC' |
| ellipsis | boolean | Whether to apply text-overflow: ellipsis to the column |
| footer | See Below | Footer options for this column |
| formatter | (value: unknown) => unknown | A formatter function to use on this column. The return value will be shown in the td. |
| hideForPrint | boolean | Whether to hide this column when printing |
| icon | string | The FontAwesome icon classes for the icon you want to display, without the leading fa- |
| iconLeft | string | If you want the icon to display to the left of the name, set this to true. |
| iconPrefix | string | The FA prefix to apply. Defaults to fas. |
| minWidth | string | Will be passed in as the value for the CSS min-width property on the <th> style attribute. |
| name | string | The name of the column as it will be displayed in the table column header |
| numeric | boolean | When true, right aligns the column (and Tds in the column) and applies font-variant-numeric: tabular-nums; |
| property | string | The property name of the column. Must map to a property in the objects defined in rows(see above) |
| sortType | "ALPHA_NUM" | "STANDARD" | false | How to sort this column. Default is STANDARD. ALPHA_NUM is better for mixed number and letter columns. false will disallow sorting this column. |
| title | string | The th's title |
| unhidable | boolean | Whether to disallow hiding the column. |
| width | string | Will be passed in as the value for the CSS width property on the <th> style attribute. |
| wrap | boolean | If true, will allow column names to wrap. |
The Column type is generic. If you pass the type of one of your rows to it, then the property property will be strongly to properties of that object. This is optional for backwards compatability reasons.

Below are the properties of the footer object
| fn | "COUNT" | "AVG" | "SUM" | A function to call on each row when calculating that column's footer. |
| altProperty | string | If you want to compute footers off a different property than column.property, specify it here. |
| formatCurrency | boolean | If true, will format the column as currenty. |
| requiredValue | unknown | If specified, only column values matching this value will be included in calculations |
You can also pass a custom callback function to footer.fn, which changes some of the property types as shown below. The properties that are not repeated below behave the same whether you use a built-in or custom reducer fn.
| fn | (previousValue: FooterReducerValue<R>, currentValue: FooterReducerValue<R>) => FooterReducerValue<R> | A function to call on each row when calculating that column's footer. currentValue is the value of row[property] or row[altProperty] if specified. |
| initialValue | FooterReducerValue<R> | The initial value of the reducer function. |
FooterReducerValue is a union of string, number, and the property types of the row object
Slots
- default - This represents one row of the table.
- exposes the
row prop, of type R & { originalIndex: number; uuid: string
- exposes the
index prop, which is the current index of that row.
- header - Only available if
filterEnabled is true. This yields inside the first column of a <div class="form-row"></div> above the table. The 2nd column contains the filter input itself.
- footer - Rendered below the table and its parent div, but above the pagination control (if enabled)
- no-rows - Shown when
rows.length === 0
- body - If you want to specify all of the contents of the
tbody yourself, you can do so here. Specifying anything for this slot will prevent the "default" and "no-rows" slot from being rendered.
- exposes the
rows prop, of type Array<R & { originalIndex: number; uuid: string }>
- footer-row - If
showFooter is enabled, This is the tr where the footer values are shown.
- exposes the
footers prop, of type Array<FooterValue>.
Events
- pageChange - Fired when a page changes.
event.detail.pageNumber contains the page number. See below for a TS example.
function paginationHandler(event: ComponentEvents<Pagination<Row>>["pageChange"]) {
const pageNumber = event.detail.pageNumber
}
Methods
There are a couple methods you can access by calling them on the Table component instance, which you can access using bind:this.
rowClick(row: IndexedRow): void - handles the logic for clicking on and selecting rows
setPageVisibleByItemId({ id, keyName }: { id: number | string; keyName: K }) - Given an item's id, and a keyName, sets the current page to be the one containing that item
expandRow(rowId: (typeof selectedRowIds)[0], expanded = true) - Tree mode only. Expands a row given its id.
setColumnVisibility(columnProperties: Array<string> | string, visible: boolean) - Sets the visibility of the specified column(s)
Context
A handful of values are set as context of the component so they're accessible to their children.
- selectedRowIds - same as listed under Props
- columnInfo - Map of column properties to column definitions (
Record<string, Column & { visible: boolean }>)
Examples
There are three ways to use this component, with the difference lying in which slots are specified, which affects how table body is rendered. They are listed in order from "most automatic" to "most manual".
Method 1 : "Full Auto" (No Default or "body" slots)
For this method, all you have to do is specify the rows and columns props on the Table, and do not specify anything for the default slot. For each column in each row, the table component will render a td, getting the value from that column's property prop, applying any formatter function if specified for that column.
This is the simplest method, and will work for most use cases that don't need to insert any extra HTML into the tds.
<Table
{rows}
{columns}
/>
You can still specify other slots, for example, "no-rows" and "header", just not the default slot.
<Table
{rows}
{columns}
>
<tr slot="no-rows">
<td
class="text-center"
colspan={columns.length}>No rows here bro
</td>
</tr>
<Input
slot="header"
label="Items per page"
class="mb-3"
type="number"
bind:value={perPageCount}
/>
<Table>
Method 2 : "Semi Auto" (Default Slot)
For this method, you specify a single row's contents in the default slot, and access each row's value by adding let:row to the Table. These rows come from currentPageRows, so they've already been filtered, sorted, and paginated.
With this method, you can insert extra HTML into any of the td/Tds, but you have to specify them all yourself.
As above, you can still specify other slots when using this method.
<Table
{rows}
{columns}
let:row
>
<tr>
<Td property="id">
{row.id}
</Td>
<Td property="name">
{row.name}
</Td>
<Td property="email">
{row.email}
</Td>
<Td property="deleted">
<Button color="danger" icon="trash">Delete<Button>
</Td>
</tr>
</Table>
Method 3 : "Full Manual" ("body" Slot)
With this method, you put anything you want to render in the tbody into the "body" slot. You can access the currentPageRows with let:rows on the element where you specify slot="body".
This method is the most similar to how our Ractive table component works, and requires you to specify the whole contents of the tbody.
The contents of the "default" and "no-rows" slots will be ignored when using this method, but the "header" and "footer" slots will still work.
<Table
{rows}
{columns}
>
<svelte:fragment slot="body" let:rows>
{#each rows as row}
<tr>
<Td property="id">
{row.id}
</Td>
<Td property="name">
{row.name}
</Td>
<Td property="email">
{row.email}
</Td>
<Td property="deleted">
<Button color="danger" icon="trash">Delete<Button>
</Td>
</tr>
{:else}
<tr colspan={columns.length}>No Rows.</td>
{/each}
</svelte:fragment>
</Table>
Bonus Method: As a Tree Control
See TreeRow.
Extending
Guides on how to extend the Table component for various common use cases
There is a fully-functioning demo at /serverside on the demo page.
To successfully use the Table component in a context where the server/API is handling the sorting/pagination/filtering, you'll want to ensure you do the following:
- Pass the
sortDirection, sortColumn, perPageCount, currentPageNumber, & totalItemsCount props.
- Set the
filterEnabled prop to false. Alternatively, if your backend API has the ability to filter by string input, you can leave it true and pass an async function to filterMethod prop that either sets the rows or performs a state change with the now-filtered rows.
- Add a handler for the
pageChange event.
- Pass a
columnClickedMethod function. This will be called whenever the user clicks on a column.
- Make sure you pass
false for the sortType property on the columns definition for any columns which lack sorting support by your backend API.
- Don't use the
let:rows method of handling the rows. Instead, use the "Full Manual" option without a let:rows declaration. Example:
<Table
{rows}
{columns}
sortDirection={orderByDirection}
sortColumn={columns.find(column => column.property === orderByColumn)}
perPageCount={pageSize}
{currentPageNumber}
{totalItemsCount}
>
<svelte:fragment slot="body">
{#each rows as row}
{/each}
</svelte:fragment>
</Table
Custom Filter Logic
Various ways to change how the table filters rows, ordered by complexity.
filterProps
By default, the table will check the filter against every property in the columns array. If you want to match against a different set of properties, you can specify them with the filterProps prop.
<Table
{rows}
{columns}
filterProps={['foo', 'bar']}
></Table>
rowMatchesFilterMethod
By default, the table will show any rows whose filterProps (or column properties) contain a substring of the filter string. If you want to override that logic, you can pass a rowMatchesFilterMethod.
In simple cases, you can just use the default method, and add your own logic as well:
function rowMatchesFilterMethod(filter: string, row: R, props: Array<RowProperties<R>>) {
return table?.defaultRowMatchesFilter(...args) || someOtherLogic()
}
But you can use all your own logic too.
function rowMatchesFilterMethod(filter: string, row: R, props: Array<RowProperties<R>>) {
return props.some(prop => {
})
}
filterMethod
If you really need to override some larger part of the filter logic, you can pass the filterMethod prop. This isn't recommended and is mostly a holdover from the original version of the component, but you can use it if you want to.
Column Resizing
Enable the columnResizingEnabled prop, and pass the localStorageKey prop if you want to save the column sizes (and whether it's shown/hidden) to localstorage. These values are also stored in the columnInfo store if you want to do something else with it.
See Also
TreeRow
To use this component as a tree control, add the tree prop to your Table, use the TreeRow Component.
Td
You can use the Td Component in your table to support hiding columns.
The table component will handle the Pagination Component for you.
Considerations for 2.0.0
- Svelte 5 / Runes
- Remove
any default value from Column type's type arg and require that the consumer pass a type