Skip to content

Table options

SST_TableOptions<TData> is the single configuration object passed to useShadStackTable. Everything the table does — what data it renders, which features are on, how state is held, what slots get overridden — flows through it.

import {
ShadStackTable,
useShadStackTable,
type SST_ColumnDef,
type SST_TableOptions,
} from 'shadstack-table';
const options: SST_TableOptions<Person> = {
columns,
data,
// ...everything else is optional, documented below
};
const table = useShadStackTable(options);
return <ShadStackTable table={table} />;
OptionTypeDescription
columnsSST_ColumnDef<TData>[]Column definitions. See SST_ColumnDef for the full shape. Memoize this with useMemo — re-creating the array re-renders the table.
dataTData[]Row data. Same array identity stability rule as columns — keep it referentially stable when nothing has changed.

Both are typed against the same TData generic, so accessorKey on a column is narrowed to the keys of your row type.

OptionTypeDescription
defaultColumnPartial<SST_ColumnDef<TData>>Default props merged into every column. Set things like size, enableSorting, enableColumnFilter once.
initialStatePartial<SST_TableState<TData>>Starting state — columnVisibility, columnOrder, columnPinning, sorting, pagination.pageSize, density, etc.
statePartial<SST_TableState<TData>>Controlled state. Pair with the corresponding on*Change handlers to manage state outside the table.

Every major feature has an enable* boolean. They default sensibly — start with what you need and turn things off as you scope down. Selected highlights:

GroupOptions
SortingenableSorting, enableMultiSort, enableMultiRemove, enableSortingRemoval
FilteringenableColumnFilters, enableGlobalFilter, enableColumnFilterModes, enableGlobalFilterModes, enableGlobalFilterRankedResults, enableFacetedValues, enableFilterMatchHighlighting, enableFilterByColumnMenuItem, enableFilterModeMenuDividers
Row selectionenableRowSelection, enableSelectAll, enableMultiRowSelection, enableBatchRowSelection
PaginationenablePagination
ExpansionenableExpanding, enableExpandAll
EditingenableEditing, enableCellActions
VirtualizationenableRowVirtualization, enableColumnVirtualization
PinningenableColumnPinning, enableRowPinning, enablePinning
Reorder & dragenableColumnDragging, enableRowDragging, enableColumnOrdering, enableRowOrdering
Column resizeenableColumnResizing
VisibilityenableHiding, enableColumnActions
ToolbarsenableTopToolbar, enableBottomToolbar, enableToolbarInternalActions, enableDensityToggle, enableFullScreenToggle, enableGlobalFilterToggle
Layout polishenableTableHead, enableTableFooter, enableStickyHeader, enableStickyFooter, enableRowNumbers
MiscenableClickToCopy, enableRowActions, enableKeyboardShortcuts

Most of these accept boolean or, for row-targeted features, (row: SST_Row<TData>) => boolean — handy for “selectable only when X” / “editable only when Y” rules.

Every state slice you might want to control externally has a matching on*Change handler. Pair them with a corresponding entry in state to drive state from your own store:

const [sorting, setSorting] = useState<SST_SortingState>([]);
const table = useShadStackTable({
columns,
data,
state: { sorting },
onSortingChange: setSorting,
});

The handler signature is TanStack’s OnChangeFn<T> — it takes the next value or an updater function (prev) => next. Apply it the same way useState’s setter applies updates.

Common change handlers: onSortingChange, onColumnFiltersChange, onGlobalFilterChange, onPaginationChange, onColumnVisibilityChange, onColumnOrderChange, onColumnPinningChange, onRowSelectionChange, onExpandedChange, onDensityChange, onIsFullScreenChange.

slotProps is the single passthrough for styling and prop-overriding every internal slot — toolbars, head cells, body cells, buttons, dialogs, etc. Each slot’s slotProps shape accepts the same props the underlying shadcn primitive does.

useShadStackTable({
columns,
data,
slotProps: {
tablePaper: { className: 'rounded-xl' },
tableContainer: { className: 'max-h-[480px]' },
tableHeadCell: { className: 'bg-muted/40' },
},
});

Slots also accept callbacks of the form (props) => className | object when the override depends on the current row, column, or cell.

Some slots receive an inline style from the library. Inline styles outrank any class, so a className setting one of these properties is accepted and silently loses:

PropertySlotsWhere it comes from
backgroundColortablePaper, topToolbar, bottomToolbar, tableHeadRow, tableBodyRow, footer rowtheme.baseBackgroundColor; body rows also read selectedRowBackgroundColor and pinnedRowBackgroundColor
maxHeighttableContainerenableStickyHeaderclamp(350px, calc(100vh - <toolbar height>), 9999px). Full-screen uses calc(100vh - <toolbar height>)

Head and body cells inherit their row’s background rather than setting one, so styling the row covers the cells.

Every slot merges slotProps.<slot>.style after its own, so style on the same slot wins:

slotProps: {
// Hand the surface back to CSS; the class can now paint it.
tablePaper: { style: { backgroundColor: 'transparent' }, className: 'glass rounded-lg' },
// Size by the parent instead of the viewport — for a table in a pane,
// a modal, or a split view rather than a full page.
tableContainer: { style: { maxHeight: 'none' }, className: 'min-h-0 flex-1' },
}

For colour specifically, theme is usually the shorter route: it is merged over the library defaults, so a partial theme stays partial and one key reaches every surface that reads it.

Inputs the library renders — global search, column filters, cell editors — take their background from --sst-input-bg, defaulting to shadcn’s own (transparent in light, input/30 in dark). A host whose form controls paint a solid field colour sets it once:

:root {
--sst-input-bg: var(--your-field-color);
}

That covers both colour modes and every input slot. Overriding slotProps.searchInput / slotProps.filterInput with a background class also works, but needs repeating per slot and per mode.

For deep customization of a specific surface — toolbar layout, empty state, action menu, etc. — provide a render* callback in place of overriding via slotProps. The renderer is passed { table, ... } and returns a ReactNode:

  • renderTopToolbar(props) / renderBottomToolbar(props)
  • renderEmptyRowsFallback(props)
  • renderRowActions(props), renderRowActionMenuItems(props)
  • renderCellActionMenuItems(props), renderColumnActionsMenuItems(props)
  • renderDetailPanel(props)
  • renderColumnFilterModeMenuItems(props), renderGlobalFilterModeMenuItems(props)

renderColumnActionsMenuItems receives the built-in entries as internalColumnMenuItems. Each carries a stable id from SST_COLUMN_MENU_ITEM_IDS as its React key, so an override can target one entry without depending on its position:

import { SST_COLUMN_MENU_ITEM_IDS } from 'shadstack-table';
renderColumnActionsMenuItems: ({ internalColumnMenuItems }) =>
internalColumnMenuItems.filter((item) => item.key !== SST_COLUMN_MENU_ITEM_IDS.filterByColumn);

Reach for this when you’re overriding the slot anyway — to reorder entries, relabel one, or splice your own in. To only hide an entry, use the option that produces it (enableSorting, enableGrouping, enableColumnPinning, enableColumnResizing, enableHiding, enableFilterByColumnMenuItem); an override taken to hide something also freezes the entries you kept at their current behaviour.

OptionDescription
localizationOverride individual UI strings. See the localization guide.
memoModeMemoize cells / rows / the whole body for very large datasets. See memoize-components.
layoutMode'semantic' (default) renders <table> semantics; 'grid' / 'grid-no-grow' uses CSS Grid for tighter layout control.
positionToolbarAlertBanner'top' / 'bottom' / 'none'. Controls where the selection/filter banner appears.
iconsOverride the default lucide icon set with your own components. Keys match the SST_Default_Icons shape.