Pricing

DataTable handbook

v1.15.0 · @snowui-design-system/components

License: Before reading component documentation, source, or npm usage instructions for the first time, read the full LICENSE. Reuse that review for the same task and version. Public API configuration and internal implementation changes have different license implications. All components use the unified runtime package; source rights remain governed by the license.

Overview

Display and manage structured data with searching, sorting, selection, actions, pagination, and loading states.

When to use it

It is used when file or record lists need to be searched, filtered, sorted, pagined, checked or manipulated columns. DataTable coordinates the data status, and internally combines TableToolbar, Table series and Pagination; Table for pure display. The selection needs to be explicitly enabled, and the stable rowKey and rowId are used; the operation column is configured through actions. Business request functions and data formatting are provided by the page.

  • The client mode processes the data locally, and the server mode requests and provides results by the page; children receives the current rendering status.

  • Explicitly enable selection / actions; rowKey is indexed by default. When sorting or updating data, the stable business ID should be used, and TableRow.rowId should be consistent with it.

  • The page number starts from 1; the first loading, background refreshing and additional loadMore are in different states.

Quick start

This example shows the recommended basic composition. See the sections below for more cases and composition limits.

<script lang="ts">	import type { Snippet } from 'svelte';	import { Avatar, Button, Chip, formatDate, Frame, Pagination, Switch, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, type TableSortState } from '@snowui-design-system/components';	import { DataTable, TableToolbar, type DataTableLoadMoreRequest, type DataTableRenderState } from '@snowui-design-system/components';	import { Apple, AvatarFemale01, AvatarFemale02, AvatarFemale03, AvatarMale01, AvatarMale02, Facebook, FigmaA, Google, Gradient01, MagicWand, Medium, Perplexity, Question, Slack, SnowLogo, SortAscending, Calendar, ThreeDots } from '@snowui-design-system/resource-svelte';	import { OverflowActions, OverflowAction } from '@snowui-design-system/components';	const DEMO_NOW = new Date("2026-06-19T04:00:00Z");	const minute = 60 * 1000;	const hour = 60 * minute;	const day = 24 * hour;	    const baseRows = [	        { createdAt: new Date(DEMO_NOW.getTime() - 10 * 1000), store: "SnowUI Pro", logo: SnowLogo, subscriber: "Natali Craig", avatar: AvatarFemale01, plan: "Monthly $19.9", term: "First", price: 19.9, status: "Active" },	        { createdAt: new Date(DEMO_NOW.getTime() - minute), store: "Apple", logo: Apple, subscriber: "Kate Morrison", avatar: AvatarFemale02, plan: "Affiliate Monthly $19.9", term: "Renewal", price: 19.9, status: "Active" },	        { createdAt: new Date(DEMO_NOW.getTime() - hour), store: "Magic 3D Store", logo: MagicWand, subscriber: "Drew Cano", avatar: AvatarMale01, plan: "Monthly $19.9", term: "First", price: 9.95, status: "Active" },	        { createdAt: new Date(DEMO_NOW.getTime() - day), store: "Facebook", logo: Facebook, subscriber: "Orlando Diggs", avatar: AvatarMale02, plan: "Affiliate Monthly $19.9", term: "Renewal", price: 9.95, status: "Active" },	        { createdAt: new Date("2025-02-02T12:00:00Z"), store: "My illustration world", logo: Gradient01, subscriber: "Andi Lane", avatar: AvatarFemale03, plan: "Affiliate Yearly $189", term: "Renewal", price: 189, status: "Active" },	        { createdAt: new Date(DEMO_NOW.getTime() - 20 * 1000), store: "Medium", logo: Medium, subscriber: "Natali Craig", avatar: AvatarFemale01, plan: "Yearly $189", term: "First", price: 189, status: "Active" },	        { createdAt: new Date(DEMO_NOW.getTime() - 2 * minute), store: "Perplexity", logo: Perplexity, subscriber: "Kate Morrison", avatar: AvatarFemale02, plan: "Yearly $189", term: "First", price: 189, status: "Active" },	        { createdAt: new Date(DEMO_NOW.getTime() - 2 * hour), store: "Figma", logo: FigmaA, subscriber: "Drew Cano", avatar: AvatarMale01, plan: "Yearly $189", term: "First", price: 189, status: "Active" },	        { createdAt: new Date(DEMO_NOW.getTime() - day - hour), store: "Google", logo: Google, subscriber: "Orlando Diggs", avatar: AvatarMale02, plan: "Yearly $189", term: "First", price: 189, status: "Expired" },	        { createdAt: new Date("2025-02-02T10:00:00Z"), store: "Slack", logo: Slack, subscriber: "Andi Lane", avatar: AvatarFemale03, plan: "Yearly $189", term: "First", price: 189, status: "Expired" },	    ];	    const initialRows = Array.from({ length: 200 }, (_, index) => {	        const source = baseRows[index % baseRows.length];	        return {	            ...source,	            id: index + 1,	            note: source.term === "Renewal" ? "Subscription renewal" : "New subscription",	            createdAt: new Date(	                source.createdAt.getTime() -	                    Math.floor(index / baseRows.length) * day,	            ),	        };	    });	    type Row = (typeof initialRows)[number];	let rows = $state(initialRows);	let page = $state(1);	let pageSize = $state(10);	let tableState = $state<DataTableRenderState<Row>>();	let selectedRowIds = $state<Array<string | number>>([]);	let query = $state('');	let sort = $state<TableSortState>({ columnId: 'createdAt', direction: 'descending' });	let feedback = $state('');	const ACTIVE_ROW_ID = 2;	const sorters = { createdAt: (row: Row) => row.createdAt, store: (row: Row) => row.store };	const cellLinkStyle = 'color: inherit; text-decoration: none;';	async function copySelected() {	  try {	    await navigator.clipboard.writeText(rows.filter(row => selectedRowIds.includes(row.id)).map(row => `${row.store} — ${row.subscriber}`).join('\n'));	    feedback = 'Copied.';	  } catch (error) { feedback = 'Copy unavailable.'; throw error; }	}	function deleteSelected() {	  rows = rows.filter(row => !selectedRowIds.includes(row.id));	  selectedRowIds = [];	  feedback = 'Deleted.';	}</script>     {#snippet subscriptionsHeader(showNote: boolean)}        <TableHeader            ><TableRow>                <TableHead as="selection" />                <TableHead columnId="createdAt" label="Date" resizable>Date</TableHead>                <TableHead columnId="store" label="Stores" resizable>Stores</TableHead>                <TableHead columnId="subscriber" label="Subscriber" resizable>Subscriber</TableHead>                <TableHead columnId="price" label="Price" resizable>Price</TableHead>                <TableHead columnId="status" label="Status" resizable>Status</TableHead>                {#if showNote}<TableHead columnId="note" label="Note" resizable>Note</TableHead>{/if}                <TableHead as="actions" />            </TableRow></TableHeader        >    {/snippet}    {#snippet subscriptionsBody(stateRows: Row[], showNote: boolean, activeRowId: number, onlyLastAction: boolean, footer: Snippet | undefined = undefined)}        <TableBody>            {#each stateRows as row (row.id)}                <TableRow rowId={row.id} active={row.id === activeRowId}>                    <TableCell as="selection" />                    <TableCell columnId="createdAt" singleLine gap={1}>                        <Text tag="a" lines={1} onclick={(event) => { event.preventDefault(); event.stopPropagation(); }} onauxclick={(event) => { event.preventDefault(); event.stopPropagation(); }} style={cellLinkStyle} href={`#subscription-${row.id}-created-at`}>                            {formatDate(                                    row.createdAt,                                    { relative: true, now: DEMO_NOW, locale: 'en-US' },                                )}                        </Text>                    </TableCell>                    <TableCell columnId="store" singleLine>                        <Avatar as="leading" src={row.logo} size={24} />                        <Text tag="a" lines={1} onclick={(event) => { event.preventDefault(); event.stopPropagation(); }} onauxclick={(event) => { event.preventDefault(); event.stopPropagation(); }} style={cellLinkStyle} href={`#subscription-${row.id}-store`}>{row.store}</Text>                    </TableCell>                    <TableCell columnId="subscriber" singleLine>                        <Avatar as="leading" src={row.avatar} size={24} />                        <Text tag="a" lines={1} onclick={(event) => { event.preventDefault(); event.stopPropagation(); }} onauxclick={(event) => { event.preventDefault(); event.stopPropagation(); }} style={cellLinkStyle} href={`#subscription-${row.id}-subscriber`}>{row.subscriber}</Text>                    </TableCell>                    <TableCell columnId="price" singleLine gap={1}>                        <Text tag="a" lines={1} onclick={(event) => { event.preventDefault(); event.stopPropagation(); }} onauxclick={(event) => { event.preventDefault(); event.stopPropagation(); }} style={cellLinkStyle} href={`#subscription-${row.id}-price`}>${row.price}</Text>                        {#if row.term === "Renewal"}<Icon as="trailing" icon={Question} size={16} aria-label="Renewal price" />{/if}                    </TableCell>                    <TableCell columnId="status">                        <a onclick={(event) => { event.preventDefault(); event.stopPropagation(); }} onauxclick={(event) => { event.preventDefault(); event.stopPropagation(); }} style={cellLinkStyle} href={`#subscription-${row.id}-status`}>                            <Chip                                variant="background"                                style={`color:${row.status === "Active" ? "var(--secondary-green)" : "var(--black-40)"};`}                                >{row.status}</Chip                            >                        </a>                    </TableCell>                    {#if showNote}<TableCell columnId="note" nowrap={false} lines={false}>                            {row.note}                    </TableCell>{/if}                    <TableCell as="actions">                        <OverflowActions moreLabel={`More actions for ${row.subscriber}`}>                            {#if !onlyLastAction}                                <OverflowAction as="item" id="edit" label="Edit" onclick={() => feedback = `Editing ${row.subscriber}.`} />                                <OverflowAction as="item" id="info" label="Renewal info" icon="question" onclick={() => feedback = `${row.term} · ${row.plan}`} />                                <OverflowAction as="item" id="sort" label="Sort by store" icon="sort-ascending" onclick={() => sort = { columnId: 'store', direction: 'ascending' }} />                            {/if}                            <OverflowAction as="item" id="details" label="Subscription details" onclick={() => feedback = `${row.store} · ${row.status}`} />                        </OverflowActions>                    </TableCell>                </TableRow>            {/each}            {#if stateRows.length === 0}                <TableRow data-empty-state="true">                    <TableCell                        colspan={showNote ? 8 : 7}                        align="center"                        lines={false}                        tooltip={false}                        style="height: 25rem;"                    >                        No subscriptions                    </TableCell>                </TableRow>            {/if}            {#if footer}{@render footer()}{/if}        </TableBody>    {/snippet}<DataTable defaultSort={{ columnId: "createdAt", direction: "descending" }} variant="striped" {rows} {sorters} rowKey={(row) => row.id}  selection actions horizontalScroll={false} roleLayout="overlay" bind:query bind:sort bind:pageSize bind:page  bind:state={tableState} bind:selectedRowIds>  <TableToolbar searchPlaceholder="Search subscriptions…" filterKey="status"    filterOptions={[{ label: 'Active', value: 'Active' }, { label: 'Expired', value: 'Expired' }]}    onCopy={copySelected} onDelete={deleteSelected} />  <Table ariaLabel="Subscriptions">    {@render subscriptionsHeader(true)}    {@render subscriptionsBody(tableState?.rows ?? [], true, ACTIVE_ROW_ID, false)}  </Table>  <Pagination page={tableState?.page ?? 1} total={tableState?.totalPages ?? 1}    bind:pageSize pageSizeOptions={[10, 20, 50]} onPageChange={(value) => page = value} /></DataTable>

API

Keyword Type Default Description
rows T[] [] Current data
mode DataTableMode "client" Client slice or server-side controlled mode Default behavior: client.
sorters Record<string, Sorter<T>> {} Only complex derived columns need to be provided explicitly.
searcher Searcher<T> — Overwrite client mode search logic Default behavior: shallow string/number matching.
filterer Filterer<T> — Override client mode filtering logic Default behavior: field equivalent matching.
rowKey (row: T, index: number) => string | number (_row, index) => index Stable row identity reader Default behavior: index.
sort TableSortState defaultSort The current sequence and direction; the client mode executes the sorting, and the server mode is handled by the caller.
defaultSort TableSortState — Compatible with the initial single-column sorting; the new code is prioritized to declare defaultSort on TableHead
query string "" The current search text; the client mode performs the search, and the server mode is handled by the caller.
filters Record<string, unknown> {} Client mode executes field-by-field equivalent or array-containing filtering
page number 1 The current page number starts from 1; the rendering status will be taken to the valid page range.
pageSize number 10 Rows per page; changing this resets to page 1.
totalRows number — The total number of lines of server mode
loading boolean false The first loading status is passed to the table loading and rendering status.
refreshing boolean false The background refresh status retains the existing data and provides it through the rendering status.
loadMore DataTableLoadMore<T> — Incremental data source; request includes offset, limit and AbortSignal
loadMoreSize number 5 The maximum number of rows loaded in increments per time
loadMoreMode DataTableLoadMoreMode "manual" Manually call state.loadMore(), or automatically trigger by the internal table tail sentry. Default behavior: manual.
loadMoreThreshold number 80 How many px does the auto mode continue to scroll after crossing the bottom of the table to trigger?
loadMoreThrottleMs number 500 The auto mode confirms the waiting time after entering the trigger area for the first time, and serves as the minimum interval for continuous requests; leaving the trigger area during the first waiting period is canceled.
hasMore boolean — Whether the explicit overlay can still be loaded; otherwise, it will be derived according to totalRows first, and then infer according to the return quantity. Default behavior: automatic derivation.
error unknown — External errors take precedence over incremental loading errors to enter state.error; components do not automatically generate business error copy.
resetPageOnQuery boolean true Reset the page number to 1 when querying or filtering changes.
onSortChange (sort: TableSortState | undefined) => void — Sorting change callback, pass in undefined when unsorting.
onQueryChange (query: string) => void — Call back when querying text changes.
onPageChange (page: number) => void — Call back when the page number changes, and the parameter starts from 1.
onPageSizeChange (pageSize: number) => void — Triggered when the number of lines per page after normalization changes
onStateChange (state: DataTableRenderState<T>) => void — Callback when the current rendering status is updated, including visible rows, paging, selection and loading status.
state DataTableRenderState<T> — The current rendering state that can be bound
variant DataTableVariant "divided" Line vision; zebra pattern or dividing line Default behavior: divided.
selectedRowIds Array<string | number> [] Selected line id; the selected status is held by DataTable
selection boolean false Whether to display the checkbox selection column; the Head/Cell of as="selection" when false is not rendered
actions boolean | DataTableActionsConfig<T> false Whether to display the operation column; the configuration object will generate OverflowActions in the empty actions Cell by default.
horizontalScroll boolean true Send a horizontal scrolling strategy to Table; when false, close the ordinary columns that are allowed to be hidden from right to left, and scroll the bottom when the protection column is still super wide.
roleLayout TableRoleLayout "auto" When the width, the role column is permanent, and if the space is not enough, first turn the hover floating layer; column / overlay fixes the specified layout.
roleOverlayLayers number 4 Overlay the number of progressive blur layers
roleOverlayBlurMode TableRoleOverlayBlurMode "progressive" Blur algorithm for role overlays; defaults to progressive.
roleOverlayUniformFeather number 0.3 Uniform mode inner feathering ratio
onRowPreview (row: T, state: DataTableRenderState<T>, event: MouseEvent) => void — After configuration, the blank click in the TableRow line to open the row preview; when not configured, the blank click does not respond.
onSelectedRowIdsChange (rowIds: Array<string | number>) => void — Select the full snapshot after the change
children Snippet<[DataTableRenderState<T>]> — Table, toolbar and paging combine content, and receive the current rendering status.

See the base component documentation for inherited capabilities not redeclared here. This table lists only named public Props declared by this component.

Usage boundaries and details

Pass column headings through TableHead’s children slot. Text, icons, and non-interactive components may be combined. Give icon-only headings an accessible name, and do not nest interactive controls in sortable headings. See TableHead icon and component headings.

Layout model

Main layout model: composite management layout.

Structure:

  • Root Frame (column status and combination shell)

    • TableToolbar of the user's combination (optional fixed operation area)

    • Table (semantic table area)

    • Pagination (optional paging area)

    • Other status areas such as loading / error / empty declared by the user

Local layout source: Root Frame uses column + contentFill to manage the direct area; TableToolbar maintains its own row slot host layout, Table maintains the native table layout, and Pagination maintains the inline shell layout. DataTable only coordinates regional arrangement and data status, and does not rewrite the main layout model of subcomponents.

Padding attribution: DataTable root layer does not create padding; toolbar, table Cell, paging and custom status areas are the responsibility of their respective components. The table header of the internal Table and the left and right padding of the data cell respond according to the width of the Table container between 4px and 12px, keep 8px up and down, the outer edge of the table is at least 8px, and the minimum between the adjacent column contents is 8px; DataTable does not copy the cell padding implementation.

Gap attribution: Root Frame uniformly controls the spacing of the direct area, which defaults to var(--gap-3)(12px); the internal gap of the subcomponent is controlled by the subcomponent itself.

Rolling attribution: DataTable does not build a separate rolling area, and the horizontal overflow directly inherits the local horizontal viewport and viewport bottom track of the internal Table; vertical virtualization or fixed table headers are still clearly provided by the upper business area. loadMoreMode="auto" Only observe the tail sentry inside DataTable: give priority to using the nearest vertical scrolling ancestor as root, and use the page viewport when it can't be found, and do not change the attribute of the vertical scrolling container.

When a columnId matches a row-object field, DataTable reads its original value and makes TableHead sortable. A date field may hold a Date object, numeric timestamp, or ISO 8601 date-time with zone; no sortType: "date" is needed. If sorters is supplied, its result provides the display value. A TableHead without a matching field or sorter stays static.

When defaultSort is not declared on any TableHead or DataTable, and the current sort === undefined, DataTable does not derive the ascending order of the first column, nor does it apply any other implicit sorting. The client mode directly retains the input order of rows prop; when the page enters filteredRows, it restores/maintains the original relative order of filteredRows.

TableHead defaultSort defines the default column and the default direction at the same time, which takes precedence over DataTable's own compatibility prop. When defaulting, the column is cycled according to "default → reverse → default", and only the reverse display activates the visual. When TableHead declares defaultSortActive at the same time, the default direction also displays the activation vision, and the loop is changed to "default → reverse → no sorting → default". Other columns remain "unsorted → ascending → descending → unsorted".

<script lang="ts">  import { Pagination, Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@snowui-design-system/components';  import { DataTable, TableToolbar, type DataTableRenderState } from '@snowui-design-system/components';  const rows = [{ id: 'one', name: 'Project files' }];  let page = $state(1);  let state = $state<DataTableRenderState<(typeof rows)[number]>>();</script> <DataTable {rows} rowKey={(row) => row.id} bind:page bind:state variant="striped">  <TableToolbar sort={false} filter={false} />  <Table>    <TableHeader><TableRow><TableHead columnId="name">Name</TableHead></TableRow></TableHeader>    <TableBody>      {#each state?.rows ?? [] as row (row.id)}        <TableRow rowId={row.id}><TableCell columnId="name">{row.name}</TableCell></TableRow>      {/each}    </TableBody>  </Table>  <Pagination {page} total={state?.totalPages ?? 1} onPageChange={(next) => page = next} /></DataTable>

The correct way to write relative dates is formatDate(row.createdAt, { relative: true, now }). Sorting continues to compare row.createdAt, and will not compare Just now.

The default area spacing of DataTable's toolbar, Table and Pagination is 12px. striped uses Surface/3 as the odd line background from the first line, does not display the dividing line, and all data lines always maintain 12px outer rounded corners; the header of divided maintains a right angle with the two ends of the static data line, the lower line of the header is Black/20% 0.5px, the dividing line of the data line is Black/10% 0.5px, and there is no dividing line at the end line. In the dividing line mode, the beginning and end of the hover line form a 12px rounded corner, and the upper and lower adjacent dividing lines are hidden at the same time; when the mouse is moved out, the background exits immediately, and the background afterglow of the rounded corners is restored to the right angle is not displayed.

Responsive

DataTable shares Table’s complete responsive rules: move role columns into overlays, compress spacing, then scroll or hide ordinary columns from right to left. horizontalScroll defaults to true. With false, TableHead responsiveHide={false} protects a column and the first ordinary column always remains. Text in the final column may shrink and truncate with a Tooltip for the full value; fixed-size controls still trigger scrolling. Explicit Table settings take priority. DataTable adds no separate measuring or scrolling layer.

Selection column and operation column

Default read-only table: selection={false}, actions={false}, checkbox columns and operation columns are not displayed. In markup, you can still declare <TableHead as="selection" /> / <TableCell as="actions">, etc.; when DataTable is closed, the whole column is not rendered and does not participate in column registration.

Read-only default state skips row selection Set query and fully selected collection scanning at the same time; TableRow does not read the selected state and does not register rowId. After turning on selection, restore the complete selection path, and the DOM and interaction protocol remain unchanged.

 <DataTable {rows}>…</DataTable>  <DataTable selection actions {rows}>…</DataTable>  <DataTable selection {rows}>…</DataTable>  <DataTable actions {rows}>…</DataTable>

actions={true} Keep the original explicit combination: the business can still write its own buttons in each <TableCell as="actions">. When passing the configuration object, the actions Cell of undeclared children uses OverflowActions by default; explicit children always replace the default content.

<DataTable    {rows}    rowKey={(row) => row.id}    actions={{        items: (row) => [            { id: "edit", label: "Edit", icon: "pencil-simple" },            { type: "separator", id: "danger" },            { id: "delete", label: "Delete", icon: "trash", disabled: !row.canDelete },        ],        overflow: "progressive",        overflowFrom: "end",        width: 120,        minWidth: 40,        maxWidth: 240,        resizable: true,        onAction: ({ row, actionId }) => runRowAction(row, actionId),    }}>    ...</DataTable>

The default column width of the configuration mode is 160px; compact="auto", overflow="all", overflowFrom="end". resizable defaults false; only roleLayout="column" supports actions column dragging, and the overlay mode never provides column width Indicator. The drag results are taken over by Table's columnWidths.__actions and sent to each row in real time; each OverflowActions receives the value availableWidth, and no new size observer will be created for each Cell. The overlay mode uses the same actions width as the floating layer width.

When the More menu is expanded, the current responsive allocation is frozen, and the row hover hidden rule also treats the open SelectButton as visible to avoid the disappearance of the trigger after the column width or pointer leaves the row. Separator is only retained when there are still operations on both sides, and will not remain independently in the visible area or at the beginning and end of the menu.

Line preview

After configuring onRowPreview, DataTable provides TableRow with a row preview processor through Table Context. TableRow only triggers the processor in the blank area in the row; clicking on the link, button, input control, selection column or actions column control will not trigger the row preview. When onRowPreview is not configured, the blank click in the line does not respond; when selection={true}, the selection is still responsible for the selection role column.

Load Skeleton rows for the first time

When loading && rows.length === 0, DataTable provides the first loading semantics to the descendants TableBody through Table Context; TableBody retains the real TableHeader and automatically generates the same number of 40px Skeleton data rows as the current pageSize according to the registered TableHead column structure. The Skeleton line uses the real tr / td semantic structure but the whole aria-hidden, and the DataTable root continues to output aria-busy="true".

  • The number of skeleton rows directly follows the normalized pageSize; the default is pageSize=10, so 10 lines are generated when the number of paged rows is not set.

  • Do not provide independent Skeleton row attributes, avoid the high inconsistency between the loading state and the real paging data, and reduce the page jump when the first data appears as much as possible.

  • Skeleton only replaces the first empty data loading; if there are already rows at loading or refreshing, continue to keep the existing data, and add the Spinner row in the middle of the column at the end of the TableBody to avoid the layout jumping when refreshing and clearly expressing that it is still loading.

  • TableHead registers the column order, role and column width identification through the Table Context specified in the documents of both parties; DataTable / TableBody does not scan the subcomponent class name or internal DOM.

  • TableBody loading={false} can explicitly close the Skeleton rows inherited from DataTable Context; the independent Table can be actively enabled by <TableBody loading>. At this time, the number of paging rows cannot be read and will be returned to 10 lines.

Action group configuration and visual budget

The actions configuration supports maxVisible (default 3), buttonProps, parts, and moreLabel using the corresponding OverflowActions types. Items support target, rel, buttonProps, and parts. The default is progressive overflow from the end; explicit all, compact, and direction settings remain supported. Group styles apply only to the generated group. Per-item buttonProps override that item's parts.Button. Explicit children replace the generated content; an explicit OverflowActions shares the table layout budget without inheriting generated-group visuals.

The action area uses the smallest of 20% of the table's visible viewport, 14rem, and the explicit or dragged column width, reserving space for selection and protected data. The budget includes padding, More, and the fade footprint. More keeps its minimum usable target size; very narrow tables scroll locally instead of shrinking text or controls or hiding data to retain actions.

DataTable style overlay

Read the full LICENSE first; reuse that review for the same task, version, and scope.

First use public component Props to match the desired appearance, preserving the existing look where possible. If changes are necessary, follow the style selection and override order: use your own class (including Tailwind already in the project) or an instance style. The names below are public overrides actually consumed by source; check their scope and do not modify private DOM. Choose the correct SnowUI token family. External apps need not install SnowUI website utilities.

This component source does not directly consume a registered component-specific override. Style composed children through public parts or an explicit child; do not guess variable names.

Design Guide · Token · Combination boundary

DataTable type and status

Required Props: no named required Props.

Bidirectional binding: bind:sort, bind:query, bind:filters, bind:page, bind:pageSize, bind:state, bind:selectedRowIds.

Inheritance statement: Omit<FrameProps, "children" | "variant">. The inheritance ability is subject to the public document of the base class; the fields in Omit have been excluded and cannot be used as they are according to the base class.

Share Frame API · Return API

The following type declarations are actually referenced by public Props and come from source. A Svelte Snippet denotes a content fragment; ComponentProps denotes the Props of the corresponding component.

DataTableMode

Source: components/src/components/DataTable.svelte

export type DataTableMode = "client" | "server";

Sorter

Source: components/src/components/DataTable.svelte

type Sorter<T> = (row: T) => unknown;

Searcher

Source: components/src/components/DataTable.svelte

type Searcher<T> = (row: T, query: string) => boolean;

Filterer

Source: components/src/components/DataTable.svelte

type Filterer<T> = (row: T, filters: Record<string, unknown>) => boolean;

TableSortState

Source: components/src/lib/table-context.svelte.ts

export interface TableSortState {    columnId: string;    direction: TableSortDirection;}

TableSortDirection

Source: components/src/lib/table-context.svelte.ts

export type TableSortDirection = "ascending" | "descending";

DataTableLoadMore

Source: components/src/components/DataTable.svelte

export type DataTableLoadMore<T> = (        request: DataTableLoadMoreRequest,    ) => DataTableLoadMoreSource<T> | Promise<DataTableLoadMoreSource<T>>;

DataTableLoadMoreRequest

Source: components/src/components/DataTable.svelte

export interface DataTableLoadMoreRequest {        offset: number;        limit: number;        signal: AbortSignal;    }

DataTableLoadMoreSource

Source: components/src/components/DataTable.svelte

export type DataTableLoadMoreSource<T> = Iterable<T> | AsyncIterable<T>;

DataTableLoadMoreMode

Source: components/src/components/DataTable.svelte

export type DataTableLoadMoreMode = "manual" | "auto";

DataTableRenderState

Source: components/src/components/DataTable.svelte

export interface DataTableRenderState<T = unknown> {        rows: T[];        sourceRows: T[];        query: string;        sort?: TableSortState;        page: number;        pageSize: number;        totalRows: number;        totalPages: number;        loading: boolean;        refreshing: boolean;        loadingMore: boolean;        hasMore: boolean;        loadMoreSize: number;        filters: Record<string, unknown>;        error?: unknown;        getRowKey: (row: T, index: number) => string | number;        loadMore: () => Promise<void>;    }

DataTableVariant

Source: components/src/components/DataTable.svelte

export type DataTableVariant = "striped" | "divided";

DataTableActionsConfig

Source: components/src/components/DataTable.svelte

export interface DataTableActionsConfig<T> {        items: (row: T, state: DataTableRenderState<T>) => DataTableActionsItem[];        onAction?: (event: DataTableActionEvent<T>) => void;        compact?: "auto" | "none";        overflow?: "all" | "progressive";        overflowFrom?: "start" | "end";        width?: number;        minWidth?: number;        maxWidth?: number;        resizable?: boolean;        maxVisible?: number;        buttonProps?: ComponentProps<typeof OverflowActions>["buttonProps"];        parts?: ComponentProps<typeof OverflowActions>["parts"];        moreLabel?: string;    }

DataTableActionsItem

Source: components/src/components/DataTable.svelte

export type DataTableActionsItem =        | DataTableActionItem        | DataTableActionSeparatorItem;

DataTableActionItem

Source: components/src/components/DataTable.svelte

export interface DataTableActionItem extends OverflowActionsActionItem {        type?: "action";        id: string;        label: string;        icon?: string | Snippet;        disabled?: boolean;        href?: string;        onAction?: () => void;    }

OverflowActionsActionItem

Source: components/src/components/OverflowActions.svelte

export interface OverflowActionsActionItem {        type?: "action";        id: string;        label: string;        icon?: OverflowActionIcon;        disabled?: boolean;        href?: string;        target?: string;        rel?: string;        onAction?: () => void;        buttonProps?: GlobalButtonProps;        parts?: ComponentProps<typeof OverflowAction>["parts"];    }

OverflowActionIcon

Source: components/src/logic/overflow-actions.svelte.ts

export type OverflowActionIcon = string | Snippet;

GlobalButtonProps

Source: components/src/components/OverflowActions.svelte

export type GlobalButtonProps = Omit<ButtonProps, ControlledGlobalButtonKeys>;

ButtonProps

Source: components/src/components/OverflowActions.svelte

type ButtonProps = ComponentProps<typeof Button>;

ControlledGlobalButtonKeys

Source: components/src/components/OverflowActions.svelte

type ControlledGlobalButtonKeys =        | "children"        | "as"        | "iconOnly"        | "disabled"        | "href"        | "onclick"        | "aria-label"        | "title";

DataTableActionSeparatorItem

Source: components/src/components/DataTable.svelte

export interface DataTableActionSeparatorItem {        type: "separator";        id: string;    }

TableRoleLayout

Source: components/src/lib/table-context.svelte.ts

export type TableRoleLayout = "auto" | "column" | "overlay";

TableRoleOverlayBlurMode

Source: components/src/lib/table-context.svelte.ts

export type TableRoleOverlayBlurMode = "progressive" | "uniform";