[#st_tablebodycell-]
The ` ` is a `` element that surrounds the cell value or custom `Cell` render.
##### \ [#st_tablebodycellvalue-]
Renders the value of a cell. This is a popular component to use as an alternative to TanStack Table's `flexRender` method. It renders everything inside of the ``, but not the ` ` itself.
##### \ [#st_tablebodyrowgrabhandle-]
A button for grabbing a row for dragging.
##### \ [#st_tablebodyrowpinbutton-]
A button or buttons for pinning a row.
#### TableFooter Components [#tablefooter-components]
[View TableFooter Components Source Code on GitHub](https://github.com/anindosarker/shadcn-table/tree/master/packages/shadcn-table/src/components/ui/shadcn-table/footer)
##### \ [#st_tablefooter-]
The ` ` component contains the `` element, and all of the table footer rows.
##### \ [#st_tablefooterrow-]
The ` ` is a `` element that contains all of the table footer cells in a row.
##### \ [#st_tablefootercell-]
The ` ` is a `` element that surrounds the cell value or custom `Footer` render.
#### Input Components [#input-components]
[View Input Components Source Code on GitHub](https://github.com/anindosarker/shadcn-table/tree/master/packages/shadcn-table/src/components/ui/shadcn-table/inputs)
##### \ [#st_editcelltextfield-]
The textfield component for editing a cell value that can either show in a modal or table cell.
##### \ [#st_globalfiltertextfield-]
The search textfield component for global filtering.
##### \ [#st_filtertextfield-]
The textfield component for filtering a column. A somewhat comprehensive component that can be a textfield, select, multi-select, or date picker.
##### \ [#st_filtercheckbox-]
The checkbox component for filtering a column by boolean values.
##### \ [#st_filterrangeslider-]
The range slider component for filtering a column by a range of numbers.
##### \ [#st_filterfilterrangefields-]
A Container component that will render two ST_FilterTextField components for filtering in a range (min and max).
##### \ [#st_selectcheckbox-]
A checkbox component for selecting a row or selecting all rows from the table header.
# ST Hooks (/docs/api/st-hooks)
ST has been adopting more and more of a hooks based approach for its internal logic. Its API is becoming more standardized so that you can use the same hooks that the ST components use internally to build your own custom headless table if you want to.
### useShadcnTable [#useshadcntable]
[Source Code](https://github.com/anindosarker/shadcn-table/blob/master/packages/shadcn-table/src/components/ui/shadcn-table/hooks/useShadcnTable.ts)
> This is probably the only ST hook you will need to use, unless you are writing a custom headless table.
This is the main hook from Shadcn Table. It creates a TanStack Table instance with all of the features that ST provides and that you enable, and uses the proper default table options.
```tsx
import { useShadcnTable } from '@/components/ui/shadcn-table';
const table = useShadcnTable({
...options,
});
```
This hook is the combination of 2 other internal ST hooks: [`useST_TableOptions`](#usesrt_tableoptions), and [`useST_TableInstance`](#usesrt_tableinstance).
### useST_TableOptions [#usest_tableoptions]
[Source Code](https://github.com/anindosarker/shadcn-table/blob/master/packages/shadcn-table/src/components/ui/shadcn-table/hooks/useST_TableOptions.ts)
This hook simply takes in your custom table options and merges them with the default table options. It also does some extra logic to make sure that some default table options are set correctly based on other enabled features. For example, if you enable row virtualization features, the sticky header will also be enabled by default, and the layoutMode will adjust accordingly.
```tsx
import { useST_TableOptions } from '@/components/ui/shadcn-table';
const transformedTableOptions = useST_TableOptions({
...options,
});
```
### useST_TableInstance [#usest_tableinstance]
[Source Code](https://github.com/anindosarker/shadcn-table/blob/master/packages/shadcn-table/src/components/ui/shadcn-table/hooks/useST_TableInstance.ts)
This is where most of the magic happens. This hook is responsible for creating the TanStack Table instance, and adding all of the ST features to it. It needs table options to be passed in to it correctly, with good defaults, and it will return the table instance.
```tsx
import { useST_TableInstance } from '@/components/ui/shadcn-table';
const table = useST_TableInstance({
...transformedTableOptions, //usually from useST_TableOptions
});
```
This hook also uses the [`useST_Effects`](#usesrt_effects)
### useST_Effects [#usest_effects]
[Source Code](https://github.com/anindosarker/shadcn-table/blob/master/packages/shadcn-table/src/components/ui/shadcn-table/hooks/useST_Effects.ts)
This hook is responsible for adding some extra useEffect hooks to the table instance. These hooks are needed by some ST features on a table wide level.
### useST_Rows [#usest_rows]
[Source Code](https://github.com/anindosarker/shadcn-table/blob/master/packages/shadcn-table/src/components/ui/shadcn-table/hooks/useST_Rows.ts)
This hook is mostly a wrapper around `table.getRowModel`, but with a bit more custom logic for fuzzy ranking, row pinning, and more. It consumes a `table` instance and returns the rows that should be rendered in the main table body. This can be a useful hook if you are writing a custom headless table, but still want all of the extra ST enhanced behavior for fuzzy ranking, row pinning, etc. Alternatively, you can just use `table.getRowModel()` for a more vanilla TanStack Table experience.
```tsx
import { useShadcnTable, useST_Rows } from '@/components/ui/shadcn-table';
const table = useShadcnTable({
...options,
});
const rows = useST_Rows(table);
return rows.map((row) => {
//render row
});
```
### useST_ColumnVirtualizer [#usest_columnvirtualizer]
[Source Code](https://github.com/anindosarker/shadcn-table/blob/master/packages/shadcn-table/src/components/ui/shadcn-table/hooks/useST_ColumnVirtualizer.ts)
This hook is a wrapper around the `useVirtualizer` hook from TanStack Virtual. It consumes a `table` instance and returns a Column Virtualizer instance that is optimized for ST table columns, with considerations for other ST features like column pinning, column resizing, column hiding, and more.
```tsx
import {
useShadcnTable,
useST_ColumnVirtualizer,
} from '@/components/ui/shadcn-table';
const table = useShadcnTable({
...options,
});
const columnVirtualizer = useST_ColumnVirtualizer(table);
```
You would only need to use this hook if you are writing a custom headless table and want the same default column virtualization behavior that ST provides. If you are using the ST components, this hook is already used internally by the `ST_Table` component.
### useST_RowVirtualizer [#usest_rowvirtualizer]
[Source Code](https://github.com/anindosarker/shadcn-table/blob/master/packages/shadcn-table/src/components/ui/shadcn-table/hooks/useST_RowVirtualizer.ts)
This hook is a wrapper around the `useVirtualizer` hook from TanStack Virtual. It consumes a `table` instance and returns a Row Virtualizer instance that is optimized for ST table rows, with considerations for other ST features like row pinning, row dragging, and more.
```tsx
import {
useShadcnTable,
useST_RowVirtualizer,
} from '@/components/ui/shadcn-table';
const table = useShadcnTable({
...options,
});
const rowVirtualizer = useST_RowVirtualizer(table);
```
You would only need to use this hook if you are writing a custom headless table and want the same default row virtualization behavior that ST provides. If you are using the ST components, this hook is already used internally by the `ST_Table` component.
# State Options (/docs/api/state-options)
> Many of the state options you can pass here are the same as the ones you can pass to the TanStack Table v9 [useTable](https://tanstack.com/table/latest/docs/framework/react/guide/table-state) hook. Read them back from `table.state`.
Here is a list of all the state options you can pass to `initialState` or `state`.
```jsx
const table = useShadcnTable({
columns,
data,
//note: each individual state must be mutually exclusive to `initial state` or `state`
initialState: {
// all the state options you can pass here
},
state: {
// or here
},
});
```
# Table Instance APIs (/docs/api/table-instance-apis)
> NOTE: These are NOT the [table options](/docs/api/table-options) for Shadcn Table. These are just static methods on a table instance that you can use.
These are the methods available on the `table` instance that is returned from the `useShadcnTable` hook. You can use these methods to interact with the table instance.
```jsx
const columns = useMemo(() => [
{
accessor: 'name',
header: 'Name',
Cell: ({ cell, table }) => table.{/* or maybe here */}}> ,
},
], []);
const table = useShadcnTable({
columns,
data,
renderTopToolbarCustomActions: ({ table }) => table{/* or maybe here */}}>Click Me ,
});
const someEventHandler = () => {
table. //call any of the table instance methods here
};
return ;
```
# Table Options (/docs/api/table-options)
> Many of the options you can pass to `useShadcnTable` are the same as the ones you can pass to the TanStack Table v9 [useTable](https://tanstack.com/table/latest/docs/guide/tables) hook. The row model factories and the `features` object are set by the library (`stFeatures`), so they are not table options here.
Here is a list of all the table options that you can pass to the `useShadcnTable` hook.
```jsx
const table = useShadcnTable({
// all the options that you can pass here (recommended)
});
//or all of the props that could be passed to ` ` IF not using `useShadcnTable` hook
return (
);
return ; //recommended
```
# Advanced Example (/docs/examples/advanced)
Here is a more advanced example showcasing Shadcn Table's many features. Features such as row selection, expanding detail panels, header groups, column ordering, column pinning, column grouping, custom column and cell renders, etc., can be seen here.
This example is still only using client-side features. If you want to see an example of how to use Shadcn Table with server side logic and remote data, check out either the [Remote Data Example](/docs/examples/remote) or the [React-Query Example](/docs/examples/react-query).
View Extra Storybook **Examples **
# Aggregation and Grouping Example (/docs/examples/aggregation-and-grouping)
Grouping and Aggregation features are usually hard to implement, but ST (thanks to [TanStack Table](https://tanstack.com/table/latest/docs/framework/react/guide/grouping)) makes it easy. Once enabled, simply click the vertical ellipses (⋮) icon for the column you want to group by and select **Group by (column name)**.
You can group by a single column or multiple columns at a time. Then, you can run aggregations on grouped columns to calculate totals, averages, max, min, etc.
The Grouping and Aggregation features work hand in hand with the Expanding and Sorting features. Try grouping by various columns in the example below and sorting by the aggregated columns, such as "age" or "salary".
See the [Column Grouping](/docs/features/column-grouping) and [Aggregation](/docs/features/aggregation) docs for more information.
View Extra Storybook **Examples **
# Basic Example (/docs/examples/basic)
Shadcn Table enables client-side sorting, filtering, search, pagination, column hiding, and more by default. These features can easily be disabled or customized further, but here is a showcase of their default behavior. Be sure to check out a more [Advanced Example](/docs/examples/advanced) to see how more features can be customized.
View Extra Storybook **Examples **
# Chart Detail Panel Example (/docs/examples/chart-detail-panel)
The detail panel features can be used for a variety of purposes. In this example, we are integrating the new [Recharts](https://recharts.org/) library to display an expandable line chart for each row.
You could, of course, use any charting library that you prefer, but using Recharts will give you a consistent look and feel with Shadcn Table and any other shadcn/ui components you may be using.
View Extra Storybook **Examples **
# Column Grouping Example (/docs/examples/column-grouping)
Shadcn Table has a few different UI options and behaviors for displaying grouped columns. See the [Column Grouping Guide](/docs/guides/column-grouping) to learn more.
View Extra Storybook **Examples **
# Column DnD Ordering Example (/docs/examples/column-ordering)
Shadcn Table has built-in support for column drag and drop re-ordering. Learn more about column ordering in the [Column Ordering Feature Guide](/docs/guides/column-ordering-dnd).
View Extra Storybook **Examples **
# Column Pinning Example (/docs/examples/column-pinning)
Shadcn Table has an easy-to-enable column pinning feature. Columns can be pinned to the left or right of the table, and the column will be frozen in place while the rest of the table scrolls horizontally. See the [Column Pinning Feature Guide](/docs/guides/column-pinning) for more information.
View Extra Storybook **Examples **
# Column Virtualization Example (/docs/examples/column-virtualization)
Shadcn Table has a built-in column virtualization feature (via [`@tanstack/react-virtual`](https://tanstack.com/virtual/v3)) that allows you to render a large number of columns without major performance issues that you would normally see with a large number of DOM elements.
Try out the performance of the table below with **500 columns**! Filtering, Search, and Sorting also maintain usable performance.
Be sure to also check out the full [virtualization feature guide docs](/docs/guides/virtualization) to learn about both Row and Column Virtualization.
> NOTE: You should only enable row virtualization if you have a large number of rows or columns. Depending on the size of the table, if you are rendering fewer than a couple dozen rows at a time, you will actually just be adding extra overhead to the table renders. Virtualization only becomes necessary when you have over 50 rows or so at the same time with no pagination or dozens of columns.
View Extra Storybook **Examples **
# Custom Filter UI Example (/docs/examples/custom-filter-ui)
Shadcn Table supports rendering your own custom filter UI. This can be useful if you want to render your filters in a custom top toolbar, sidebar, or drawer. Learn more in the full [Column Filtering Feature Guide](/docs/guides/column-filtering).
View Extra Storybook **Examples **
# Custom Headless Example (/docs/examples/custom-headless)
For advanced use cases where you want to be in 100% control of your UI components, you can use Shadcn Table in a more headless way, kind of like how you would use [stock TanStack Table](https://tanstack.com/table/latest/docs/framework/react/examples/basic-use-table).
The power and flexibility here is almost limitless, as you can combine some already built-in Shadcn Table components alongside your own custom components. For the ST components to work properly, all you need to do is pass the `table` object returned from the `useShadcnTable` hook.
View Extra Storybook **Examples **
# Customized Grouping Example (/docs/examples/customized-grouping)
Here is an advanced example showing off various ways in which the expand column can be customized with column grouping features. See the [Column Grouping Guide](/docs/guides/column-grouping) to learn more.
View Extra Storybook **Examples **
# Detail Panel Example (/docs/examples/detail-panel)
Shadcn Table has multiple types of "expanding" row features. In its simplest form, you can use the `renderDetailPanel` option to render a component when a row is clicked. This is useful for showing additional information about a row, such as a list of nested data. Learn more in the [Detail Panel Feature Guide](/docs/guides/detail-panel).
View Extra Storybook **Examples **
# Dynamic Columns Example (/docs/examples/dynamic-columns)
This example shows how to generate column definitions dynamically from remote data after first render using TanStack Query. You may need to manage the `columnOrder` state manually if doing this.
View Extra Storybook **Examples **
# Editing CRUD Inline Cell Example (/docs/examples/editing-crud-inline-cell)
Full CRUD (Create, Read, Update, Delete) functionality can be easily implemented with Shadcn Table, with a combination of editing, toolbar, and row action features.
This example below uses the inline [`"cell"`](/docs/guides/editing#row-editing-mode) editing mode, which allows you to edit a single cell at a time. Hook up your own event listeners to save the data to your backend.
Check out the other editing modes down below, and the [editing guide](/docs/guides/editing) for more information.
View Extra Storybook **Examples **
# Editing CRUD Inline Row Example (/docs/examples/editing-crud-inline-row)
Full CRUD (Create, Read, Update, Delete) functionality can be easily implemented with Shadcn Table, with a combination of editing, toolbar, and row action features.
This example below uses the inline [`"row"`](/docs/guides/editing#row-editing-mode) editing mode, which allows you to edit a single row at a time with built-in save and cancel buttons.
Check out the other editing modes down below, and the [editing guide](/docs/guides/editing) for more information.
View Extra Storybook **Examples **
# Editing CRUD Inline Table Example (/docs/examples/editing-crud-inline-table)
Full CRUD (Create, Read, Update, Delete) functionality can be easily implemented with Shadcn Table, with a combination of editing, toolbar, and row action features.
This example below uses the inline [`"table"`](/docs/guides/editing#row-editing-mode) editing mode, which allows you to edit a single cell at a time, but all rows are always in an open editing state. Hook up your own event listeners to save the data to your backend.
Check out the other editing modes down below, and the [editing guide](/docs/guides/editing) for more information.
View Extra Storybook **Examples **
# Editing CRUD Tree Example (/docs/examples/editing-crud-tree)
Editing nested row data can be challenging. In this example, rows can be expanded to show sub rows, and editing and creating new rows can be done at any level.
Adding a new blank row in the exact row index position is now possible with the new `positionCreatingRow` table option.
This example also shows some complex client-side optimistic updates, where the table data is updated immediately after a valid user action.
View Extra Storybook **Examples **
# Editing CRUD Example (/docs/examples/editing-crud)
Full CRUD (Create, Read, Update, Delete) functionality can be easily implemented with Shadcn Table, with a combination of editing, toolbar, and row action features.
This example below uses the default [`"modal"`](/docs/guides/editing#modal-editing-mode) editing mode, where a dialog opens up to edit 1 row at a time.
Check out the other editing modes down below, and the [editing guide](/docs/guides/editing) for more information.
View Extra Storybook **Examples **
# Expanding Parsed Tree Example (/docs/examples/expanding-tree-flat-parse)
Shadcn Table supports showing rows in a expanding tree structure and parsing them from a flat data structure with the `getSubRows` option. Learn more about how to customize this in the [expanding sub-rows feature guide](/docs/guides/expanding-sub-rows).
View Extra Storybook **Examples **
# Expanding Tree Example (/docs/examples/expanding-tree)
Shadcn Table supports showing rows in a expanding tree structure. This example is the **_simplest_** implementation of this feature where each row of data potentially has a special `subRows` property that contains an array of sub rows. You don't have to follow this exact structure, but this is how ST will expect to find your sub rows by default.
Learn more about how to customize this in the [expanding sub-rows feature guide](/docs/guides/expanding-sub-rows).
View Extra Storybook **Examples **
# CSV Export Example (/docs/examples/export-csv)
Shadcn Table does not have a data exporting feature built in. However, you can easily integrate your own exporting feature, if desired.
In the example below, `export-to-csv` is connected to some export buttons in the top toolbar. If you need to export in Excel or PDF format, there are a variety of NPM packages that can be used to export in these formats.
View Extra Storybook **Examples **
# PDF Export Example (/docs/examples/export-pdf)
Shadcn Table does not have a data exporting feature built-in. However, you can easily integrate your own exporting features.
In the example below, `jspdf` and `jspdf-autotable` are connected to some export buttons in the top toolbar to export table rows to a PDF file that can be downloaded.
View Extra Storybook **Examples **
# Faceted Values Example (/docs/examples/faceted-values)
Shadcn Table can scan your data and automatically generate filter autocomplete suggestions, filter select options, or min and max values for your column filters. This is made possible by the faceted values feature from TanStack Table. Learn more in the full [Column Filtering Feature Guide](/docs/guides/column-filtering).
View Extra Storybook **Examples **
# Filter Switching Example (/docs/examples/filter-switching)
Shadcn Table supports letting users switch between multiple filter modes. Learn more in the full [Column Filtering Feature Guide](/docs/guides/column-filtering).
View Extra Storybook **Examples **
# Filter Variants Example (/docs/examples/filter-variants)
Shadcn Table not only has filtering built in, but it also has a lot of different filter variants that take care of a lot of the heavy lifting for you. Text filters, date filters, range filters, range slider filters, and more are all built in and ready to use. There is much more you can do to customize the behavior of filtering, so be sure to check out the full [Column Filtering Feature Guide](/docs/guides/column-filtering) for more information.
View Extra Storybook **Examples **
# Examples (/docs/examples)
## Quick Examples [#quick-examples]
item.href === '/docs/examples').items}
/>
# Infinite Scrolling Example (/docs/examples/infinite-scrolling)
An infinite scrolling table is a table that streams data from a remote server as the user scrolls down the table. This works great with large datasets, just like our [Virtualized Example](/docs/examples/virtualized), except here we do not fetch all of the data at once upfront. Instead, we just fetch data a little bit at a time, as it becomes necessary.
Using a library like [`@tanstack/react-query`](https://tanstack.com/query/v4) makes it easy to implement an infinite scrolling table in Shadcn Table with the `useInfiniteQuery` hook.
Enabling the virtualization feature is actually optional here but is encouraged if the table will be expected to render more than 100 rows at a time.
View Extra Storybook **Examples **
# Lazy Detail Panel Example (/docs/examples/lazy-detail-panel)
Fetching the additional data for the detail panels only after the user clicks to expand the row can be a good way to improve performance, and it is pretty easy to implement in Shadcn Table.
It's even easier if you are using [React Query.](https://tanstack.com/query/latest) In this example, react query is used to fetch the data for the detail panel, but only after the user clicks to expand the row. The data is also cached for a certain period of time so that when the detail panel is closed and reopened, the data is already available and won't need to be re-fetched.
View Extra Storybook **Examples **
# Lazy Sub-Rows Example (/docs/examples/lazy-sub-rows)
If you have a ton of nested data that you want to display, but you don't want to fetch it all up front, you can set up Shadcn Table to only fetch the sub-rows data when the user expands the row.
There are quite a few ways in which you could implement fetching sub-rows lazily. This example is just one way to do it.
This example combines concepts from the [React Query Example](/docs/examples/react-query) and the [Expanding Parsed Tree Example](/docs/examples/expanding-tree-flat-parse).
View Extra Storybook **Examples **
# Minimal Example (/docs/examples/minimal)
Shadcn Table gives you a lot out of the box, but it's also easy to turn off any features that you do not need.
Every feature has an `enable...` table option that let's you turn it on or off.
For example, you can turn off sorting or hide the top or bottom toolbars if you want.
View Extra Storybook **Examples **
# Popover Filters Example (/docs/examples/popover-filters)
Shadcn Table supports displaying column filters in other ways than in the default sub-header location. This example shows how to only display column filters when the user clicks on the filter icon in the header and then display the filters in a popover. Learn more in the full [Column Filtering Feature Guide](/docs/guides/column-filtering).
View Extra Storybook **Examples **
# React Query Example (/docs/examples/react-query)
This is just like the [Remote Data Fetching Example](/docs/examples/remote), but react-query is used to simplify all the state management of the fetching and loading of data.
For a step-by-step build of this pattern with a service, a query hook and a reusable state hook, read the [Building Production-Ready Tables](/docs/guides/production-ready-tables) guide.
[React Query](https://tanstack.com/query/v4) is by far the best way to fetch remote data in React. It has features like caching, refetching, polling, pagination, and more that work together very well with table logic as seen in this example.
Also, be sure to check out the [Virtualized Example](/docs/examples/virtualized), which shows off the use of another TanStack library, [React Virtual](https://tanstack.com/virtual/v3), to render thousands of rows at once while still maintaining great performance.
View Extra Storybook **Examples **
# Remote Data Fetching Example (/docs/examples/remote)
You will most likely be using a remote data source for your table, which is fully supported. Here is an example of data being fetched from a remote server but also filtered, paginated, and sorted on the server.
Also, be sure to check out the [React Query Example](/docs/examples/react-query), which is very similar to this one, except it uses react-query to simplify much of the state management needed for fetching data, so that you don't need to fetch from a `useEffect` hook.
View Extra Storybook **Examples **
# Row Dragging Example (/docs/examples/row-dragging)
Shadcn Table has built-in support row drag and drop features that can be used in a variety of ways. Learn more about column ordering in the [Row Ordering/DnD Feature Guide](/docs/guides/row-ordering-dnd).
View Extra Storybook **Examples **
# Row DnD Ordering Example (/docs/examples/row-ordering)
Shadcn Table has built-in support for row drag and drop re-ordering. Learn more about column ordering in the [Row Ordering Feature Guide](/docs/guides/row-ordering-dnd).
View Extra Storybook **Examples **
# Row Virtualization Example (/docs/examples/row-virtualization)
Shadcn Table has a built-in row virtualization feature (via [`@tanstack/react-virtual`](https://tanstack.com/virtual/v3)) that allows you to render a large number of rows without major performance issues that you would normally see with a large number of DOM elements.
Try out the performance of the table below with **10,000 rows**! Filtering, Search, and Sorting also maintain usable performance.
Be sure to also check out the full [virtualization feature guide docs](/docs/guides/virtualization) to learn about both Row and Column Virtualization.
> NOTE: You should only enable row virtualization if you have a large number of rows or columns. Depending on the size of the table, if you are rendering fewer than a couple dozen rows at a time, you will actually just be adding extra overhead to the table renders. Virtualization only becomes necessary when you have over 50 rows or so at the same time with no pagination or dozens of columns.
View Extra Storybook **Examples **
# Static Row Pinning Example (/docs/examples/static-row-pinning)
Shadcn Table has the ability to pin rows and keep them in view while scrolling. In this example, when rows are pinned, they will be pinned statically to either the top or bottom of the table. Learn more about row pinning in the [Row Pinning Feature Guide](/docs/guides/row-pinning).
View Extra Storybook **Examples **
# Sticky Header Example (/docs/examples/sticky-header)
Shadcn Table allows you to easily make the header and footer sticky while scrolling. See the [Sticky Header Feature Guide](/docs/guides/sticky-header) for more information.
View Extra Storybook **Examples **
# Sticky Row Pinning Example (/docs/examples/sticky-row-pinning)
Shadcn Table has the ability to pin rows and keep them in view while scrolling. In this example, when rows are pinned, they will initially appear to stay in place, but when the user scrolls up or down, the rows will stick to the top or bottom of the table. Learn more about row pinning in the [Row Pinning Feature Guide](/docs/guides/row-pinning).
View Extra Storybook **Examples **
# Sticky Row Selection Example (/docs/examples/sticky-row-selection)
Shadcn Table has the ability to pin rows as they are selected and keep them in view while scrolling. In this example, when rows are selected, they will initially appear to stay in place, but when the user scrolls up or down, the selected rows will stick to the top or bottom of the table. Learn more about row pinning in the [Row Pinning Feature Guide](/docs/guides/row-pinning).
View Extra Storybook **Examples **
# Virtualized Example (/docs/examples/virtualized)
Shadcn Table has a built-in virtualization features (via [`@tanstack/react-virtual`](https://tanstack.com/virtual/v3)) that allows you to render a large number of rows or columns without major performance issues that you would normally see with a large number of DOM elements.
Try out the performance of the table below with **10,000 rows** and over a dozen columns! Filtering, Search, and Sorting also maintain usable performance.
Be sure to also check out the full [virtualization feature guide docs](/docs/guides/virtualization) to learn about both Row and Column Virtualization.
> NOTE: You should only enable row virtualization if you have a large number of rows or columns. Depending on the size of the table, if you are rendering fewer than a couple dozen rows at a time, you will actually just be adding extra overhead to the table renders. Virtualization only becomes necessary when you have over 50 rows or so at the same time with no pagination or dozens of columns.
View Extra Storybook **Examples **
# AI Agent Setup (/docs/getting-started/agents)
### Ask your agent [#ask-your-agent]
The prompt points the agent at [`/SKILL.md`](/SKILL.md): install, the usage rules, where the option references are, and how to pull any docs example into the project. Every example page also has a "Copy prompt" in its menu for that example.
The skill knows both editions of the table: it reads the `style` in `components.json` (`radix-*` or `base-*`) and installs from the matching path of the hosted registry, `/r/radix/` or `/r/base/`. Without a `components.json` it runs `shadcn init` first and asks you for the base.
### Registry addresses [#registry-addresses]
One path per shadcn base, item names the same on both:
```bash
pnpm dlx shadcn@latest add https://www.shadcn-table.com/r/radix/shadcn-table.json # Radix UI
pnpm dlx shadcn@latest add https://www.shadcn-table.com/r/base/shadcn-table.json # Base UI
```
Namespace form, registered once per project: `{style}` resolves to the project's own style, so the same `@st/- ` address works on either base.
```bash
pnpm dlx shadcn@latest registry add @st=https://www.shadcn-table.com/r/{style}/{name}.json
pnpm dlx shadcn@latest add @st/shadcn-table
```
GitHub fallback when the site is unreachable: `anindosarker/shadcn-table/shadcn-table` (Radix UI) and `anindosarker/shadcn-table/shadcn-table-base` (Base UI).
### Skill [#skill]
The same file installs as a skill, so the agent knows the table before you ask:
```bash
npx skills add anindosarker/shadcn-table
```
### Docs as markdown [#docs-as-markdown]
- [`/llms.txt`](/llms.txt): index of all pages
- [`/llms-full.txt`](/llms-full.txt): all pages in one file
- Any page: its path under `/llms.mdx/docs` plus `/content.md`, for example [`/llms.mdx/docs/api/table-options/content.md`](/llms.mdx/docs/api/table-options/content.md). "Copy page" in the page bar copies the same text.
### Project rules [#project-rules]
For `CLAUDE.md` or `AGENTS.md` when the skill is not installed:
```md
## shadcn-table
- Data table: components/ui/shadcn-table (from the shadcn registry, edit freely).
- Before changing table options read /llms.mdx/docs/api/table-options/content.md
on the docs site. Column options: /llms.mdx/docs/api/column-options/content.md.
- `data` and `columns` given to useShadcnTable must be stable
(useMemo, useState or module scope).
- Reinstall: pnpm dlx shadcn@latest add https://www.shadcn-table.com/r/
/shadcn-table.json --overwrite
( is radix or base, from the style in components.json; replaces local edits in that directory).
```
### MCP [#mcp]
The shadcn MCP server browses the registries listed in `components.json`, so register the namespace above, then configure your client:
```bash
pnpm dlx shadcn@latest mcp init --client claude # or cursor, vscode, codex, opencode
```
**Registry URL to use:** `https://www.shadcn-table.com/r/ /registry.json`, with ` ` `radix` or `base`. The namespace template reaches it through the project's style (`/r/{style}/registry.json`). Available once the hosted registry is deployed.
# Installation (/docs/getting-started/install)
### Initialize shadcn/ui [#initialize-shadcnui]
Skip this if the project already uses shadcn/ui. The table needs shadcn/ui on the Radix UI or Base UI base, Tailwind CSS v4 and React 18 or newer; React Aria is not supported. It is built on TanStack Table v9 (`@tanstack/react-table` 9.3.1 is installed with it); a project still on TanStack Table v8 needs the [v9 migration](/docs/getting-started/migrating-v1) first. Pick your base with the Radix UI | Base UI tabs: the switch in the header does the same for the whole site, the examples included.
### Add shadcn-table [#add-shadcn-table]
Copies the table source into `components/ui/shadcn-table/` and installs the shadcn/ui parts and npm packages it needs, in the edition for the selected base. Run it again with `--overwrite` to update.
Or register the registry once and add items by name. `{style}` stands for the `style` in your `components.json` (`radix-nova`, `base-sera`, ...), so this form picks the base on its own.
### Import [#import]
```tsx
import { ShadcnTable, useShadcnTable } from '@/components/ui/shadcn-table';
```
Columns, data and the hook: [Usage](/docs/getting-started/usage). Letting an agent do all of this: [AI Agent Setup](/docs/getting-started/agents). Every [example](/docs/examples) renders, shows its source and installs for the selected base.
# Migrating to v1 (TanStack Table v9) (/docs/getting-started/migrating-v1)
shadcn-table v1 is built on TanStack Table v9. The table keeps its own option names (`enableRowPinning`, `st*Props`, `renderDetailPanel`, ...); where TanStack renamed something, the table and its `ST_*` types follow TanStack, with no aliases for the old names. Update with the install command and `--overwrite`, then apply the changes below to your own code. TanStack's own list is in the [TanStack Table v9 migration guide](https://tanstack.com/table/latest/docs/framework/react/guide/migrating).
### Dependency [#dependency]
- `@tanstack/react-table` 9.3.1 is required (it brings `@tanstack/table-core` 9.3.1, `@tanstack/react-store` and `@tanstack/store` 0.11.2). The install command updates it; a project that pins v8 elsewhere has to move to v9, TanStack Table v8 is no longer supported.
### Renamed names [#renamed-names]
| v0.x (TanStack Table v8) | v1 (TanStack Table v9) |
| ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `table.getState()` | `table.state` (`ST_TableState`: the TanStack slices plus `density`, `isFullScreen`, `creatingRow`, ... in one object) |
| column option `sortingFn` | `sortFn` |
| table option `sortingFns` | `sortFns` |
| type `ST_SortingFn`, registry `ST_SortingFns` | `ST_SortFn`, `ST_SortFns` |
| `@/components/ui/shadcn-table/fns/sortingFns` | `@/components/ui/shadcn-table/fns/sortFns` |
| state `columnSizingInfo`, type `ST_ColumnSizingInfoState` | `columnResizing`, `ST_ColumnResizingState` |
| `table.setColumnSizingInfo`, option `onColumnSizingInfoChange` | `table.setColumnResizing`, `onColumnResizingChange` |
| `columnPinning: { left, right }` | `columnPinning: { start, end }` (logical sides; `start` is left in LTR) |
| `column.pin('left' \| 'right' \| false)` | `column.pin('start' \| 'end' \| false)`, `column.getIsPinned()` returns `'start' \| 'end' \| false` (new type `ST_ColumnPinningPosition`) |
| `table.getLeftLeafColumns()`, `table.getRightLeafColumns()` | `table.getStartLeafColumns()`, `table.getEndLeafColumns()` |
| every other `Left`/`Right` method (`getLeftVisibleLeafColumns`, `getLeftTotalSize`, `row.getLeftVisibleCells`, ...) | the `Start`/`End` name; `column.getStart()`, `getAfter()`, `getIsFirstColumn()`, `getIsLastColumn()`, `getIndex()` take `'start' \| 'end' \| 'center'` |
| `table.getPaginationRowModel()`, `table.getPrePaginationRowModel()` | `table.getPaginatedRowModel()`, `table.getPrePaginatedRowModel()` |
### Row selection [#row-selection]
- The `enableBatchRowSelection` table option is removed. Shift-click range selection is TanStack's `enableRowRangeSelection` (default `true`); `isRowRangeSelectionEvent` customises which events count as a range selection.
- `table.refs.lastSelectedRowId` is removed (TanStack keeps the range anchor internally). `getST_RowSelectionHandler({ row, table })` no longer reads `staticRowIndex` (it is still accepted in the props type).
- `table.getIsSomeRowsSelected()`, `table.getIsSomePageRowsSelected()` and `row.getIsSomeSelected()` now mean "at least one" (the all-selected case included), per TanStack. The library gates its own indeterminate checkboxes with the matching `getIsAll*` check; do the same when one of these drives an indeterminate state in your code: `table.getIsSomeRowsSelected() && !table.getIsAllRowsSelected()`.
### Aggregation and the function registries [#aggregation-and-the-function-registries]
- Custom aggregation functions are v9 definitions: `constructAggregationFn({ aggregate: (context) => ..., merge? })` objects instead of `(columnId, leafRows, childRows)` callables. `ST_AggregationFn` is a definition or a name; `ST_AggregationFns.*` are definitions (call `.aggregate(context)`). A string `aggregationFn`, including names from the `aggregationFns` table option, is resolved by the library; an `aggregationFn` array still yields an array of results.
- The `filterFns`, `sortFns` and `aggregationFns` table options are typed by the library: `Record>`, `Record>` and `Record>`. The built-in registries live on the static features object; custom names are resolved by name.
- `ST_FilterFns.*` and `ST_SortFns.fuzzy` have v9 signatures (filter functions generic over the features with an optional `addMeta`; the fuzzy sort is built with `constructSortFn`).
### Table instance and types [#table-instance-and-types]
- New exports `stFeatures` and `ST_Features` (`utils/tanstack.features.ts`, also from the index): the `tableFeatures({...})` object with all 15 features, 8 row models and 3 registries. Use `createColumnHelper()` or TanStack types as `Column`; the public `ST_*` types keep their `` and `` signatures.
- `ST_TableInstance` is built on `ReactTable>`: `table.store`, `table.atoms`, `table.baseAtoms`, `table.Subscribe` and `table.FlexRender` are available.
- `ST_TableOptions` no longer accepts `features`, `onStateChange` or the `get*RowModel` options; the row models and `features` are set by the library.
- `ST_VisibilityState` now aliases TanStack's `ColumnVisibilityState` (same shape); `ST_ColumnResizingState` aliases TanStack's `columnResizingState`.
- `initialState.columnPinning` may still be partial (`{ start }` or `{ end }` alone); the library fills the missing side with `[]` because v9 does not. The `state.columnPinning` and `rowPinning` objects need both sides (`{ start: [], end: [...] }`, `{ top: [...], bottom: [] }`), and `rowSelection` only holds `true` entries (`Record`).
# Usage (/docs/getting-started/usage)
A first table in four steps. It will not cover all features of the library, but it is the shape every example builds on. [Install](/docs/getting-started/install) first.
### Import [#import]
```tsx
import {
ShadcnTable,
useShadcnTable,
} from '@/components/ui/shadcn-table';
```
`ShadcnTable` renders the table. `useShadcnTable` is the hook that takes the columns, the data and every other table option.
The install put the table in `components/ui/shadcn-table/` (edit anything), the shadcn/ui parts it uses in `components/ui/`, and the packages `@tanstack/react-table`, `@tanstack/react-virtual`, `@tanstack/match-sorter-utils`, `highlight-words`, `class-variance-authority`, `lucide-react` and `radix-ui` in your dependencies. English is the only locale installed; the others are separate items, see [Localization](/docs/guides/localization).
### Define the data [#define-the-data]
An array of objects whose keys match the accessors in your column definitions. Flat objects are easiest; nested data works with a little more column setup.
```jsx
//must be memoized or stable (useState, useMemo, defined outside of the component, etc.)
const data = [
{
name: 'John', // key "name" matches `accessorKey` in ColumnDef down below
age: 30, // key "age" matches `accessorKey` in ColumnDef down below
},
{
name: 'Sara',
age: 25,
},
];
```
> **Common Gotcha**: `data` passed to `useShadcnTable` must be _memoized_ or _stable_ (`useState`, `useMemo`, defined outside of your table component, etc.). Otherwise you may get infinite renders.
Data fetched from an API works the same way; see the [Remote Data examples](/docs/examples/react-query).
### Define the columns [#define-the-columns]
`accessorKey` points a column at a key of your data. `accessorFn` computes the value when it needs processing. Custom markup goes in `Cell`, never in the accessor.
```jsx
//simple column definitions pointing to flat data
const columns = useMemo(
() => [
{
header: 'Name',
accessorKey: 'name', //simple recommended way to define a column
//more column options can be added here to enable/disable features, customize look and feel, etc.
//optional custom cell render
Cell: ({ row }) => (
{row.name}
),
},
{
header: 'Age',
accessorFn: (dataRow) => parseInt(dataRow.age), //alternate way to access data if processing logic is needed
},
],
[],
);
```
> Note: Do NOT have your accessors resolve JSX or markup. That's what custom `Cell` renders are for. Accessors should only return primitive data so that the table can sort, filter, search, and group properly.
### Create the table and render it [#create-the-table-and-render-it]
```tsx
const table = useShadcnTable({ columns, data });
return ;
```
Every feature is a table option on the hook (`enableRowSelection`, `enableColumnOrdering`, ...) or a column option in the definitions. All of them: [Table Options](/docs/api/table-options), [Column Options](/docs/api/column-options).
### Full Simple Example [#full-simple-example]
Put it all together, and you have a basic table! You can also play around and enable some features, either per column in the column definitions, or as table options passed to `useShadcnTable`.
```tsx
import { useMemo } from 'react';
import {
ShadcnTable,
useShadcnTable,
type ST_ColumnDef, //if using TypeScript (optional, but recommended)
} from '@/components/ui/shadcn-table';
//If using TypeScript, define the shape of your data (optional, but recommended)
interface Person {
name: string;
age: number;
}
//mock data - strongly typed if you are using TypeScript (optional, but recommended)
const data: Person[] = [
{
name: 'John',
age: 30,
},
{
name: 'Sara',
age: 25,
},
];
export default function App() {
//column definitions - strongly typed if you are using TypeScript (optional, but recommended)
const columns = useMemo[]>(
() => [
{
accessorKey: 'name', //simple recommended way to define a column
header: 'Name',
stTableHeadCellProps: { style: { color: 'green' } }, //custom props
enableHiding: false, //disable a feature for this column
},
{
accessorFn: (originalRow) => parseInt(originalRow.age), //alternate way
id: 'age', //id required if you use accessorFn instead of accessorKey
header: 'Age',
Header: Age , //optional custom markup
Cell: ({ cell }) => {cell.getValue().toLocaleString()} , //optional custom cell render
},
],
[],
);
//pass table options to useShadcnTable
const table = useShadcnTable({
columns,
data, //must be memoized or stable (useState, useMemo, defined outside of this component, etc.)
enableRowSelection: true, //enable some features
enableColumnOrdering: true, //enable a feature for all columns
enableGlobalFilter: false, //turn off a feature
});
//note: you can also pass table options as props directly to instead of using useShadcnTable
//but the useShadcnTable hook will be the most recommended way to define table options
return ;
}
```
> **Note:** Again, it is very important that the columns and data definitions are _memoized_ or _stable_. Otherwise, the entire table will be re-rendered during every react re-render in your application, which can lead to performance issues. To make a variable stable, store it in `useState`, wrap it in `useMemo`, define it outside of a component, or in your state management tool of choice so it does not get recreated on every render and cause an infinite re-render loop.
### Live Example [#live-example]
### Next Steps [#next-steps]
There are numerous ways you can customize the behavior and look and feel of your Shadcn Table. View some of the [examples](/docs/examples) to see how you can customize your table, and visit the [props page](/docs/api/table-options) to see all the props that you can use to turn features on and off and customize the look and feel of your table.
Also, be sure to check out all the [Fundamental Guides](/docs/guides#fundamentals) and any of the [Advanced Feature Guides](/docs/guides#feature-guides) you may be interested in to learn more about the different features you can toggle on and off, or customize.
# Accessibility / Keyboard Navigation Guide (/docs/guides/accessibility)
Shadcn Table tries to get the basics of data grid accessibility right out of the box. But since you can easily add event handlers to just about any interaction inside of the table, you can heavily customize the accessibility of your table to your needs too.
### Relevant Table Options [#relevant-table-options]
### Keyboard Navigation [#keyboard-navigation]
Shadcn Table has a table option that is `true` by default: `enableKeyboardShortcuts`
#### `enableKeyboardShortcuts` [#enablekeyboardshortcuts]
This option enables the following keyboard shortcuts:
- `Tab` - Moves the focus to the next cell or element in the table (standard browser behavior)
- `Arrow Up` - Moves the focus 1 cell up
- `Arrow Down` - Moves the focus 1 cell down
- `Arrow Left` - Moves the focus 1 cell left
- `Arrow Right` - Moves the focus 1 cell right
- `Home` - Moves the focus to the first cell in the current row
- `End` - Moves the focus to the last cell in the current row
- `Page Up` - Moves the focus to the first cell in the current column
- `Page Down` - Moves the focus to the last cell in the current column
- `Ctrl/Cmd + Home` - Moves the focus to the first cell in the table (top left in left-to-right languages)
- `Ctrl/Cmd + End` - Moves the focus to the last cell in the table (bottom right in left-to-right languages)
- `Enter` - Performs certain actions in the currently focused cell such as sorting, row selection, row expansion, row pinning, etc.
- `Space` - Also performs certain actions in the currently focused cell such as sorting, row selection, row expansion, row pinning, etc.
- `Ctrl/Cmd + Enter` - Opens column actions menu on a header
- `Escape` - Exits full screen mode
A `tabIndex={0}` is also automatically added to all cells, headers, and footers when `enableKeyboardShortcuts` is enabled to allow for keyboard focus.
### Custom Keyboard Shortcuts [#custom-keyboard-shortcuts]
You do not have to use the provided keyboard shortcuts. You can turn off `enableKeyboardShortcuts` and add your own keyboard shortcuts using the `onKeyDown` event handler on all of the table cells (or any other element).
You can also add custom focus styles to any element using the `className` prop.
```tsx
const table = useShadcnTable({
columns,
data,
//add custom keyboard shortcuts
defaultColumn: {
//header
stTableHeadCellProps: {
onKeyDown: (event) => {
if (event.key === 't' && event.metaKey) {
alert('You pressed the custom shortcut!');
}
},
tabIndex: 0, //allow for keyboard focus
},
//body
stTableBodyCellProps: {
onKeyDown: (event) => {
if (event.key === 't' && event.metaKey) {
alert('You pressed the custom shortcut!');
}
},
//add custom focus styles (or just `focus:` if you want all focus events to be visible)
className:
'focus-visible:outline-2 focus-visible:outline-red-500 focus-visible:-outline-offset-2',
tabIndex: 0, //allow for keyboard focus
},
},
enableKeyboardShortcuts: false, //turn off default keyboard shortcuts from ST
});
```
### Custom Focus Styles [#custom-focus-styles]
An outline is automatically added to all elements when they are focused. The outline uses the shadcn `ring` color, so you can easily customize its color by overriding the `--ring` CSS variable on the table.
```tsx
const table = useShadcnTable({
columns,
data,
stTableLayoutProps: {
style: { '--ring': 'limegreen' } as React.CSSProperties,
},
});
```
Or, of course, you can just use the `className` or `style` props to override any styles you want, just like any other style override in ST.
```tsx
const table = useShadcnTable({
columns,
data,
defaultColumn: {
stTableBodyCellProps: {
className:
'focus-visible:outline-4 focus-visible:outline-red-500 focus-visible:-outline-offset-4',
},
},
});
```
### Full Screen Mode [#full-screen-mode]
A focus trap is applied to the table when in full screen mode. This is to prevent the user from navigating outside of the table when using the arrow keys.
The user can exit full screen mode by pressing `Escape`.
# Aggregation Guide (/docs/guides/aggregation)
Shadcn Table has built-in aggregation features. There are options for both automatic client-side grouping and aggregation, as well as manual server-side grouping and aggregation. This guide will walk you through the different options and how to use and customize them.
> See the [Column Grouping Guide](/docs/guides/column-grouping) as a prerequisite to this guide. The Aggregation and Grouping Guide was recently split into two separate guides.
### Relevant Table Options [#relevant-table-options]
### Relevant Column Options [#relevant-column-options]
### Relevant State [#relevant-state]
### Aggregation on Grouped Rows [#aggregation-on-grouped-rows]
One of the cool features of Shadcn Table is that it can automatically aggregate the data in grouped rows. To enable this, you must specify both an `aggregationFn` and an `AggregatedCell` render option on a column definition.
#### Built-in Aggregation Functions [#built-in-aggregation-functions]
There are several built-in aggregation functions available that you can use. They are:
- `count` - Finds the number of rows in a group
- `extent` - Finds the minimum and maximum values of a group of rows
- `max` - Finds the maximum value of a group of rows
- `mean` - Finds the average value of a group of rows
- `median` - Finds the median value of a group of rows
- `min` - Finds the minimum value of a group of rows
- `sum` - sums the values of a group of rows
- `uniqueCount` - Finds the number of unique values of a group of rows
- `unique` - Finds the unique values of a group of rows
_All of these built-in aggregation functions are from [TanStack Table](https://tanstack.com/table/latest/docs/framework/react/guide/aggregation#built-in-definitions)_
```jsx
const columns = [
{
accessorKey: 'team', //grouped by team in initial state below
header: 'Team',
},
{
accessorKey: 'player',
header: 'Player',
},
{
accessorKey: 'points',
header: 'Points',
aggregationFn: 'sum', //calc total points for each team by adding up all the points for each player on the team
AggregatedCell: ({ cell }) => Team Score: {cell.getValue()}
,
},
];
const table = useShadcnTable({
columns,
data,
enableGrouping: true,
initialState: { grouping: ['team'], expanded: true },
});
return ;
```
#### Custom Aggregation Functions [#custom-aggregation-functions]
If none of these pre-built aggregation functions work for you, you can also pass in a custom aggregation definition. Since TanStack Table v9 a custom aggregation is a `constructAggregationFn({ aggregate, merge? })` definition, not a plain callable: `aggregate` receives a context with the `rows` to aggregate and a `getValue(row)` accessor and returns the single value that will be displayed in the aggregated cell.
```tsx
import { constructAggregationFn } from '@tanstack/react-table';
const columns = [
{
accessorKey: 'points',
header: 'Points',
aggregationFn: constructAggregationFn({
aggregate: ({ rows, getValue }) =>
rows.reduce((sum, row) => sum + Number(getValue(row) ?? 0), 0),
}),
AggregatedCell: ({ cell }) => Team Score: {cell.getValue()}
,
},
];
```
Register reusable definitions by name in the `aggregationFns` table option and reference them from `aggregationFn: 'myAggregation'`.
### Aggregation on All Rows in Footer [#aggregation-on-all-rows-in-footer]
Shadcn Table does not automatically aggregate all rows for you to calculate totals for the entire table. However, it is still easy enough to do this manually and add in your custom calculations into the `footer` or `Footer` of a column definition. It is recommended that you do any necessary aggregation calculations on your data in a useMemo hook before passing it to the columns footer in your columns definition.
```jsx
//calculate the total points for all players in the table in a useMemo hook
const averageScore = useMemo(() => {
const totalPoints = data.reduce((acc, row) => acc + row.points, 0);
const totalPlayers = data.length;
return totalPoints / totalPlayers;
}, [data]);
const columns = useMemo(
() => [
{
accessorKey: 'name',
header: 'Name',
},
{
accessorKey: 'score',
header: 'Score',
Footer: () => Average Score: {averageScore}
, //do not do calculations in render, do them in useMemo hook and pass them in here
},
],
[averageScore],
);
```
> Please remember to perform heavy aggregation calculations in a useMemo hook to avoid unnecessary re-renders!
### Custom Cell Renders for Aggregation and Grouping [#custom-cell-renders-for-aggregation-and-grouping]
There are a few custom cell render overrides that you should be aware of when using grouping and aggregation features.
#### AggregatedCell Column Option [#aggregatedcell-column-option]
"Aggregation Cells" are cells in an aggregated row (not a normal data row) that can display aggregates (avg, sum, etc.) of the data in a group. The cell that the table is grouped on, however, is not an Aggregate Cell, but rather a [GroupedCell](#groupedcell-column-option).
You can specify the custom render for these cells with the `AggregatedCell` render option on a column definition.
```jsx
const columns = [
{
accessorKey: 'points',
header: 'Points',
aggregationFn: 'sum',
AggregatedCell: ({ cell }) => Total Score: {cell.getValue()}
,
},
];
```
#### GroupedCell Column Option [#groupedcell-column-option]
"Grouped Cells" are cells in a grouped row (not a normal data row) that by default display the value that the rows are grouped on and the number of rows in the group. You can override the default render for these cells with the `GroupedCell` render option on a column definition.
```jsx
const columns = [
{
accessorKey: 'team',
header: 'Team',
GroupedCell: ({ cell }) => Team: {cell.getValue()}
,
},
];
```
#### PlaceholderCell Column Option [#placeholdercell-column-option]
"Placeholder Cells" are cells that are usually meant to be empty in grouped rows and columns. They are simply rendered with a value of `null` by default, but you can override the default render for these cells with the `PlaceholderCell` render option on a column definition.
```jsx
const columns = [
{
accessorKey: 'team',
header: 'Team',
PlaceholderCell: ({ cell, row }) => (
{row.original.someOtherRowValue}
),
},
];
```
### Aggregation/Grouping Example [#aggregationgrouping-example]
### Multiple Aggregations Per column [#multiple-aggregations-per-column]
You may want to calculate more than one aggregation per column. You can specify an array of `aggregationFn`s, and then reference the aggregation results from an array in the `AggregatedCell` render option.
```jsx
const columns = [
{
header: 'Salary',
accessorKey: 'salary',
aggregationFn: ['count', 'mean'], //multiple aggregation functions
AggregatedCell: ({ cell, table }) => (
{/*get the count from the first aggregation*/}
Count: {cell.getValue()[0]}
{/*get the average from the second aggregation*/}
Average Salary: {cell.getValue()[1]}
),
},
];
```
### Manual Grouping [#manual-grouping]
Manual Grouping means that the `data` that you pass to the table is already grouped and aggregated, and you do not want Shadcn Table to do any of the grouping or aggregation for you. This is useful if you are using a backend API to do the grouping and aggregation for you, and you just want to display the results. However, you will need to put your data in the specific format that the `expanding` features understand.
# Async Loading UI Guide (/docs/guides/async-loading)
While you are fetching your data, you may want to show some loading indicators. Shadcn Table has some nice loading UI features built in that look better than a simple spinner.
> This guide is mostly focused on the loading UI features. Make sure to also check out the [Remote Data](/docs/examples/remote) and [React Query](/docs/examples/react-query) examples for server-side logic examples.
### Relevant Table Options [#relevant-table-options]
### Relevant State Options [#relevant-state-options]
### isLoading UI [#isloading-ui]
There are three different loading UI features that are built into Shadcn Table:
1. Loading Overlay - shows spinner overlay over the table container.
2. Cell Skeletons - show pretty and shimmering skeletons for each cell.
3. Linear Progress Bars - shows progress bars above and/or below the table.
You can use any combination of these loading UIs by managing the `showLoadingOverlay`, `showSkeletons`, and `showProgressBars` states.
There are also two other loading states that are shortcuts for combining some of the above states:
- `isLoading` - shows loading overlay and cell skeletons.
- `isSaving` - shows the progress bars and adds spinners to the save buttons in editing features.
Here is some of the recommended loading UI that you might use with React Query:
```jsx
const {
data = [],
isLoading: isLoadingTodos,
isRefetching: isRefetchingTodos,
} = useQuery({
/**/
});
const { mutate, isPending: isSavingTodos } = useMutation({
/**/
});
const table = useShadcnTable({
columns,
data,
state: {
isLoading: isLoadingTodos, //cell skeletons and loading overlay
showProgressBars: isRefetchingTodos, //progress bars while refetching
isSaving: isSavingTodos, //progress bars and save button spinners
},
});
return ;
```
> Note: The Loading Overlay UI makes the table container non-interactive while it is showing. This is usually desired while no data is yet in the table. Consider avoiding using the Loading Overlay UI during "refetching" operations like filtering, sorting, or pagination.
### Customize Loading UI [#customize-loading-ui]
You can customize the loading UI by passing props to the `stSkeletonProps`, `stLinearProgressProps`, and `stCircularProgressProps` props.
```jsx
const table = useShadcnTable({
columns,
data,
stSkeletonProps: {
className: 'bg-muted/70',
},
stLinearProgressProps: {
progressComponentProps: {
className: '[&>[data-slot=progress-indicator]]:bg-secondary-foreground',
},
},
stCircularProgressProps: {
className: 'text-secondary-foreground',
},
});
return ;
```
#### Custom Loading Spinner component [#custom-loading-spinner-component]
If you need to use a custom loading spinner component other than the built-in shadcn/ui one, you can now pass in custom component in the `stCircularProgressProps` `Component` prop.
```jsx
const table = useShadcnTable({
columns,
data,
stCircularProgressProps: {
Component: ,
},
});
```
### Only Show Progress Bars or Skeletons [#only-show-progress-bars-or-skeletons]
If you do not want both progress bars and cell skeletons to show, you can use the `showProgressBars` and `showSkeletons` states, instead.
```jsx
const table = useShadcnTable({
columns,
data,
state: {
showProgressBars: true, //or showSkeletons
},
});
```
### Full Loading and Server-Side Logic Example [#full-loading-and-server-side-logic-example]
Here is a copy of the full [React Query](/docs/examples/react-query) example.
# Best Practices (TypeScript) (/docs/guides/best-practices)
Here are some best practices to follow when using Shadcn Table. We'll cover Type-Safety (even if you are not using TypeScript) and how to best create re-usable ST components.
### Stay Up-To-Date [#stay-up-to-date]
Run this command in your terminal every few weeks to make sure you are using the latest version of Shadcn Table and shadcn/ui
```bash
pnpm dlx shadcn@latest add anindosarker/shadcn-table/shadcn-table
```
### Type-Safety [#type-safety]
TanStack Table itself is written in TypeScript, and Shadcn Table builds on top of its great type definitions for a best-in-class TypeScript experience.
If, however, you cannot use TypeScript in your project for some reason, checkout down below for [how to use JSDoc instead of TypeScript](#use-jsdoc-instead-of-typescript) to get the same type hints.
#### Is TypeScript Required? [#is-typescript-required]
No, TypeScript is not required to use Shadcn Table. You can just use JavaScript and everything will work just fine, but you will be missing out on a lot of great type hints and type safety that can help you build your app faster and with less bugs.
There are a couple of ways to still get type hints without TypeScript with the [`createSRTColumnHelper`](#createsrtcolumnhelper-utility) utility function or by using [JSDoc](#use-jsdoc-instead-of-typescript), so you can still get some of the benefits of type safety without TypeScript.
#### Defining TData Type [#defining-tdata-type]
Shadcn Table makes use of generics to make working with your specific row data structures easier. You will see that most of the `ST_*` types that you can use accept a `TData` generic.
Let's say that the data in your table is an array of users that looks like this:
```tsx
const data: User[] = [
{ id: 1, name: 'John', age: 23 },
{ id: 2, name: 'Alice', age: 17 },
{ id: 3, name: 'Bob', age: 32 },
];
```
Then your `TData` type can be defined as:
```tsx
export type User = {
id: number;
name: string;
age: number;
};
```
You will often pass this `TData` type as a generic to the `ST_*` types that you use so that you can get type hints for your specific data structure.
#### Define Your Columns With Type-Safety [#define-your-columns-with-type-safety]
Shadcn Table provides a couple of ways to define your columns with type safety. You can either simply use the `ST_ColumnDef` type or use the new `createSRTColumnHelper` utility function.
#### ST_ColumnDef Type [#st_columndef-type]
The most straightforward way to define your columns with type-safety is to just type your columns as `Array>`.
```tsx
import {
ShadcnTable,
useShadcnTable,
type ST_ColumnDef, // <--- import ST_ColumnDef
} from '@/components/ui/shadcn-table';
import { type User } from './types'; // <--- import your TData type from wherever you defined it
// define your columns, pass User as a generic to ST_ColumnDef
const columns: Array> = [
{
accessorKey: 'id', //you should get type hints for all of your keys if you defined your TData type correctly
header: 'ID',
enableSorting: false, //you should get type hints for all possible column options that you can define here
},
{
accessorKey: 'name',
header: 'Name',
},
{
accessorFn: (originalRow) => Number(originalRow.age), //you should also get type hints for your accessorFn
header: 'Age',
Cell: ({ cell }) => {cell.getValue()} , //cell.getValue() will be typed as `unknown` by default, but you can pass a generic to get the correct type
//see the createSRTColumnHelper example below for a better way to get type safety with cell.getValue()
},
];
```
#### createSRTColumnHelper Utility [#createsrtcolumnhelper-utility]
Alternatively you can use the `createSRTColumnHelper` utility function to define your columns. This works the same way as the TanStack `createColumnHelper`.
Additional `TValue` type-safety is provided by using this utility. That means that when you call `cell.getValue()` in either a custom `Cell` render, or in any of the `st*Props`, you will get the correct type for the data in that column instead of `unknown`.
```tsx
import {
ShadcnTable,
useShadcnTable,
createSRTColumnHelper, // <--- import createSRTColumnHelper
} from '@/components/ui/shadcn-table';
import { type User } from './types'; // <--- import your TData type from wherever you defined it (if using TS)
const columnHelper = createSRTColumnHelper(); // <--- pass your TData type as a generic to createSRTColumnHelper (if using TS)
//columns will be inferred as Array>
const columns = [
//accessorKey as first argument, rest of column options as second argument
columnHelper.accessor('name', {
header: 'Last Name',
}),
//accessorFn as first argument, rest of column options as second argument
columnHelper.accessor((row) => Number(row.age), {
id; 'age', //id required for accessorFn
header: 'Age',
filterVariant: 'range-slider', //you should get type hints for all possible column options that you can define here
Cell: ({ cell }) => {cell.getValue()} , //cell.getValue() will be typed as number instead of unknown
}),
//display column (no accessor needed)
columnHelper.display({
header: 'Contact',
Cell: ({ row }) => (
sendEmail(row.original.email)}>Send Email
),
}),
];
```
#### Use JSDoc instead of TypeScript [#use-jsdoc-instead-of-typescript]
If you are in a situation where you are not able to install TypeScript in your project, you can technically do the same thing as up above in JavaScript using JSDoc.
```jsx
import {
ShadcnTable,
useShadcnTable,
} from '@/components/ui/shadcn-table';
//define TData type with JSDoc
/**
* @typedef {Object} User
* @property {number} id
* @property {string} name
* @property {number} age
*/
//import ST_ColumnDef type with JSDoc
/**
* @type {import('@/components/ui/shadcn-table').ST_ColumnDef[]}
*/
const columns = [
{
accessorKey: 'id', //you should get type hints for all of your keys if you defined your TData type correctly
header: 'ID',
enableSorting: false, //you should get type hints for all possible column options that you can define here
},
{
accessorKey: 'name',
header: 'Name',
},
{
accessorFn: (originalRow) => Number(originalRow.age), //you should also get type hints for your accessorFn
header: 'Age',
},
];
```
### Re-Usable ST Components [#re-usable-st-components]
If you are going to have multiple tables in your app, chances are that you will want to make a re-usable component built on top of Shadcn Table. This is a good idea and good practice, but here are a few suggestions to maintain type safety with some TypeScript generics.
#### Re-usable Components or Options? [#re-usable-components-or-options]
In my opinion, instead of creating a re-usable component, it is instead actually best to define your default options and share them between all of your tables.
##### Re-usable Default Options [#re-usable-default-options]
In this example, we are simply creating a factory function that creates all of the default options that you want all of your tables to start with.
```ts
import {
type ST_RowData,
type ST_TableOptions,
} from '@/components/ui/shadcn-table';
//define re-useable default table options for all tables in your app
export const getDefaultSRTOptions = (): Partial<
ST_TableOptions
> => ({
//list all of your default table options here
enableGlobalFilter: false,
enableRowPinning: true,
initialState: { showColumnFilters: true },
manualFiltering: true,
manualPagination: true,
manualSorting: true,
stTableHeadCellProps: {
style: { fontSize: '1.1rem' },
},
paginationDisplayMode: 'pages',
//etc...
defaultColumn: {
//you can even list default column options here
},
});
```
Then you can use these options in every new table that you create:
```tsx
import {
ShadcnTable,
useShadcnTable,
type ST_ColumnDef,
} from '@/components/ui/shadcn-table';
import { getDefaultSRTOptions } from './utils'; //your default options
interface User {
id: number;
name: string;
age: number;
}
const defaultSRTOptions = getDefaultSRTOptions(); //get your default options
export const OneOfYourTableComponents = () => {
const columns: ST_ColumnDef[] = [
//...
];
const { data } = useQuery({
//...
});
const table = useShadcnTable({
...defaultSRTOptions, //spread your default options
columns,
data,
enableGlobalFilter: true, //override default options
initialState: {
...defaultSRTOptions.initialState, //spread default initial state
showColumnFilters: false, //override default initial state for just this table
},
//...
});
//you will have access to the entire table instance where you need it
console.log(table.state);
return ;
};
```
Doing it this way, you maintain 100% control of your table instance and any state that you are managing in each table component.
I believe this is by far the best way to work with Shadcn Table in your application code, and how I personally use it in my own apps.
##### Re-usable ST Component [#re-usable-st-component]
If you still want to just create a re-usable ST component instead, you can do that too, of course. Here is a type-safe way to do that:
```tsx
import {
ShadcnTable,
useShadcnTable,
type ST_ColumnDef,
type ST_RowData, //default shape of TData (Record)
type ST_TableOptions,
} from '@/components/ui/shadcn-table';
interface Props extends ST_TableOptions {
columns: ST_ColumnDef[];
data: TData[];
}
export const CustomSRTTable = ({
columns,
data,
...rest
}: Props) => {
const table = useShadcnTable({
columns,
data,
//your custom table options...
...rest, //accept props to override default table options
});
return ;
};
```
By using the `TData` generic correctly, you can maintain type-safety in your re-usable component that will adapt to different types of data you will have throughout your application.
Though, be aware that the weakness of this approach is that it will be more annoying to get access to the `table` instance or read table state where you need it.
When re-using your ST table component, it will just look something like this:
```tsx
const columns: ST_ColumnDef[] = [
//...
];
export const YourComponent = () => {
//no easy access to the table instance or table state here unless you manage all of the state in this component
const [pagination, setPagination] = useState({
pageIndex: 0,
pageSize: 10,
});
const [sorting, setSorting] = useState([]);
//etc...
const { data } = useQuery({
//...
});
return (
);
};
```
### Debugging Shadcn Table [#debugging-shadcn-table]
Shadcn Table _(though really TanStack Table)_ can be a lot easier to debug than other data grid libraries. This is because you are often in charge of the state that you care about, and the entire table instance is available to you in your own scope if you are using the `useShadcnTable` hook. There are also some advanced TanStack Table Dev Tools that you can optionally install.
#### Console Logging from the Table Instance [#console-logging-from-the-table-instance]
When in doubt, console log it! There are a lot of things you can easily console log. Here are some examples:
##### Console Log All Internal Table State [#console-log-all-internal-table-state]
```jsx
const table = useShadcnTable({
columns,
data,
//** */
});
console.log(table.state);
```
##### Console Log Current Rendering Rows [#console-log-current-rendering-rows]
```jsx
const table = useShadcnTable({
columns,
data,
//** */
});
console.log(table.getRowModel().rows);
```
# Cell Actions Guide (/docs/guides/cell-actions)
Shadcn Table provides you an easy shell to render a context menu for when a table cell/row is right clicked or otherwise activated. This is useful for providing additional actions that can be performed on a cell or row.
### Relevant Table Options [#relevant-table-options]
### Relevant Column Options [#relevant-column-options]
### Enable Cell Actions [#enable-cell-actions]
To enable cell actions, you need to set the `enableCellActions` option to `true` for the cells that you want to have access to the context menu. This can be done at the table level or at the column level, and accepts a boolean or a function that returns a boolean.
```jsx
const table = useShadcnTable({
columns,
data,
enableCellActions: true,
//or
enableCellActions: (cell) => (cell.row.original.someCondition ? true : false),
});
```
### Render Cell Action Menu Items [#render-cell-action-menu-items]
The cell actions context menu will only appear if there are items to display. You can provide the `renderCellActionMenuItems` table option or column option to render the appropriate items in the context menu for each cell.
ST also provides a `ST_ActionMenuItem` component that you can use to render the menu items. This just a wrapper for the shadcn/ui MenuItem component that also provides consistent CSS for styling the icons, spacing, and optional sub-menu items. Use it if you want to have a consistent look and feel with all of the other built-in ST Menus.
```jsx
const table = useShadcnTable({
columns,
data,
enableCellActions: true,
renderCellActionMenuItems: ({ closeMenu, cell, row, table }) => [
//array required
}
key={1}
label="Item 1"
onClick={() => {
//your logic here
closeMenu(); //close the menu after the action is performed
}}
table={table}
/>,
}
key={2}
label="Item 2"
onClick={async () => {
//await your logic here
closeMenu(); //close the menu after the async action is performed
}}
table={table}
/>,
],
});
```
### Include Automatic Cell Actions [#include-automatic-cell-actions]
A few cell actions are included by default when certain other features are enabled.
- A "Copy" action will be included when the `enableClickToCopy` option is set to `"context-menu"` (instead of `true`) for the table or column.
- An "Edit" action will be included when the `enableEditing` option is set to `true` and the `editDisplayMode` option is set to `"cell"`. This will not disable the default double-click to edit behavior, but will provide an additional way to edit the cell.
More built-in cell actions may be added in the future.
```jsx
const table = useShadcnTable({
columns,
data,
enableCellActions: true,
enableClickToCopy: 'context-menu',
enableEditing: true,
editDisplayMode: 'cell',
});
```
If you want to render these actions alongside your custom actions, you will just need to include the `internalMenuItems` parameter in your `renderCellActionMenuItems` function.
```jsx
const table = useShadcnTable({
columns,
data,
enableCellActions: true,
enableClickToCopy: 'context-menu',
enableEditing: true,
editDisplayMode: 'cell',
renderCellActionMenuItems: ({
closeMenu,
cell,
row,
table,
internalMenuItems,
}) => [
...internalMenuItems, //render the copy and edit actions wherever you want in the list
, //optionally place a Menu Divider to separate groups of actions
}
key={1}
label="Item 1"
onClick={() => {
//your logic here
closeMenu(); //close the menu after the action is performed
}}
table={table}
/>,
}
key={2}
label="Item 2"
onClick={async () => {
//await your logic here
closeMenu(); //close the menu after the async action is performed
}}
table={table}
/>,
],
});
```
View Extra Storybook **Examples **
# Click to Copy Guide (/docs/guides/click-to-copy)
Shadcn Table has an easy-to-implement feature that allows a user to copy a cell's value to the clipboard.
### Relevant Table Options [#relevant-table-options]
### Relevant Column Options [#relevant-column-options]
### Enable Click to Copy Per Column [#enable-click-to-copy-per-column]
Most likely, there will just be a couple columns that you want to enable click to copy for. You can do this by setting the `enableClickToCopy` option to `true` per column on the column definition.
```tsx
const columns = [
//...
{
accessorKey: 'email',
header: 'Email',
enableClickToCopy: true,
},
//...
];
```
### Enable Click to Copy For All Cells [#enable-click-to-copy-for-all-cells]
Alternatively, you can enable click to copy globally by setting the `enableClickToCopy` table option to `true`. You could then opt out per column by setting the `enableClickToCopy` option to `false` on the column definition.
```tsx
const table = useShadcnTable({
columns,
data,
enableClickToCopy: true,
});
```
### Customize Copy Buttons [#customize-copy-buttons]
The click to copy feature is built on top of the shadcn/ui Button component. You can customize the copy button by passing in the `stCopyButtonProps` table or column option.
```tsx
const table = useShadcnTable({
columns,
data,
enableClickToCopy: true,
stCopyButtonProps: {
className: 'w-full justify-start',
},
});
```
### Click to Copy Example [#click-to-copy-example]
View Extra Storybook **Examples **
# Column Actions Guide (/docs/guides/column-actions)
By default, Shadcn Table renders a column actions button for each column header. It contains a drop-down menu to help your users use the other features of the table. All of these actions can be triggered in some other way other than from this drop-down menu, so this serves as a UI/UX alternative to make sure your users can find many of the table features easily.
### Relevant Table Options [#relevant-table-options]
### Relevant Column Options [#relevant-column-options]
### Disable or Hide Column Actions Buttons [#disable-or-hide-column-actions-buttons]
You can set the `enableColumnActions` table option to `false` in the table to disable this feature and hide the button in each column header completely.
```jsx
const table = useShadcnTable({
data,
columns,
enableColumnActions: false,
});
return ;
```
Alternatively, if you only want to hide the column actions button in specific columns, you can set the `enableColumnActions` option for the desired column definition to `false` instead.
In this demo, we disable the column actions button for the 'ID' column.
### Custom Column Actions Menu [#custom-column-actions-menu]
If you do not like the default column actions menu items that Shadcn Table generates, you can provide your own custom menu items with the `renderColumnActionsMenuItems` table or column option. You can choose whether or not to include the internal menu items by using the `internalColumnMenuItems` parameter.
```jsx
const table = useShadcnTable({
data,
columns,
renderColumnActionsMenuItems: ({ internalColumnMenuItems }) => {
return [
...internalColumnMenuItems, //optionally include the internal menu items above or below your custom menu items
Custom Menu Item 1 ,
Custom Menu Item 2 ,
];
},
});
return ;
```
### Justify Column Actions Button [#justify-column-actions-button]
By default, the column actions button is left aligned directly after the column header text and any sort or filter labels that may be present. If you want to change this, you can use a CSS selector in `stTableHeadCellProps` to change the `justify-content` property of the column header container.
View Extra Storybook **Examples **
# Column Filtering Guide (/docs/guides/column-filtering)
Filtering is one of the most powerful features of Shadcn Table and is enabled by default. There is a lot of flexibility and customization available here. Whether you want to customize the powerful client-side filtering already built in or implement your own [server-side filtering](#manual-server-side-column-filtering), Shadcn Table has got you covered.
### Relevant Table Options [#relevant-table-options]
### Relevant Column Options [#relevant-column-options]
### Relevant State Options [#relevant-state-options]
### Disable Filtering Features [#disable-filtering-features]
Various subsets of filtering features can be disabled. If you want to disable filtering completely, you can set the `enableColumnFilters` table option to `false` to remove all filters from each column. Alternatively, `enableColumnFilter` can be set to `false` for individual columns.
`enableFilters` can be set to `false` to disable both column filters and the global search filter.
### Filter Variants [#filter-variants]
Shadcn Table has several built-in filter variants for advanced filtering. These can be specified on a per-column basis using the `filterVariant` option. The following variants are available:
- `'text'` - shows the default text field
- `'autocomplete'` - shows an autocomplete text field with the options from faceted values or specified in `filterSelectOptions` array.
- `'select'` - shows a select dropdown with the options from faceted values or specified in `filterSelectOptions` array.
- `'multi-select'` - shows a select dropdown with the options from faceted values or specified in `filterSelectOptions` and allows multiple selections with checkboxes
- `'range'` - shows min and max text fields for filtering a range of values
- `'range-slider'` - shows a slider for filtering a range of values
- `'date'` - shows a date picker for filtering by date values
- `'datetime'` - shows a date and time picker for filtering by date and time values
- `'date-range'` - shows a date range picker for filtering by date ranges
- `'datetime-range'` - shows a date and time range picker for filtering by date and time ranges
- `'time'` - shows a time picker for filtering by time values
- `'time-range'` - shows a time range picker for filtering by time ranges
- `'checkbox'` - shows a checkbox for filtering by `'true'` or `'false'` values
#### Faceted Values for Filter Variants [#faceted-values-for-filter-variants]
Faceted values are a list of unique values for a column that gets generated under the hood from table data when the `enableFacetedValues` table option is set to `true`. These values can be used to populate the select dropdowns for the `'select'` and `'multi-select'` filter variants, or the min and max values for the `'range-slider'` variant. This means that you no longer need to manually specify the `filterSelectOptions` table option for these variants manually, especially if you are using client-side filtering.
#### Custom Faceted Values [#custom-faceted-values]
If you are using server-side pagination and filtering, you can still customize the faceted values output with the `getFacetedUniqueValues` and `getFacetedMinMaxValues` props.
```jsx
const table = useShadcnTable({
columns,
data,
enableFacetedValues: true,
//if using server-side pagination and filtering
getFacetedMinMaxValues: (table) => {
//fetch min and max values from server
return [minValue, maxValue];
},
//if using server-side filtering
getFacetedUniqueValues: (table) => {
const uniqueValueMap = new Map();
//fetch unique values from server, ideally including the count of each unique value
return uniqueValueMap;
},
})
```
### Column Filter Display Modes [#column-filter-display-modes]
By default, column filters inputs show below the column header. You can switch to a more "excel-like" UI by setting the `columnFilterDisplayMode` table option to `'popover'`. This will show a filter icon in the column header that can be clicked to open a popover with the filter input.
```jsx
const table = useShadcnTable({
columns,
data,
columnFilterDisplayMode: 'popover', //filter inputs will show in a popover (like excel)
});
```
Alternatively, if you want to render your own column filter UI in a separate sidebar, but still want to use the built-in filtering functionality, you can set the `columnFilterDisplayMode` table option to `'custom'`.
```jsx
const table = useShadcnTable({
columns,
data,
columnFilterDisplayMode: 'custom', //render your own column filter UI (e.g. in a sidebar)
});
```
### Custom Filter Functions [#custom-filter-functions]
You can specify either a pre-built filterFn that comes with Shadcn Table or pass in your own custom filter functions.
#### Custom Filter Functions Per Column [#custom-filter-functions-per-column]
By default, Shadcn Table uses a `fuzzy` filtering algorithm based on the popular `match-sorter` [library from Kent C. Dodds](https://www.npmjs.com/package/match-sorter). However, Shadcn Table also comes with numerous other filter functions that you can specify per column in the `filterFn` column options.
##### Pre-built ST Filter Functions [#pre-built-st-filter-functions]
> Pre-built filter functions from Shadcn Table include `between`, `betweenInclusive`, `contains`, `empty`, `endsWith`, `equals`, `fuzzy`, `greaterThan`, `greaterThanOrEqualTo`, `lessThan`, `lessThanOrEqualTo`, `notEmpty`, `notEquals`, and `startsWith`. View these algorithms [here](https://github.com/anindosarker/shadcn-table/blob/master/packages/shadcn-table/src/components/ui/shadcn-table/fns/filterFns.ts)
##### Pre-built TanStack Table Filter Functions [#pre-built-tanstack-table-filter-functions]
> Pre-built filter functions from TanStack Table include `includesString`, `includesStringSensitive`, `equalsString`, `equalsStringSensitive`, `arrIncludes`, `arrIncludesAll`, `arrIncludesSome`, `weakEquals`, and `inNumberRange`. View more information about these algorithms in the [TanStack Table Filter docs](https://tanstack.com/table/latest/docs/framework/react/guide/column-filtering#filterfns).
You can specify either a pre-built filter function, from Shadcn Table or TanStack Table, or you can even specify _your own custom filter function_ in the `filterFn` column option.
```jsx
const columns = [
{
accessorKey: 'firstName',
header: 'First Name',
// using a prebuilt filter function from Shadcn Table
filterFn: 'startsWith',
},
{
accessorKey: 'middleName',
header: 'Middle Name',
// using a prebuilt filter function from TanStack Table
filterFn: 'includesStringSensitive',
},
{
accessorKey: 'lastName',
header: 'Last Name',
// custom filter function
filterFn: (row, id, filterValue) =>
row.getValue(id).startsWith(filterValue),
},
];
```
If you provide a custom filter function, it must have the following signature:
```ts
(row: Row, id: string, filterValue: string | number) => boolean;
```
This function will be used to filter 1 row at a time and should return a `boolean` indicating whether or not that row passes the filter.
#### Add Custom Filter Functions [#add-custom-filter-functions]
You can add custom filter functions to the `filterFns` table option. These will be available to all columns to use. The `filterFn` table option on a column will override any filter function with the same name in the `filterFns` table option.
```jsx
const columns = [
{
accessorKey: 'name',
header: 'Name',
filterFn: 'customFilterFn',
},
];
const table = useShadcnTable({
columns,
data,
filterFns: {
customFilterFn: (row, id, filterValue) => {
return row.customField === value;
},
},
});
```
### Filter Modes [#filter-modes]
#### Enable Column Filter Modes (Filter Switching) [#enable-column-filter-modes-filter-switching]
If you want to let the user switch between multiple different filter modes from a drop-down menu on the Filter Textfield, you can enable that with the `enableColumnFilterModes` table option or column option. This will enable the filter icon in the filter text field to open a drop-down menu with the available filter modes when clicked.
```tsx
const table = useShadcnTable({
columns,
data,
enableColumnFilterModes: true,
});
```
#### Customize Filter Modes [#customize-filter-modes]
You can narrow down the available filter mode options by setting the `columnFilterModeOptions` table option or a column specific `columnFilterModeOptions` option.
```tsx
const columns = [
{
accessorKey: 'firstName',
header: 'First Name',
columnFilterModeOptions: ['fuzzy', 'contains', 'startsWith'],
},
{
accessorKey: 'age',
header: 'Age',
columnFilterModeOptions: ['between', 'lessThan', 'greaterThan'],
},
];
```
#### Render Custom Filter Mode Menu [#render-custom-filter-mode-menu]
You can also render custom menu items in the filter mode drop-down menu by setting the `renderColumnFilterModeMenuItems` table option or column option. This option is a function that takes in the column and returns an array of `MenuItem` components. This is useful if you want to add custom filter modes that are not included in Shadcn Table, or if you just want to render the menu in your own custom way
```jsx
const columns = [
{
accessorKey: 'firstName',
header: 'First Name',
renderColumnFilterModeMenuItems: ({ column, onSelectFilterMode }) => [
onSelectFilterMode('startsWith')}
>
Start With
,
onSelectFilterMode('yourCustomFilterFn')}
>
Your Custom Filter Fn
,
],
},
];
const table = useShadcnTable({
columns,
data,
enableColumnFilterModes: true,
// renderColumnFilterModeMenuItems could go here if you want to apply to all columns
});
return ;
```
### Expanded Leaf Row Filtering Options [#expanded-leaf-row-filtering-options]
If you are using the filtering features along-side either the [grouping](/docs/guides/aggregation-and-grouping) or [expanding](/docs/guides/expanding-sub-rows) features, then there are a few behaviors and customizations you should be aware of.
Check out the [Expanded Leaf Row Filtering Behavior docs](/docs/guides/expanding-sub-rows#expanded-leaf-row-filtering-behavior) to learn more about the `filterFromLeafRows` and `maxLeafRowFilterDepth` props.
### Manual Server-Side Column Filtering [#manual-server-side-column-filtering]
A very common use case when you have a lot of data is to filter the data on the server, instead of client-side. In this case, you will want to set the `manualFiltering` table option to `true` and manage the `columnFilters` state yourself like so (can work in conjunction with [manual global filtering](/docs/guides/global-filtering#manual-server-side-global-filtering)).
```jsx
// You can manage and have control over the columnFilters state yourself
const [columnFilters, setColumnFilters] = useState([]);
const [data, setData] = useState([]); //data will get updated after re-fetching
useEffect(() => {
const fetchData = async () => {
// send api requests when columnFilters state changes
const filteredData = await fetch();
setData([...filteredData]);
};
}, [columnFilters]);
const table = useShadcnTable({
columns,
data, // this will already be filtered on the server
manualFiltering: true, //turn off client-side filtering
onColumnFiltersChange: setColumnFilters, //hoist internal columnFilters state to your state
state: { columnFilters }, //pass in your own managed columnFilters state
});
return ;
```
> Specifying `manualFiltering` turns off all client-side filtering and assumes that the `data` you pass to ` ` is already filtered.
See either the [React Query](/docs/examples/react-query) or [useEffect fetching](/docs/examples/remote) examples to see fully server-side **filtering**, pagination, and sorting in action.
### Customize shadcn/ui Filter components [#customize-shadcnui-filter-components]
You can customize the shadcn/ui filter components by setting the `stFilterTextFieldProps` table option or column option.
You can also turn a filter textfield into a select dropdown by setting the `filterSelectOptions` table option or column option.
### Custom Filter Components [#custom-filter-components]
If you need custom filter components that are much more complex than text-boxes and dropdowns, you can create and pass in your own filter components using the `Filter` column option.
### Filter Match Highlighting [#filter-match-highlighting]
Filter Match Highlighting is a new featured enabled by default that will highlight text in the table body cells that matches the current filter query with a shade of the `theme.palette.warning.main` color.
Filter Match Highlighting will only work on columns with the default `text` filter variant. Also, if you are using a custom `Cell` render override for a column, you will need to use the `renderedCellValue` table option instead of `cell.getValue()` in order to preserve the filter match highlighting.
```jsx
const columns = [
{
accessorKey: 'name',
header: 'Name',
Cell: ({ renderedCellValue }) => {renderedCellValue} , // use renderedCellValue instead of cell.getValue()
},
];
```
#### Disable Filter Match Highlighting [#disable-filter-match-highlighting]
This feature can be disabled by setting the `enableFilterMatchHighlighting` table option to `false`.
```jsx
const table = useShadcnTable({
columns,
data,
enableFilterMatchHighlighting: false,
});
```
View Extra Storybook **Examples **
# Column Grouping Guide (/docs/guides/column-grouping)
Shadcn Table has built-in column grouping features. There are options for both automatic client-side grouping as well as manual server-side grouping. This guide will walk you through the different options and how to use and customize them.
### Relevant Table Options [#relevant-table-options]
### Relevant Column Options [#relevant-column-options]
### Relevant State [#relevant-state]
### Enable Grouping [#enable-grouping]
To enable grouping, set the `enableGrouping` table option to `true`. This will both add a drag handle button so that columns can be dragged to the dropzone to be grouped and will add an entry column actions menu to group or ungroup a column.
```jsx
const table = useShadcnTable({
columns,
data,
enableGrouping: true,
});
```
### Grouped Column Mode [#grouped-column-mode]
The `groupedColumnMode` table option controls how grouped columns are displayed once a column has been grouped. There are three options:
1. `"reorder"` (default) - Grouped columns will be displayed as the first columns in the table, followed by the 'st-row-expand' display column, followed by the remaining columns in their original order.
2. `"remove"` - Grouped columns will be removed from the table and only their aggregate values will be displayed alongside the expand button in the 'st-row-expand' display column, followed by the remaining columns in their original order.
3. `false` - Grouping columns will have no effect on the column order. The 'st-row-expand' display column will be displayed as the first column in the table, followed by the remaining columns in their original order.
```jsx
const table = useShadcnTable({
columns,
data,
enableGrouping: true,
groupedColumnMode: 'remove', //instead of default "reorder"
});
```
#### Disable Grouping Per Column [#disable-grouping-per-column]
```jsx
const columns = [
{
accessorKey: 'name',
header: 'Name',
enableGrouping: false, // disable grouping for this column
},
{
accessorKey: 'age',
header: 'Age',
},
];
const table = useShadcnTable({
columns,
data,
enableGrouping: true,
});
return ;
```
#### Hide Drag Buttons for Grouping [#hide-drag-buttons-for-grouping]
If you do not want the drag buttons that come with the grouping feature, you can independently disable them without disabling the grouping feature entirely by setting the `enableColumnDragging` table option to `false`.
```jsx
const table = useShadcnTable({
columns,
data,
enableGrouping: true,
enableColumnDragging: false, //do not show drag handle buttons, but still show grouping options in column actions menu
});
return ;
```
### Group Columns by Default [#group-columns-by-default]
If you want columns to be grouped by default, you can set the `grouping` state in either the `initialState` or `state` table option.
```jsx
const table = useShadcnTable({
columns,
data,
enableGrouping: true,
initialState: { grouping: ['location', 'department'] }, //group by location and department by default
});
return ;
```
### Expand Grouped Rows by Default [#expand-grouped-rows-by-default]
In addition to grouping columns by default, you may also want those grouped rows to be expanded and visible by default, too. You can do this by setting the `expanded` state to `true` in either the `initialState` or `state` table option.
```jsx
const table = useShadcnTable({
columns,
data,
enableGrouping: true,
initialState: {
grouping: ['location', 'department'], //group by location and department by default and expand grouped rows
expanded: true, //show grouped rows by default
},
});
return ;
```
### Customize Expand Column [#customize-expand-column]
You can customize the expand column by using the `displayColumnDefOptions` table option.
```jsx
const table = useShadcnTable({
columns,
data,
enableGrouping: true,
displayColumnDefOptions: {
'st-row-expand': {
size: 120, //make the expand column wider
},
},
});
```
Going further, you can completely overhaul how the expand column renders using custom `Cell` and `Header` renders here for advanced use cases.
```jsx
const table = useShadcnTable({
columns,
data,
enableGrouping: true,
displayColumnDefOptions: {
'st-row-expand': {
Cell: ({ row, table }) => {
return (
<>
{/*custom content*/}
>
);
},
Header: ({ table }) => {
return (
<>
{/*custom content*/}
>
);
},
},
},
});
```
### Manual Grouping [#manual-grouping]
Manual Grouping means that the `data` that you pass to the table is already grouped and aggregated, and you do not want Shadcn Table to do any of the grouping or aggregation for you. This is useful if you are using a backend API to do the grouping and aggregation for you, and you just want to display the results. However, you will need to put your data in the specific format that the `expanding` features understand.
> Learn more about [Expanding Sub Rows](/docs/guides/expanding-sub-rows) and [Aggregation](/docs/guides/aggregation) Features in their own dedicated guides.
# Column Hiding Guide (/docs/guides/column-hiding)
The column hiding feature is enabled by default and allows the user to hide data columns from either the column actions menu or the show/hide columns menu.
### Relevant Table Options [#relevant-table-options]
### Relevant Column Options [#relevant-column-options]
### Relevant State [#relevant-state]
### Hide Some Columns by Default [#hide-some-columns-by-default]
You can easily hide columns by default by setting the `columnVisibility` `state` or `initialState` to hide the desired columns by id.
```jsx
const table = useShadcnTable({
columns,
data,
initialState: { columnVisibility: { firstName: false } }, //hide firstName column by default
});
return ;
```
### Dynamically Hide Columns [#dynamically-hide-columns]
If you need to control how the columns are hidden dynamically, you can use `state` instead of `initialState` along with the `onColumnVisibilityChange` table option. Or alternatively you can use the `setColumnVisibility` table instance api.
```jsx
const [columnVisibility, setColumnVisibility] = useState({
firstName: false,
});
useEffect(() => {
setColumnVisibility({ firstName: true }); //programmatically show firstName column
}, [someDependency]);
const table = useShadcnTable({
columns,
data,
state: { columnVisibility }, //manage columnVisibility state
onColumnVisibilityChange: setColumnVisibility,
});
//NOTE: Instead of managing the columnVisibility state yourself, we could call a table instance api
table.setColumnVisibility({ firstName: true }); //programmatically show firstName column
```
### Disable Column Hiding [#disable-column-hiding]
If you do not want this feature to be enabled at all, you can disable it by setting the `enableHiding` table option to `false`.
```jsx
const table = useShadcnTable({
columns,
data,
enableHiding: false,
});
```
Alternatively, you can disable hiding specific columns by setting the `enableHiding` column option to `false` per column.
If you want to hide certain columns by default, you can specify column visibility in the `initialState.columnVisibility` table option.
### Enable Column Hiding on Display Columns [#enable-column-hiding-on-display-columns]
By default, column hiding is only enabled on data columns. Display columns, such as `st-row-numbers`, `st-row-select`, etc., do not have column hiding enabled, and their toggle will be disabled. You can turn that back on by setting the `enableHiding` option to `true` in the `displayColumnsOptions` table option.
```jsx
const table = useShadcnTable({
columns,
data,
displayColumnDefOptions: {
'st-row-numbers': {
enableHiding: true, //now row numbers are hidable too
},
},
});
```
See the [Display Columns Feature Guide](/docs/guides/display-columns#display-column-definition-options-prop) for a more in depth explanation of the `displayColumnsOptions` table option.
### Hide Column From Show Hide Menu [#hide-column-from-show-hide-menu]
By default, all columns are visible in the column show hide menu that is opened from the columns button in the toolbar internal actions button. You can hide a column from this menu by setting the `visibleInShowHideMenu` column option to `false`.
```jsx
const columns = [
{
accessorKey: 'uuid',
header: 'UUID',
visibleInShowHideMenu: false, //hide this column from the show hide menu, but still show the column in the table
},
];
const table = useShadcnTable({
columns,
data,
displayColumnDefOptions: {
'st-row-actions': {
visibleInShowHideMenu: false, //hide the built-in row actions column from the show hide menu
},
},
});
```
### Custom Columns Menu [#custom-columns-menu]
The `ST_ShowHideColumnsMenu` component is one of the few ST components that is pretty opinionated and not easily customizable. Instead of trying to customize the menu with overrides, it might be easier for you to just build your own new button and menu from scratch using the Table and Column Instance APIs.
Adding your own Toolbar Buttons is covered in the [Toolbar Guide](/docs/guides/toolbar-customization#customize-built-in-internal-toolbar-button-area)
If all you want to do is customize the buttons above the columns menu, you can import and use the `ST_ShowHideColumnsMenuItems` component from shadcn table, which is a component that renders the columns in the list with the toggle switches, but then render your own buttons in the top or bottom of the menu itself.
View Extra Storybook **Examples **
# Column Ordering/DnD Guide (/docs/guides/column-ordering-dnd)
Whether you just want to change the default column order in your table or let columns be reordered by dragging and dropping, Shadcn Table has a simple API for this.
### Relevant Table Options [#relevant-table-options]
### Relevant Column Options [#relevant-column-options]
### Relevant State [#relevant-state]
### Change the Default Column Order [#change-the-default-column-order]
By Default, Shadcn Table will order the columns in the order they are defined in the `columns` table option. And Display Columns such as Actions, Selection, Expansion, etc., get added to either the beginning or the end of the table. You can customize all of this by defining your own `columnOrder` state and passing it either to the `initialState` or `state` table options.
The `columnOrder` state is an array of string column ids, that come from the ids or accessorKeys that you defined in your column definitions.
If you are enabling features that generate built-in ST display columns such as `enableRowSelection`, `enableRowNumbers`, `enableRowActions`, etc., you should include those column ids in your `columnOrder` state at the index you want them to appear in the table. If you do not include them, ST should automatically insert them at either the beginning or the end of the table just fine, but it might not be in the order you want.
See the [Display Columns Guide](/docs/guides/display-columns) for more information on what `"st-row-\*"` column ids are available.
```jsx
const table = useShadcnTable({
data,
columns,
enableRowSelection: true,
initialState: {
columnOrder: [
'name',
'email',
'phone',
'city',
'country',
'st-row-select', //move the built-in selection column to the end of the table
],
},
});
return ;
```
> **Note:** If the number of `columns` and the length of the `columnOrder` state do not match, ST will automatically re-generate the `columnOrder` state internally.
### Manage Column Order State [#manage-column-order-state]
If you need easier access to the `columnOrder` state, you can store the column order in your own state management, and then pass it back into the ST `state` table option and sync back up internal mutations with the `onColumnOrderChange` callback.
You should also should initialize the `columnOrder` state yourself for the best results. Though, if you do not initialize the `columnOrder` state, as Shadcn Table will generate a default column order for you based on the order of the columns passed in the `columns` option, so you only need to properly initialize the column order state if there is a problem with the default order.
```jsx
const columns = [
//...
];
//easy shortcut to initialize the columnOrder state as array of column ids
const [columnOrder, setColumnOrder] = useState(
['st-row-select', ...columns.map((c) => c.accessorKey)], //array of column ids (Initializing is optional)
);
const table = useShadcnTable({
data,
columns,
enableRowSelection: true,
state: {
columnOrder,
},
onColumnOrderChange: setColumnOrder,
});
return ;
```
### Enable Column Ordering with Drag and Drop [#enable-column-ordering-with-drag-and-drop]
Shadcn Table has a built-in drag and drop feature to reorder columns. This feature is enabled by passing the `enableColumnOrdering` table option.
The ability for a column to have a drag and drop handle can be specified by setting the `enableColumnOrdering` option on the column.
View Extra Storybook **Examples **
# Column Pinning Guide (/docs/guides/column-pinning)
Column pinning is a cool feature that lets users pin (freeze) columns to the left or right of the table. Pinned columns will not scroll horizontally with the rest of the columns so that they always stay visible to the user.
### Relevant Table Options [#relevant-table-options]
### Relevant Column Options [#relevant-column-options]
### Relevant State [#relevant-state]
### Enable Column Pinning [#enable-column-pinning]
Column pinning can simply be enabled by setting the `enableColumnPinning` table option to `true`.
```jsx
const table = useShadcnTable({
data,
columns,
enableColumnPinning: true,
});
return ;
```
### Pin (Freeze) Columns By Default [#pin-freeze-columns-by-default]
Columns can start out pinned in your table by setting the `columnPinning` states in `initialState` or `state`.
```jsx
const table = useShadcnTable({
data,
columns,
enableColumnPinning: true,
initialState: { columnPinning: { start: ['email'] } }, //pin email column to the start (left in LTR) by default
});
return ;
```
### Apply Absolute Column Widths [#apply-absolute-column-widths]
You might consider using the `layoutMode: 'grid-no-grow'` table option to give all columns an exact width if you don't want the columns collapsing a little while scrolling. Some people like this subtle behavior, but others do not.
```jsx
const table = useShadcnTable({
data,
columns,
enableColumnPinning: true,
layoutMode: 'grid-no-grow',
});
return ;
```
#### Column Pinning Example [#column-pinning-example]
View Extra Storybook **Examples **
# Column Resizing Guide (/docs/guides/column-resizing)
Shadcn Table has a built-in column resizing draggable handle feature.
> The Column Size features was recently split into its own [Guide](/docs/guides/column-size). View that guide as a prerequisite to this one.
### Relevant Table Options [#relevant-table-options]
### Relevant Column Options [#relevant-column-options]
### Relevant State [#relevant-state]
### Initial Column Sizes [#initial-column-sizes]
Column sizes will behave differently depending on which `layoutMode` you have set.
See the [Column Size Guide](/docs/guides/column-size) for more information on layout modes and how to set initial column sizes properly for you use case.
### Enable Column Resizing Feature [#enable-column-resizing-feature]
`enableColumnResizing` is the boolean table option that enables the column resizing feature.
```jsx
const table = useShadcnTable({
columns,
data,
enableColumnResizing: true,
});
return ;
```
You can disable specific columns from being resizable by setting the `enableResizing` column option to false in their respective column definition.
### Column Resize Mode [#column-resize-mode]
The default `columnResizeMode` is `onChange`, which means that the column resizing will occur immediately as the user drags the column resize handle. If you are running into performance issues because of many other enabled features, you might want to set the `columnResizeMode` to `onEnd` instead. This will make the column resizing only occur after the user has finished dragging the column resize handle and released their mouse.
```jsx
const table = useShadcnTable({
columns,
data,
enableColumnResizing: true,
columnResizeMode: 'onEnd', //instead of the default "onChange" mode
});
```
### Column Growing [#column-growing]
When column resizing is enabled, by default, a layoutMode of `"grid-no-grow"` will be applied internally. This means that columns will have an absolute size and they will NOT grow to fill in the remaining space of the table. You can let columns grow to fill in the remaining space by changing the `layoutMode` back to `"grid"` or `"semantic"`.
```jsx
const table = useShadcnTable({
columns,
data,
enableColumnResizing: true,
layoutMode: 'grid', //instead of the default "grid-no-grow" when column resizing is enabled
});
```
Alternatively, if you only want certain columns to grow to fill the remaining space, you can set the `grow` column option to `true` in their respective column definitions.
```jsx
const columns = [
//...
{
accessorKey: 'address',
header: 'Address',
size: 250,
grow: true, //allow this column to grow to fill the remaining space
},
];
```
This is discussed in more detail in the [Column Size Guide](/docs/guides/column-size#column-grow).
### Column Resize Direction [#column-resize-direction]
If you are displaying your table in a RTL (right-to-left) language, you can set the `columnResizeDirection` table option to `"rtl"` to make the column resize handle appear on the left side of the column instead of the right side.
Shadcn Table does not read the document direction, so set this option explicitly when your table is rendered in an RTL layout.
```jsx
const table = useShadcnTable({
columns,
data,
enableColumnResizing: true,
columnResizeDirection: 'rtl', //instead of the default "ltr" direction
});
return (
{' '}
{/* app-wide style? */}
);
```
### Enable Resizing on Built-in Display Columns [#enable-resizing-on-built-in-display-columns]
As discussed further in the [Display Columns Guide](/docs/guides/display-columns), you can customize the options of the built-in columns that get generated under the hood by ST by enabling certain features.
Here, we can enable column resizing on the built-in row numbers column by setting the `enableResizing` column option to true in the `displayColumnDefOptions` table option.
```jsx
const table = useShadcnTable({
columns,
data,
displayColumnDefOptions: {
'st-row-numbers': {
enableResizing: true, //allow the row numbers column to be resized
size: 40,
grow: false, //do not fill remaining space using this column
},
},
enableRowNumbers: true,
layoutMode: 'grid', // `grow` only works in the grid* layout modes
});
return ;
```
View Extra Storybook **Examples **
# Column Size Guide (/docs/guides/column-size)
Shadcn Table lets you easily change the default widths (sizes) of columns.
### Relevant Table Options [#relevant-table-options]
### Relevant Column Options [#relevant-column-options]
### Relevant State [#relevant-state]
### Layout Modes [#layout-modes]
Shadcn Table has 3 layout modes that affect how columns styles are applied internally. Depending on which features you enable, the `layoutMode` table option will automatically change to the appropriate value, though you can override it with your own value if you want.
1. `"semantic"` (default with default features) - uses default css styles that come with ``, ``, ``, etc. elements.
2. `"grid"` (default when virtualization is enabled) - uses CSS Grid and Flexbox styles instead of default styles.
3. `"grid-no-grow"` (default when column resizing is enabled) - uses CSS Grid and Flexbox styles, but also sets `flex-grow: 0` on all columns and adds an empty "spacer" column to the end of the table to fill the potential remaining space.
If you want your columns to have an absolute width, you can use the `"grid-no-grow"` layout mode and set the `size` option on each column.
### Column Size [#column-size]
You can change the width of any column by setting its `size` option on the column definition. There are `minSize` and `maxSize` column options available to enforce limits during resizing events.
The `size`, `minSize`, and `maxSize` do not take in any CSS width unit. They are only numbers that represent the width of the column in pixels.
```jsx
const columns = [
{
accessorKey: 'id',
header: 'ID',
grow: false, //don't allow this column to grow to fill in remaining space
size: 50, //small column
},
{
accessorKey: 'username',
header: 'Username',
minSize: 100, //min size enforced during resizing
maxSize: 400, //max size enforced during resizing
size: 180, //medium column
},
{
accessorKey: 'email',
header: 'Email',
grow: true, //allow this column to grow to fill in remaining space
size: 300, //large column
},
];
```
### Column Grow [#column-grow]
You can also set `grow` properties on the column definitions when using layoutMode `"grid-no-grow"` or `"grid"`.
If the layout mode of your table is `"grid"` and you set `grow: false` on a column, that column will have a fixed size and will not grow to fill in the remaining space of the table.
If the layout mode of your table is `"grid-no-grow"` and you set `grow: true` on a column, that column will grow to fill in the remaining space of the table.
If the layout mode of your table is `"grid-no-grow"` and you set `grow: 1` on one column, and `grow: 2` on another column, the first column will grow to fill in 1/3 of the remaining space of the table, and the second column will grow to fill in 2/3 of the remaining space of the table.
If the layout mode of your table is `"semantic"`, the `grow` column option will have no effect, but columns will size themselves to fit their own content better than the other layout modes.
### But I Want My Columns to Have a Percentage Width [#but-i-want-my-columns-to-have-a-percentage-width]
Shadcn Table is not designed to use percentage column widths well by default. This is because having horizontal scrollbars on tables is both a very common use case, and usually the best way to ensure that the actual content of the cells in each column is visible and not cutoff or wrapped excessively.
If you disagree with that, that's fair, and you're not out of luck. Shadcn Table exposes all `st*Props`, so if you really want to, you can add whatever CSS you want to achieve your own custom column widths behavior. This is not recommended by the maintainer, but some developers have found it useful to set all column sizes and minSizes to a very small number like `1`, `1.5`, `2`, ect., and then use either `layoutMode: "semantic"` or `layoutMode: "grid"`. This makes the columns act more like they are using percentage widths, up until a certain point. If you really need to never have a horizontal scrollbar, you can edit the `stTableContainerProps` `className` or `style` to set `overflow-x: hidden`. Be very careful with this though, as this could cause your table to be unusable on smaller screens.
If the above still does not get you to where you need to be, `stTableHeadCellProps`, `stTableBodyCellProps`, and `stTableFooterCellProps` (`className` or `style`) and more are all exposed and available to you to override with whatever custom CSS that you need. Just be aware that non of the built-in TanStack Table size APIs will be accurate anymore.
#### Side Note: Just Make Your Scrollbars Prettier [#side-note-just-make-your-scrollbars-prettier]
Making your scrollbars look better (especially on Windows and Linux) can go a long way to not letting a horizontal scrollbar ruin the look of your table.
Here's what this docs site uses in its global CSS to make scrollbars to more like the default Mac OS scrollbars for everyone:
```css
::-webkit-scrollbar-track {
background-color: transparent;
}
::-webkit-scrollbar {
right: 0;
width: 12px;
height: 12px;
}
::-webkit-scrollbar-thumb {
background-color: #999;
border-radius: 8px;
width: 12px;
height: 12px;
}
```
### Default Column [#default-column]
By default, columns will have the following size properties defined:
```jsx
defaultColumn = { minSize: 40, maxSize: 1000, size: 180 }; //units are in px
```
You can modify the default column widths by setting the `defaultColumn` table option on the table.
```jsx
const table = useShadcnTable({
columns,
data,
defaultColumn: {
minSize: 20, //allow columns to get smaller than default
maxSize: 9001, //allow columns to get larger than default
size: 260, //make columns wider by default
},
});
```
### Change Sizes of Built-in Display Columns [#change-sizes-of-built-in-display-columns]
As discussed further in the [Display Columns Guide](/docs/guides/display-columns), you can customize the options of the built-in columns that get generated under the hood by ST by enabling certain features.
```jsx
const table = useShadcnTable({
columns,
data,
displayColumnDefOptions: {
'st-row-select': {
size: 50, //adjust the size of the row select column
grow: false, //default is false for this column
},
'st-row-numbers': {
size: 40,
grow: true, //allow this column to grow to fill in remaining space
},
},
enableRowNumbers: true,
});
return ;
```
View Extra Storybook **Examples **
# Customize (Style) Components Guide (/docs/guides/customize-components)
One of the strengths of Shadcn Table is that it exposes a majority of all the underlying shadcn/ui component props used to build the table.
Additionally, in one of the sections below, you will learn [how to theme your table](#theming) to customize colors, typography, or any other default CSS that is used by Shadcn Table.
### Relevant Table Options [#relevant-table-options]
All props labeled `st...Props` get forwarded to shadcn/ui components. Here is a list of all the props that are exposed in both the root props and column options.
### Relevant Column Options [#relevant-column-options]
Some of the column options expose the same props as above, but on a per column basis.
### Props and Types [#props-and-types]
Each prop can either be passed as an object or as a callback function where you get access to the underlying `table` instance and any other relevant callback parameters, such as `cell`, `row`, `column`, etc. This lets you easily run conditional logic on the props you pass. Let's take a look at a few examples.
> All `st...Props` props are strongly typed and you should get ts hints as you write them. Each one forwards `className`, `style` and the props of the element or [shadcn/ui component](https://ui.shadcn.com/docs/components) it targets.
#### Static Prop Objects [#static-prop-objects]
```jsx
const table = useShadcnTable({
columns,
data,
enableRowSelection: true,
//passing the static object variant if no dynamic logic is needed
stSelectCheckboxProps: {
className: 'rounded-full', //makes all checkboxes round
},
});
return ;
```
#### Callback Functions to Dynamically Set Prop Values [#callback-functions-to-dynamically-set-prop-values]
```jsx
const table = useShadcnTable({
columns,
data,
enableRowSelection: true,
//passing the callback function variant. (You should get type hints for all the callback parameters available)
stSelectCheckboxProps: ({ row }) => ({
className: 'rounded-full',
disabled: row.original.isAccountLocked, //access the row data to determine if the checkbox should be disabled
}),
});
return ;
```
### Styling Components [#styling-components]
Each `st...Props` prop accepts `className` and `style`, which are forwarded to the underlying element. Since Shadcn Table is built with Tailwind CSS, the recommended way to style components is with Tailwind classes in `className`. They are merged with the default classes, and they can use the shadcn/ui CSS variables from your theme.
1. The `style` prop works just like a style prop in any React component.
```jsx
const table = useShadcnTable({
columns,
data,
stTableHeadCellProps: {
//simple styling with the `style` prop
style: {
fontWeight: 'normal',
fontSize: '14px',
},
},
});
return ;
```
2. Tailwind classes in `className` get easy access to your theme colors, no hook needed.
```jsx
const table = useShadcnTable({
columns,
data,
stTableHeadCellProps: {
//shadcn/ui theme colors are available as Tailwind classes
className: 'text-muted-foreground',
},
});
return ;
```
3. Tailwind responsive variants give you a concise way to add media queries to your styling.
```jsx
const table = useShadcnTable({
columns,
data,
stTableHeadCellProps: {
//responsive variants instead of media queries
className:
'text-[10px] sm:text-[11px] md:text-[12px] lg:text-[13px] xl:text-[14px]',
},
});
return ;
```
4. Tailwind arbitrary variants let you target nested elements and states.
```jsx
const table = useShadcnTable({
columns,
data,
stTableHeadCellProps: {
//target nested elements (the buttons in the header cell) and the hover state
className: '[&_button]:p-0 hover:font-bold',
},
});
```
You can learn more about utility classes in the official [Tailwind CSS Docs](https://tailwindcss.com/docs/styling-with-utility-classes) and about the theme variables in the [shadcn/ui Theming Docs](https://ui.shadcn.com/docs/theming).
### Conditional Styling [#conditional-styling]
Using the `style` or `className` props and the parameters from the callback functions (cell, column, row, table instance APIs, etc.), you can easily add conditional styling to your shadcn/ui components. Here's a few examples
#### Style Selected Rows [#style-selected-rows]
```jsx
const table = useShadcnTable({
stTableBodyRowProps: ({ row }) => ({
//conditionally style selected rows
className: row.getIsSelected() ? 'font-bold' : 'font-normal',
}),
});
```
#### Style Expanded Rows [#style-expanded-rows]
```jsx
const table = useShadcnTable({
stTableBodyRowProps: ({ row }) => ({
//conditionally style expanded rows
className: row.getIsExpanded() ? 'font-bold' : 'font-normal',
}),
});
```
#### Style Pinned Columns [#style-pinned-columns]
```jsx
const table = useShadcnTable({
stTableHeadCellProps: ({ column }) => ({
//conditionally style pinned columns
className: column.getIsPinned() ? 'bg-muted' : undefined,
}),
stTableBodyCellProps: ({ column }) => ({
//conditionally style pinned columns
className: column.getIsPinned() ? 'font-bold' : 'font-normal',
}),
});
```
There are hundreds of table, column, header, row, and cell instance APIs that you can use for conditional styling and other logic. See the [Table Instance APIs](/docs/api/table-instance-apis), [Column Instance APIs](/docs/api/column-instance-apis), [Row Instance APIs](/docs/api/row-instance-apis), and [Cell Instance APIs](/docs/api/cell-instance-apis) docs for more info.
### Theming [#theming]
Shadcn Table respects your shadcn/ui theme. If you have already set up shadcn/ui with `shadcn init`, you should already be set. The table reads the CSS variables from your global stylesheet (`--background`, `--foreground`, `--primary`, `--border`, `--muted`, etc.), so changing them changes the table along with the rest of your app. If you have not set that up, you should visit the official [shadcn/ui Theming Docs](https://ui.shadcn.com/docs/theming) to learn how.
```css
/* app/globals.css: have you set up your theme variables globally in your app root? */
:root {
--primary: oklch(0.45 0.2 280);
--border: oklch(0.9 0 0);
}
.dark {
--primary: oklch(0.7 0.18 280);
--border: oklch(0.3 0 0);
}
```
#### Customize Theme Just for your Table [#customize-theme-just-for-your-table]
Because the theme is CSS variables, you can change it just for the ` ` component by redefining the variables on an element around just your table (or on the table root through the `stTableLayoutProps` table option). The values only effect the ` ` component, and not the rest of your site. Everything you do not redefine is inherited from your global theme, so you do not have to redefine everything again. You can just tweak the values you want to change.
```jsx
//in one of your normal components
return (
);
```
#### Custom Theme Example [#custom-theme-example]
### Table Theme Values [#table-theme-values]
The colors Shadcn Table uses internally come from the shadcn/ui theme variables, so there is no separate theme object to pass in. Here is where the default values come from, if you want to override them:
- the table and toolbars use `--background` and `--foreground`
- selected rows use `--primary` at 20% opacity, pinned rows at 10%, hovered rows use `--foreground` at 10%
- dragging borders use `--primary`
- filter match highlighting uses `--destructive` at 20% opacity (30% in dark mode), on the element with `data-slot="st-match-highlight"`
### Customize Table Layout Styling [#customize-table-layout-styling]
You can customize both the props and the styles of the internal ` ` component that wraps the table by passing in a `stTableLayoutProps` table option. This is useful if you want to remove the shadow, or change the border radius, or any other styling you want to do to the wrapper.
```jsx
const table = useShadcnTable({
columns,
data,
stTableLayoutProps: {
//customize wrapper styles
className: 'rounded-none border-dashed shadow-none',
},
});
return ;
```
### Customize Table Body, Rows, Columns, and Cells [#customize-table-body-rows-columns-and-cells]
#### Stripe Rows Example [#stripe-rows-example]
If you need to style many of the rows or columns in a consistent manner, it usually makes sense to style the ` ` component itself. For example if you want to stripe the rows, you can do something like this:
```jsx
const table = useShadcnTable({
columns,
data,
stTableBodyProps: {
//stripe the rows, make odd rows a darker color
className: '[&_tr:nth-of-type(odd)>td]:bg-muted',
},
});
return ;
```
#### Stripe Columns Example [#stripe-columns-example]
Similarly, if you want to stripe the columns, you can do something like this:
```jsx
const table = useShadcnTable({
columns,
data,
stTableBodyProps: {
//stripe the columns, make odd columns a darker color
className: '[&_td:nth-of-type(odd)]:bg-muted',
},
stTableBodyCellProps: {
className: 'border-r-2', //add a border between columns
},
});
```
### Customize No Rows overlay [#customize-no-rows-overlay]
As part of the customization options, we allow you to override the No Records overlay.
```jsx
const table = useShadcnTable({
columns,
data,
renderEmptyRowsFallback: ({ table }) => (
Customized No Rows Overlay
),
});
```
# Custom Icons Guide (/docs/guides/customize-icons)
Shadcn Table uses [Lucide icons](https://lucide.dev) by default for all of its internal icons.
If you need to, you can customize and replace some or all of the icons with your own custom icons. You can either use other Lucide icons or icons from a completely different library.
### Relevant Table Options [#relevant-table-options]
### Replace with Custom Icons [#replace-with-custom-icons]
To replace a default icon, specify the icon in the `icons` table option. You should get TS hints for the name of the icons you can replace, but you can also consult [this source file](https://github.com/anindosarker/shadcn-table/blob/master/packages/shadcn-table/src/components/ui/shadcn-table/icons.ts) for a list of all the icons you can replace.
```jsx
,
SearchIcon: () => ,
SortIcon: (props) => ( ), //best practice
}}
```
Some icons have `style` props that get applied to them internally. So, in order to preserve the ability of those Icons to be styled with the correct paddings, margins, or rotations, you should pass in `{...props}` to your custom icon component as a best practice.
### Font Awesome Example [#font-awesome-example]
Here is an example where we use icons from a completely different library, [Font Awesome](https://fontawesome.com/).
# Data (Accessor) Columns (/docs/guides/data-columns)
Data columns are used to display data and are the default columns that are created when you create a column with an `accessorKey` or `accessorFn`.
The table can perform processing on the data of a data column, such as sorting, filtering, grouping, etc.
The other type of column that you can make is a display column, which you can learn more about in the [next section](/docs/guides/display-columns).
### Accessors (Connect a column to data) [#accessors-connect-a-column-to-data]
Each column definition must have at least an `accessorKey` (or a combination of an `id` and `accessorFn`) and a `header` property. The `accessorKey`/`accessorFn` property is the key that will be used to join the data from the `data` keys. The `header` property is used to display the column header, but is also used in other places in the table.
> Note: Do NOT have your accessors resolve JSX or markup. That's what custom [`Cell`](#custom-cell-render) renders are for. Accessors should only return primitive data so that the table can sort, filter, search, and group properly.
#### Method 1 - Using an accessorKey (Recommended) [#method-1---using-an-accessorkey-recommended]
The simplest and most common way to define a column is to use the `accessorKey` column option. The `accessorKey` column option is the key that will be used to join the data from the `data` keys.
The `accessorKey` must match one of the keys in your data, or else no data will show up in the column. The `accessorKey` also supports dot notation, so you can access nested data.
By default, the `accessorKey` will double as the `id` for the column, but if you need the id of the column to be different than the accessorKey, you can use the `id` property in addition.
```tsx
const columns = useMemo[]>( //TS helps with the autocomplete while writing columns
() => [
{
accessorKey: 'username', //normal recommended usage of an accessorKey
header: 'Username',
},
{
accessorKey: 'name.firstName', //example of dot notation used to access nested data
header: 'First Name',
},
{
accessorKey: 'name.lastName', //example of dot notation used to access nested data
header: 'Last Name',
},
{
accessorKey: 'customerAge',
id: 'age' //id overridden, usually not necessary to do this, but can be helpful
header: 'Age',
},
],
[],
);
```
#### Method 2 - Using an accessorFn and id [#method-2---using-an-accessorfn-and-id]
You can alternatively use the `accessorFn` column option. Here are at least three ways you can use it.
In each case, the `id` property is now required since there is no `accessorKey` for ST to derive it from.
```tsx
const columns = useMemo[]>(
() => [
{
//simple accessorFn that works the same way as an `accessorKey`
accessorFn: (row) => row.username,
id: 'username',
header: 'Username',
},
{
//accessorFn function that combines multiple data together
accessorFn: (row) => `${row.firstName} ${row.lastName}`,
id: 'name',
header: 'Name',
},
{
//accessorFn used to access nested data, though you could just use dot notation in an accessorKey
accessorFn: (row) => row.personalInfo.age,
id: 'age',
header: 'Age',
},
],
[],
);
```
#### Method 3 - Using createSRTColumnHelper [#method-3---using-createsrtcolumnhelper]
Alternatively you can use the `createSRTColumnHelper` utility function to define your columns definitions in a slightly more type-safe way. Instantiate a `columnHelper` by passing in your `TData` type as a generic argument. Then the first argument of the `columnHelper.accessor()` method can be either an `accessorKey` or an `accessorFn`. Then you can specify the rest of the column options as the second argument.
```tsx
const columnHelper = createSRTColumnHelper(); //TS now knows the shape of your data
const columns = [
//accessorKey as first argument, rest of column options as second argument
columnHelper.accessor('name', {
//TS should provide autocomplete for valid accessorKeys
header: 'Last Name',
}),
//accessorFn as first argument, rest of column options as second argument
columnHelper.accessor((row) => Number(row.age), {
//TS should provide autocomplete for valid properties on your data
header: 'Age',
id: 'age', //id is required when using accessorFn
}),
];
```
### Custom Header Render [#custom-header-render]
If you want to pass in custom JSX to render the header, you can pass in a `Header` option in addition to the `header` string property.
> The `header` (lowercase) property is still required and still must only be a string because it is used within multiple components in the table and has string manipulation methods performed on it.
```tsx
const columns = useMemo(
() => [
{
accessorKey: 'name',
header: 'Name',
Header: ({ column }) => (
{column.columnDef.header} //re-use the header we already defined
), //arrow function
},
{
accessorKey: 'age',
header: 'Age',
Header: Age , //plain jsx with no function
},
],
[],
);
```
### Custom Cell Render [#custom-cell-render]
Similarly, the data cells in a column can have a custom JSX render with the `Cell` option. This is one of the most common features used in ST.
Using the `Cell` column option should be the only way that you use to render custom JSX in table cells. Do not put JSX in an accessorFn, or else the table will not be able to sort, filter, search, or group properly.
```tsx
const columns = useMemo(
() => [
{
accessorFn: (row) => `${row.firstName} ${row.lastName}`,
header: 'Name',
//Add a link in a cell render
Cell: ({ renderedCellValue, row }) => (
{renderedCellValue}
),
},
{
accessorKey: 'salary',
header: 'Salary',
//Format a number in a cell render
Cell: ({ cell }) => (
${cell.getValue().toLocaleString()}
),
},
{
accessorKey: 'profileImage',
header: 'Profile Image',
//Render images in a cell render
Cell: ({ cell }) => ()} />,
},
],
[],
);
```
### Custom Footer Render [#custom-footer-render]
If you want to pass in custom JSX to render the footer, you can pass in a `Footer` option. If no custom markup is needed, you can just use the `footer` string property.
The footer cells can be a good place to put totals or other summary information.
```tsx
const columns = useMemo(
() => [
{
accessorKey: 'name',
header: 'Name',
footer: 'Name', //simple string header
},
{
accessorKey: 'age',
header: 'Age',
//Custom footer markup for a aggregation calculation
Footer: () => (
Max Age:
{Math.round(maxAge)}
),
},
],
[],
);
```
> See the [Customize Components Guide](/docs/guides/customize-components) for more ways to style and customize header and cell components.
### Set Column Widths [#set-column-widths]
This topic is covered in detail in the [Column Size Guide](/docs/guides/column-size), but here is a brief overview.
Setting a CSS (`className` or `style`) width prop in the `stTableHeadCellProps`, `stTableBodyCellProps`, etc. might not work well, and is redundant. ST/TanStack Table has an official way to set column widths with the `size`, `minSize`, `maxSize`, and `grow` column options.
```tsx
const columns = [
{
accessorKey: 'id',
header: 'ID',
size: 50, //small column
grow: false, //don't allow this column to grow (if layoutMode is grid)
},
{
accessorKey: 'username',
header: 'Username',
minSize: 100, //min size enforced during resizing
maxSize: 200, //max size enforced during resizing
size: 180, //medium column
},
{
accessorKey: 'email',
header: 'Email',
size: 300, //large column
},
];
```
There is a lot of different behaviors for column widths depending on what other features are enabled or how they are configured. See the [Column Size Guide](/docs/guides/column-size) for more details on the `layoutMode`s, and how and why they are enabled and how they affect column widths.
### Set Column Alignment [#set-column-alignment]
By default, all columns are left-aligned. You can change the alignment of a column by setting the `align` option to either `"center"`, `"right"`, or `"justify"` in the `stTableHeadCellProps` and `stTableBodyCellProps` props/column options.
```tsx
const columns = [
{
accessorKey: 'id',
header: 'ID',
//right align the header, body, and footer cells each individually
stTableHeadCellProps: {
align: 'right',
},
stTableBodyCellProps: {
align: 'right',
},
stTableFooterCellProps: {
align: 'right',
},
},
{
accessorKey: 'username',
header: 'Username',
//center align the header, body, and footer cells each individually
stTableHeadCellProps: {
align: 'center',
},
stTableBodyCellProps: {
align: 'center',
},
stTableFooterCellProps: {
align: 'center',
},
},
];
```
### Enable or Disable Features Per Column [#enable-or-disable-features-per-column]
In the same way that you can pass props to the main ` ` component to enable or disable features, you can also specify options on the column definitions to enable or disable features on a per-column basis.
```tsx
const columns = useMemo(
() => [
{
accessorKey: 'salary',
header: 'Salary',
enableClickToCopy: true, //enable click to copy on this column
},
{
accessorKey: 'profileImage',
header: 'Profile Image',
enableSorting: false, //disable sorting on this column
},
],
[],
);
```
> See all the column options you can use in the [Column Options API Reference](/docs/api/column-options).
# Density Toggle Guide (/docs/guides/density-toggle)
Shadcn Table includes a density toggle button in the top toolbar by default that lets you toggle between three different density levels. This is a great feature to include to help with accessibility for different user preferences, but it can also easily be disabled if desired.
### Relevant Table Options [#relevant-table-options]
### Relevant State [#relevant-state]
### Default Density [#default-density]
By default, Shadcn Table will render with a medium `comfortable` density.
A density toggle is shown by default to let a user change the density to cycle through `spacious`, `comfortable`, and `compact` densities.
> When a `compact` density is set, whitespace is set to `nowrap` by default to keep the rows as short in height as possible. This can be overridden in the `stTableBodyCellProps` `style` or `className` prop.
#### Change Default Density [#change-default-density]
If you want to change the default density, you can set it in either the `initialState` table option or the `state` table option.
```tsx
const table = useShadcnTable({
data,
columns,
initialState: { density: 'compact' },
});
return ;
```
### Disable or Hide the Density Toggle [#disable-or-hide-the-density-toggle]
You can change the default density and disable the density toggle itself if you want.
In this example, the density toggle is disabled and a `compact` density is set by default in the `initialState` table option.
# Detail Panel Expanding Guide (/docs/guides/detail-panel)
Shadcn Table has multiple kinds of expanding features. This guide will show you how to use the detail panel feature to expand a single row to show more information for that row.
If you are looking for how to expand multiple rows from a tree data structure, see the [Expanding Sub-Rows](/docs/guides/expanding-sub-rows) guide.
### Relevant Table Options [#relevant-table-options]
### Relevant State [#relevant-state]
### Render Detail Panel [#render-detail-panel]
To add a detail panel to a row, all you need to do is add a `renderDetailPanel` table option.
The recommended way to access the row data for the detail panel is to pull from the `original` object on a row. This gives you the original data for the row, not transformed or filtered by TanStack Table.
> Using `row.getValue('columnId')` will not work for data that does not have its own column. Using `row.original.columnId` is recommended for detail panels since the data in the detail panel usually does not have its own column.
### Disable Expand All Button [#disable-expand-all-button]
If you don't want to show the expand all button, you can set the `enableExpandAll` table option to `false`.
```jsx
const table = useShadcnTable({
data,
columns,
enableExpandAll: false,
});
```
### Enable Detail Panel Conditionally Per Row [#enable-detail-panel-conditionally-per-row]
If the return value of your `renderDetailPanel` function returns `null` or a falsy value for a row, the expand button will be disabled for that row.
```jsx
const table = useShadcnTable({
columns,
data,
renderDetailPanel: ({ row }) =>
row.original.someCondition ? : null,
});
```
One thing to note about the implementation of conditional detail panels is that additional `` elements will still be created for all rows, even if they do not have detail panel content. It is implemented this way in order to avoid bugs with row virtualization, or striped row CSS.
### Only Allow One Detail Panel Open At A Time [#only-allow-one-detail-panel-open-at-a-time]
If you want to only allow one detail panel to be open at a time, all you have to do is add your own `onClick` logic to the `stExpandButtonProps` table option.
```jsx
const table = useShadcnTable({
data,
columns,
renderDetailPanel: ({ row }) => ,
stExpandButtonProps: ({ row, table }) => ({
onClick: () => table.setExpanded({ [row.id]: !row.getIsExpanded() }), //set only this row to be expanded
}),
});
```
### Rotate Expand Icon [#rotate-expand-icon]
If you don't like the default rotation styles for the expand icons, you can pass in custom CSS to the `stExpandButtonProps` and `stExpandAllButtonProps` table options.
### Replace Expand Icon [#replace-expand-icon]
You can easily use a custom expand icon either by following the [Custom Icons Guide](/docs/guides/custom-icons) or by passing in custom `children` to the `stExpandButtonProps` and `stExpandAllButtonProps` table options.
```jsx
const table = useShadcnTable({
data,
columns,
// icons, //or manage icons globally
stExpandButtonProps: ({ row }) => ({
children: row.getIsExpanded() ? : ,
}),
});
```
### Customize or Style Detail Panel [#customize-or-style-detail-panel]
You can use the `stDetailPanelProps` table option to pass in custom props to the detail panel. These props are passed to the `` element that contains the detail panel content.
If you need to customize the ` ` element containing the detail panel cell, you can just use the `stTableBodyRowProps` table option that you use for customizing all rows. There is a `isDetailPanel` parameter that is available to you to target only detail panel rows.
```jsx
const table = useShadcnTable({
data,
columns,
stDetailPanelProps: ({ row }) => ({
style: {
//...
},
}),
stTableBodyRowProps: ({ isDetailPanel, row }) => ({
style: {
// isDetailPanel ? ... : ...
},
}),
});
```
### Expand Detail Panel By Default [#expand-detail-panel-by-default]
If you want some or all rows to be expanded by default, you can specify that in the `initialState.expanded` table option. Pass `true` to expand all rows, or specify which rowIds should be expanded.
```jsx
const table = useShadcnTable({
data,
columns,
initialState: {
expanded: true,
// or expand specific rows by default
// expanded: {
// 1: true,
// 2: true,
// },
},
});
```
### Position Expand Column Last [#position-expand-column-last]
If you want to position the expand column last, you can set the `positionExpandColumn` table option to `'last'`.
Alternatively though, you could use the [Column Pinning Feature](/docs/guides/column-pinning) to pin the expand column to the right side of the table.
### Detail Panel With Charts [#detail-panel-with-charts]
The detail panel can be used to show a variety of content. Here's an example of a detail panel rendering charts with the [Recharts](https://recharts.org/) library.
### Detail Panels with Virtualization [#detail-panels-with-virtualization]
If you are using row virtualization, detail panels will now work more properly as of version 2.6.0. However, there are some caveats to be aware of. In order for row virtualization to work well, many of the animation/transitions have been disabled. This means that the detail panel will not animate open and closed. It will simply appear and disappear.
You also may need to specify some more accurate row height estimations for the row virtualizer in order to achieve the best scrollbar behavior. See the [Row Virtualization Guide](/docs/guides/row-virtualization) for the full details on this topic, but here's an example of how you might do that.
```jsx
const table = useShadcnTable({
data,
columns,
enableRowVirtualization: true,
renderDetailPanel: ({ row }) => ,
rowVirtualizerOptions: ({ table }) => {
const { density, expanded } = table.state;
return {
//adjust to your needs
estimateSize: (index) =>
index % 2 === 1 //even rows are normal rows, odd rows are detail panels
? //Estimate open detail panels as 80px tall, closed detail panels as 0px tall
expanded === true
? 80
: 0
: //estimate normal row heights
density === 'compact'
? 37
: density === 'comfortable'
? 58
: 73,
};
},
});
```
### Lazy Detail Panels [#lazy-detail-panels]
Fetching the additional data for the detail panels only after the user clicks to expand the row can be a good way to improve performance, and it is pretty easy to implement. It's even easier if you are using React Query.
View Extra Storybook **Examples **
# Display (Built-in) Columns (/docs/guides/display-columns)
Display columns are used to display non-data elements in a table. They only require an `id` and `header` in the column definition. They do not need an `accessorKey` or `accessorFn`, as they are not meant to connect to your data at all.
Display columns do not have any processing features, such as sorting, filtering, grouping, etc. enabled on them by default.
### Relevant Table Options [#relevant-table-options]
### Built-in ST Display Columns [#built-in-st-display-columns]
Shadcn Table has a few built-in display columns that are created automatically when certain features are enabled.
- `st-row-pin` - created when `enableRowPinning` table option is `true` with certain `rowPinningDisplayMode` values
- `st-row-drag` - created when `enableRowDragging` or `enableRowOrdering` table option are `true`
- `st-row-actions` - created when `enableRowActions` (or sometimes when `enableEditing`) props are `true`
- `st-row-expand` - created when `enableExpanding`, `enableGrouping`, or `renderDetailPanel` props are `true`
- `st-row-select` - created when `enableRowSelection` table option is `true`
- `st-row-numbers` - created when `enableRowNumbers` table option is `true`
- `st-row-spacer` - created when `layoutMode` is `"grid-no-grow"` (column resizing)
Display columns are, for the most part, the same as a data column, except they do not have an accessor to access data. When a display column is created internally by Shadcn Table, the following options are all set to false by default:
```tsx
const defaultDisplayColumnDefOptions = {
columnDefType: 'display',
enableClickToCopy: false,
enableColumnActions: false,
enableColumnDragging: false,
enableColumnFilter: false,
enableColumnOrdering: false,
enableEditing: false,
enableGlobalFilter: false,
enableGrouping: false,
enableHiding: false,
enableResizing: false,
enableSorting: false,
} as Partial;
```
All of these values are able to be overridden if needed, and you'll learn about that in the next section down below.
### Customize Built-in ST Display Columns [#customize-built-in-st-display-columns]
It is possible to change and override the default behavior of built-in display columns. Whether you want to change the default column width, add some styles, or enable some features, such as column actions or column drag and drop, you can do it with the `displayColumnDefOptions` table option.
#### Default Display Column Table Option [#default-display-column-table-option]
First of all, if you want to enable or disable some feature for all display columns, you can just use the `defaultDisplayColumn` table option. This will apply to all display columns, including any custom display columns you create.
```jsx
const table = useShadcnTable({
columns,
data,
defaultDisplayColumn: {
enableColumnOrdering: true,
enableColumnResizing: true,
minSize: 100,
},
});
```
#### Display Column Definition Options Table Option [#display-column-definition-options-table-option]
Let's say you need to adjust the width of the Actions column to be wide enough to fit all of your action buttons. You could do that as follows:
```jsx
const table = useShadcnTable({
columns,
data,
displayColumnDefOptions: { 'st-row-actions': { size: 300 } }, //change width of actions column to 300px
enableRowActions: true,
renderRowActions: ({ row }) => (
Action 1
Action 2
Action 3
),
});
return ;
```
Or maybe you want to enable a feature that is off by default for display columns, such as column ordering or pinning.
```jsx
const table = useShadcnTable({
columns,
data,
displayColumnDefOptions: {
'st-row-numbers': {
enableOrdering: true,
enablePinning: true,
enableColumnActions: true,
size: 40,
grow: true,
},
},
enableRowNumbers: true,
});
return ;
```
Here is a full example and demo for customizing display columns.
### Create your own Display Columns [#create-your-own-display-columns]
You do not have to use the built in Row Actions feature. You can just create your own display columns instead. It is as easy as creating a normal column, only specifying the `columnDefType` as `display`.
```jsx
const columns = [
{
id: 'sendEmail',
header: 'Send Email',
columnDefType: 'display', //turns off data column features like sorting, filtering, etc.
enableColumnOrdering: true, //but you can turn back any of those features on if you want like this
Cell: ({ row }) => (
sendEmail(row.original.userId)}>Send Email
),
},
{
id: 'name',
header: 'Name',
accessorKey: 'name',
},
];
```
# Editing Guide (/docs/guides/editing)
If your tables need full CRUD functionality, you can enable editing features in Shadcn Table.
There are five visually distinct editing modes to choose from, whether you want to let users [edit data in a modal](#modal-edit-display-mode), [inline one row at a time](#row-edit-display-mode), [one cell at a time](#cell-edit-display-mode), or just always have [editing enabled for every cell](#table-edit-display-mode), or a [completely custom editing UI](#custom-edit-display-mode).
### Relevant Table Options [#relevant-table-options]
### Relevant Column Options [#relevant-column-options]
### Relevant State Options [#relevant-state-options]
### Enable Editing [#enable-editing]
To enable editing, you first need to set the `enableEditing` table option to `true`.
```jsx
const table = useShadcnTable({
columns,
data,
enableEditing: true,
});
```
However, this is just the first step. You will need to hook up logic and event listeners, but it depends on which `editDisplayMode` you want to use.
### Disable Editing [#disable-editing]
Editing can be turned off on a per-column basis with the `enableEditing` column option.
```jsx
const columns = [
{
accessorKey: 'age',
header: 'Age',
enableEditing: false,
},
];
```
In the modal editing mode, a disabled text field will still be rendered for columns with editing disabled, but in the row, cell, and table editing modes, normal cell values will be rendered instead of text fields.
If you want to completely remove an editing textfield from the editing modal or render the normal cell value instead, you can use the `Edit` column option to override the editing UI for that column.
```jsx
const columns = [
{
accessorKey: 'age',
header: 'Age',
enableEditing: false,
Edit: () => null, //don't render anything in the editing modal for this column
//or Edit: ({ cell, renderedCellValue }) => <>{renderedCellValue}>, //render the normal cell value instead of a text field
},
];
```
### Edit Display Modes [#edit-display-modes]
There are five different editDisplayModes to choose from. The default is `"modal"`, but you can also choose `"row"`, `"cell"`, `"table"`, or `"custom"`.
#### Modal Edit Display Mode [#modal-edit-display-mode]
When using the default `"modal"` editDisplayMode, the user will be presented with a modal dialog where they can edit the data for one row at a time. No data is saved until the user clicks the save button. Clicking the cancel button clears out any changes that were made on that row.
An `onEditingRowSave` callback function prop must be provided where you will get access to the updated row data so that changes can be processed and saved. It is up to you how you handle the data. This function has a `exitEditingMode` parameter that must be called in order to exit editing mode upon save. The reason for this is so that you can perform validation checks before letting the modal close.
By default, Shadcn Table will render all of the cells in the row as text fields in a vertical stack. You can customize or override this behavior with the `stEditRowDialogProps` table option, or the `renderEditRowDialogContent` table option.
```jsx
const table = useShadcnTable({
columns,
data,
enableEditing: true,
editDisplayMode: 'modal', //default
onEditingRowSave: ({ table, values }) => {
//validate data
//save data to api
table.setEditingRow(null); //exit editing mode
},
onEditingRowCancel: () => {
//clear any validation errors
},
stEditRowDialogProps: {
//optionally customize the dialog
},
renderEditRowDialogContent: ({ internalEditComponents, row, table }) => {
//optionally, completely override the render of the dialog content
//use `internalEditComponents` to render the generated text fields, or completely render your own form and inputs
},
});
```
#### Row Edit Display Mode [#row-edit-display-mode]
The `"row"` editDisplayMode works just like the default `"modal"` editDisplayMode, except that the editing components will render inline in the table instead of in a modal dialog. Only one row is made editable at a time.
By default, you will probably not want to save any data until the user clicks the save button, though you could wire up `onChange` or `onBlur` events to save data as the user inputs data.
```jsx
const table = useShadcnTable({
columns,
data,
enableEditing: true,
editDisplayMode: 'row',
onEditingRowSave: ({ table, values }) => {
//validate data
//save data to api
table.setEditingRow(null); //exit editing mode
},
onEditingRowCancel: () => {
//clear any validation errors
},
});
```
#### Cell Edit Display Mode [#cell-edit-display-mode]
The `"cell"` editDisplayMode is a bit simpler visually. By default, a user can double-click a cell to activate editing mode, but only for that cell.
Then there is a bit of work for you to do to wire up either the `onBlur`, `onChange`, etc., events yourself in order to save the table data. This can be done in the `stEditTextFieldProps` table option or column definition option.
```jsx
const columns = [
{
accessor: 'age',
header: 'Age',
stEditTextFieldProps: ({ cell, row, table }) => ({
onBlur: (event) => {
//validate data
//save data to api and/or rerender table
// table.setEditingCell(null) is called automatically onBlur internally
},
}),
},
];
const table = useShadcnTable({
columns,
data,
enableEditing: true,
editDisplayMode: 'cell',
//optionally, use single-click to activate editing mode instead of default double-click
stTableBodyCellProps: ({ cell, column, table }) => ({
onClick: () => {
table.setEditingCell(cell); //set editing cell
//optionally, focus the text field
queueMicrotask(() => {
const textField = table.refs.editInputRefs.current?.[column.id];
if (textField) {
textField.focus();
textField.select?.();
}
});
},
}),
});
```
#### Table Edit Display Mode [#table-edit-display-mode]
The `"table"` editDisplayMode is similar to the `"cell"` editDisplayMode, but it simply has all of the data cells in the table become editable all at once. You will most likely wire up all of the logic the same way as the `"cell"` editDisplayMode.
#### Custom Edit Display Mode [#custom-edit-display-mode]
There is another option if you don't like any of the built-in editDisplayModes UI. If you want to completely handle your own editing UI, you can use the `"custom"` editDisplayMode. This will give you access to the `editingCell`, `editingRow`, and `creatingRow` state options, but ST will not render any editing UI for you. This is common for rendering a form in a sidebar or similar.
### Enable Creating [#enable-creating]
Shadcn Table offers new functionality to make creating news rows of data easier. It works just like the editing features, but with separate state options and callbacks. A Blank row is added to the table or modal for the user to fill out and submit.
#### Create Display Modes [#create-display-modes]
There are just three different createDisplayModes to choose from. The default is `"modal"`, but you can also choose `"row"` or `"custom"`. They work exactly the same as their editDisplayMode counterparts.
#### Position Creating Row [#position-creating-row]
By default, the creating row will be added to the top of the table. You can change this behavior with the `positionCreatingRow` table option.
```jsx
const table = useShadcnTable({
columns,
data,
enableEditing: true,
createDisplayMode: 'modal',
positionCreatingRow: 'bottom', //default is 'top'
});
```
In advanced use cases, such as editing nested expanding sub-rows, you could even pass in a number to the `positionCreatingRow` table option to specify the index of the row to insert the creating row before.
#### Trigger Create Mode [#trigger-create-mode]
To trigger a new blank row to be added to the table, we just need to just populate the `creatingRow` state option with a new blank row. This can be done with the `table.setCreatingRow` table instance API. You can either pass in `true` as an argument, or pass in row object with default values.
```jsx
const table = useShadcnTable({
columns,
data,
enableEditing: true,
editDisplayMode: 'modal',
createDisplayMode: 'modal',
onCreatingRowSave: ({ table, values }) => {
//validate data
//save data to api
table.setCreatingRow(null); //exit creating mode
},
onCreatingRowCancel: () => {
//clear any validation errors
},
renderTopToolbarCustomActions: ({ table }) => (
{
table.setCreatingRow(true); //simplest way to open the create row modal with no default values
//or you can pass in a row object to set default values with the `createRow` helper function
// table.setCreatingRow(
// createRow(table, {
// //optionally pass in default values for the new row, useful for nested data or other complex scenarios
// }),
// );
}}
>
Create New User
),
});
```
### CRUD Examples [#crud-examples]
### Customizing Editing Components [#customizing-editing-components]
You can pass any shadcn/ui TextField Props with the `stEditTextFieldProps` table option.
```jsx
const columns = [
{
accessor: 'age',
header: 'Age',
stEditTextFieldProps: {
required: true,
type: 'number',
variant: 'outlined',
},
},
];
```
#### Add Validation to Editing Components [#add-validation-to-editing-components]
You can add validation to the editing components by using the `stEditTextFieldProps` events. You can write your validation logic and hook it up to the `onBlur`, `onChange`, etc., events, then set the `error` and `helperText` props accordingly.
If you are implementing validation, you may also need to use the `onEditingRowCancel` table option to clear the validation error state.
```jsx
const [validationErrors, setValidationErrors] = useState({});
const columns = [
{
accessor: 'age',
header: 'Age',
stEditTextFieldProps: {
error: !!validationErrors.age, //highlight the input red
helperText: validationErrors.age, //show error message in helper text.
required: true,
type: 'number',
onChange: (event) => {
const value = event.target.value;
//validation logic
if (!value) {
setValidationErrors((prev) => ({ ...prev, age: 'Age is required' }));
} else if (value < 18) {
setValidationErrors({
...validationErrors,
age: 'Age must be 18 or older',
});
} else {
delete validationErrors.age;
setValidationErrors({ ...validationErrors });
}
},
},
},
];
```
### Use Custom Editing Components [#use-custom-editing-components]
If you need to use a much more complicated Editing component than the built-in textfield, you can specify a custom editing component with the `Edit` column definition option.
```tsx
const columns = [
{
accessorKey: 'email',
header: 'Email',
Edit: ({ cell, column, row, table }) => {
const onBlur = (event) => {
row._valuesCache[column.id] = event.target.value;
if (isCreating) {
setCreatingRow(row);
} else if (isEditing) {
setEditingRow(row);
}
};
return ;
},
},
];
```
### Customize Actions/Edit Column [#customize-actionsedit-column]
You can customize the actions column in a few different ways in the `displayColumnDefOptions` prop's `'st-row-actions'` section.
```jsx
const table = useShadcnTable({
columns,
data,
displayColumnDefOptions: {
'st-row-actions': {
header: 'Edit', //change "Actions" to "Edit"
size: 120,
//use a text button instead of a icon button
Cell: ({ row, table }) => (
table.setEditingRow(row)}>Edit Customer
),
},
},
});
```
# Expanding Sub-Rows/Tree Data Guide (/docs/guides/expanding-sub-rows)
Shadcn Table has support for expanding sub-rows or tree data. This feature is useful for displaying hierarchical data. The sub-rows can be expanded and collapsed by clicking on the expand/collapse icon.
> NOTE: This feature is for expanding rows of the same data type. If you want to add expansion of more data for the same row, check out the [Detail Panel Feature Guide](/docs/guides/detail-panel).
### Relevant Table Options [#relevant-table-options]
### Relevant State Options [#relevant-state-options]
### Enable Expanding Sub-Rows [#enable-expanding-sub-rows]
To enable expanding sub-rows, you must first set the `enableExpanding` table option to `true`.
However, your data must also be formatted in a way to allow for expanding rows that are in some way related to each other. By default, Shadcn Table will look for a special `subRows` property on each row of your data and treat any array of rows that it finds as the sub-rows for that row. You can customize or override this behavior by passing a custom `getSubRows` table option.
```jsx
const data = [
{
id: 1,
name: 'John Doe',
subRows: [
{
id: 2,
name: 'Jane Doe',
},
//more sub rows...
],
},
//more rows...
];
const table = useShadcnTable({
columns,
data,
enableExpanding: true,
getSubRows: (originalRow) => originalRow.subRows, //default, can customize
});
return ;
```
### Expand All Rows Button [#expand-all-rows-button]
By default, Shadcn Table will show the expand all button in the expand column header. You can disable this by setting the `enableExpandAll` table option to `false`.
```jsx
const table = useShadcnTable({
columns,
data,
enableExpanding: true,
enableExpandAll: false, //hide expand all button in header
});
```
### Generate Sub Rows with getSubRows [#generate-sub-rows-with-getsubrows]
If your data is not yet in a tree structure, but the data has relationships that can be parsed into a tree, you can use the `getSubRows` table option to let TanStack Table find the sub rows for each row.
There are a couple key things you have to do to make this work:
1. Only pass in root (top level) rows in your data prop.
2. Set the `getSubRows` table option to a function that scans all the rest of your data and returns the sub rows for a given row.
This can sometimes be useful in combination with [lazy loading sub rows](#lazy-load-sub-rows).
> NOTE: Be conscious of the performance implications of the `getSubRows` function. It will be called for every row in your table, so it should be performant.
### Expanded Rows Pagination Behavior [#expanded-rows-pagination-behavior]
By default, Shadcn Table will treat expanded sub-rows the same as any other row when it comes to pagination. This means that some expanded rows may be on the next page. You can change this behavior by setting the `paginateExpandedRows` table option to `false`.
```jsx
const table = useShadcnTable({
columns,
data,
enableExpanding: true,
paginateExpandedRows: false, //expanded rows will be on the same page as their parent row
});
```
### Expanded Leaf Row Filtering Behavior [#expanded-leaf-row-filtering-behavior]
If you are using the [filtering features](/docs/guides/column-filtering) alongside sub-row features, then there are a few behaviors and customizations you should be aware of.
#### Filter From Leaf Rows [#filter-from-leaf-rows]
By default, filtering is done from parent rows down (so if a parent row is filtered out, all of its children will be filtered out as well). Setting the `filterFromLeafRows` table option to `true` will cause filtering to be done from leaf rows up (which means parent rows will be kept so long as one of their child, or grand-child, etc. rows pass the filtering).
```jsx
const table = useShadcnTable({
columns,
data,
enableExpanding: true,
filterFromLeafRows: true, //search for child rows and preserve parent rows
});
```
#### Max Leaf Row Filter Depth [#max-leaf-row-filter-depth]
By default, filtering is done for all rows (max depth of 100), no matter if they are root level parent rows or the child leaf rows of a parent row. Setting the `maxLeafRowFilterDepth` table option to `0` will cause filtering to only be applied to the root level parent rows, with all sub-rows remaining unfiltered. Similarly, setting this option to 1 will cause filtering to only be applied to child leaf rows 1 level deep, and so on.
This is useful for situations where you want a row's entire child hierarchy to be visible, regardless of the applied filter.
```jsx
const table = useShadcnTable({
columns,
data,
enableExpanding: true,
maxLeafRowFilterDepth: 0, //When filtering root rows, keep all child rows of the passing parent rows
});
```
### Expand All Rows By Default [#expand-all-rows-by-default]
You can manage the initial state of the expanded rows with the `expanded` state option in either the `initialState` or `state` props.
For example, you may want all rows to be expanded by default. To do this, you can simply set the `expanded` state option to `true`.
```jsx
const table = useShadcnTable({
columns,
data,
enableExpanding: true,
initialState: { expanded: true }, //all rows expanded by default
});
```
### Expand Root Rows Only By Default [#expand-root-rows-only-by-default]
Here is a slightly more complex initial expanded state example where all the root rows are expanded by default, but none of the sub rows themselves are expanded by default. We just need to find all of the root row ids and set their key in the `expanded` `initialState` option to `true`.
### Customize Expand Column [#customize-expand-column]
You can customize the expand column by using the `displayColumnDefOptions` table option.
```jsx
const table = useShadcnTable({
columns,
data,
enableGrouping: true,
displayColumnDefOptions: {
'st-row-expand': {
enableResizing: true, //allow resizing
size: 120, //make the expand column wider
},
},
});
```
### Lazy Load Sub Rows [#lazy-load-sub-rows]
If you have a ton of nested data that you want to display, but you don't want to fetch it all up front, you can set up Shadcn Table to only fetch the sub-rows data when the user expands the row.
There are quite a few ways in which you could implement fetching sub-rows lazily. This example is just one way to do it.
The main concept to understand from this example is that you can manage the `expanded` state option in your own scope, and fetch the data for your table based on that state.
How your data is structured from the server is up to you. It is usually easiest to have the server do the hard work and return the data in a nested tree structure, but you can also return the data in a flat structure and use the `getSubRows` table option to parse the data into a tree structure.
# Full Screen Toggle Guide (/docs/guides/full-screen-toggle)
### Relevant Table Options [#relevant-table-options]
### Relevant State [#relevant-state]
### Disable Full Screen Toggle [#disable-full-screen-toggle]
The full screen toggle button is enabled by default. You can hide it by setting the `enableFullScreenToggle` table option to `false`.
```jsx
const table = useShadcnTable({
columns,
data,
enableFullScreenToggle: false,
});
return ;
```
### Change Z-Index of Full Screen Table [#change-z-index-of-full-screen-table]
Under the hood, when the table is full screen, these classes are applied to the root table container:
```txt
fixed inset-0 z-50 h-dvh max-h-dvh w-dvw max-w-dvw rounded-none border-0 m-0 p-0
```
If you need to change the `zIndex` of the full screen table, you can do so by passing in a `stTableLayoutProps` table option with a `style` object that has a `zIndex` property (or a `z-*` Tailwind class in `className`).
```jsx
stTableLayoutProps: ({ table }) => ({
style: {
zIndex: table.state.isFullScreen ? 1000 : undefined,
},
});
```
# Global Filtering/Search Guide (/docs/guides/global-filtering)
Shadcn Table has a powerful built-in global filtering (search) feature that uses a fuzzy matching algorithm and ranks/sorts the results based on how closely rows match the search query. In this guide, we'll cover how to use, customize, or disable the global filter and search features to fit your needs.
### Relevant Table Options [#relevant-table-options]
### Relevant Column Options [#relevant-column-options]
### Relevant State Options [#relevant-state-options]
### Disable Global Filtering [#disable-global-filtering]
You can either disable the global filter feature entirely or disable it for specific columns.
#### Disable Global Filtering per Column [#disable-global-filtering-per-column]
If you simply want to not include a column as one of the columns that the global filter scans through during filtering, you can set the `enableGlobalFilter` option to `false` for that column.
```tsx
const columns = [
{
accessorKey: 'id',
header: 'Id',
enableGlobalFilter: false, // do not scan this column during global filtering
},
{
accessorKey: 'name',
header: 'Name',
},
];
```
#### Disable Global Filter Feature [#disable-global-filter-feature]
You can disable the global filtering feature and hide the search icon by setting the `enableGlobalFilter` table option to `false`.
```tsx
const table = useShadcnTable({
columns,
data,
enableGlobalFilter: false, //disable search feature
});
return ;
```
### Client-Side Global Filtering [#client-side-global-filtering]
Client-side filtering (and global filtering) is enabled by default. This means that the search box will scan through all columns and try to find matches for the search term.
#### Global Filter Function [#global-filter-function]
You can use any of the built-in `filterFns` or any of the custom filter functions that you have defined in the `filterFns` prop, just like you would with the column filters.
```jsx
const table = useShadcnTable({
columns,
data,
globalFilterFn: 'contains', //turn off fuzzy matching and use simple contains filter function
});
```
Or a custom filter function:
```jsx
const table = useShadcnTable({
columns,
data,
filterFns: {
myCustomFilterFn: (row, id, filterValue) =>
row.getValue(id).startsWith(filterValue),
},
globalFilterFn: 'myCustomFilterFn', //set the global filter function to myCustomFilterFn
});
```
The default global filter function is set to `fuzzy`, which is a filtering algorithm based on the popular `match-sorter` [library from Kent C. Dodds](https://www.npmjs.com/package/match-sorter), though you can change the global filter function by setting the `globalFilterFn` table option.
#### Ranked Results [#ranked-results]
If you keep the default `fuzzy` filterFn option as the global filter function, you get an extra ranked results feature enabled by default. This means that when a user searches with the search box, the results will be sorted by the closest match first instead of the order the data was defined in.
The ranked results feature will disable itself automatically if a sort direction is applied to a column, if any sub-rows are expanded, or if any of the `manual` props are set to `true`.
If you do not want ranked results to be enabled at all, but you still want fuzzy matching, you can set the `enableGlobalFilterRankedResults` table option to `false`.
```jsx
const table = useShadcnTable({
columns,
data,
enableGlobalFilterRankedResults: false, //preserve the order of the data when fuzzy match searching
});
```
#### Global Filter Modes [#global-filter-modes]
Similar to the column filter modes, you can enable the user to be able to choose between multiple different filter modes for the global filter with the `enableGlobalFilterModes` table option. You can then customize which filter modes are available in the drop-down by setting the `globalFilterModeOptions` table option or by rendering your own custom menu items with the `renderGlobalFilterModeMenuItems` table option.
```jsx
const table = useShadcnTable({
columns,
data,
enableGlobalFilterModes: true, //enable the user to choose between multiple search filter modes
globalFilterModeOptions: ['fuzzy', 'startsWith'], //only allow the user to choose between fuzzy and startsWith filter modes
});
```
#### Show Search Field by Default [#show-search-field-by-default]
Additionally, if you want to show the search text box by default and not hide it behind the search icon, you can set the `showGlobalFilter` state to `true` in the `initialState`.
### Manual Server-Side Global Filtering [#manual-server-side-global-filtering]
A very common use case when you have a lot of data is to filter the data on the server, instead of client-side. In this case, you will want to set the `manualFiltering` table option to `true` and manage the `globalFilter` state yourself like in the example below (can work in conjunction with [manual column filtering](/docs/guides/column-filtering#manual-server-side-column-filtering)).
```tsx
// You can manage and have control over the columnFilters state yourself
const [globalFilter, setGlobalFilter] = useState('');
const [data, setData] = useState([]); //data will get updated after re-fetching
useEffect(() => {
const fetchData = async () => {
// send api requests when columnFilters state changes
const filteredData = await fetch();
setData([...filteredData]);
};
}, [globalFilter]);
const table = useShadcnTable({
columns,
data, // this will already be filtered on the server
manualFiltering: true, //turn off client-side filtering
onGlobalFilterChange: setGlobalFilter, //hoist internal global state to your state
state: { globalFilter }, //pass in your own managed globalFilter state
});
return ;
```
> Specifying `manualFiltering` turns off all client-side filtering and assumes that the `data` you pass to ` ` is already filtered.
Here is the full Remote Data example showing off server-side **filtering**, pagination, and sorting.
### Customize Global Filter Position [#customize-global-filter-position]
You can customize the position of the global filter (search box) in the top toolbar by setting the `positionGlobalFilter` table option to `left` or `right`. It is shown on the right by default.
```jsx
const table = useShadcnTable({
columns,
data,
positionGlobalFilter: 'left', //show the global filter on the left side of the top toolbar
initialState: {
showGlobalFilter: true, //show the global filter by default
},
});
```
### Customize the Search Text Field [#customize-the-search-text-field]
You can customize the search text field by passing in props to the `stSearchTextFieldProps` table option. This is useful if you want to customize the placeholder text, add styles, or any other text field props.
```jsx
const table = useShadcnTable({
columns,
data,
stSearchTextFieldProps: {
placeholder: 'Search all users',
style: { minWidth: '300px' },
},
});
return ;
```
### Filter Match Highlighting [#filter-match-highlighting]
Filter match highlighting is a new featured enabled by default that will highlight text in the table body cells that matches the current search query with a shade of the `theme.palette.warning.main` color.
If you are using a custom `Cell` render override for a column, you will need to use the `renderedCellValue` table option instead of `cell.getValue()` to preserve the filter match highlighting.
```jsx
const columns = [
{
accessorKey: 'name',
header: 'Name',
Cell: ({ renderedCellValue }) => {renderedCellValue} , // use renderedCellValue instead of cell.getValue()
},
];
```
#### Disable Filter Match Highlighting [#disable-filter-match-highlighting]
Filter match highlighting can be disabled by setting the `enableFilterMatchHighlighting` table option to `false`.
```jsx
const table = useShadcnTable({
columns,
data,
enableFilterMatchHighlighting: false, //disable filter match highlighting
});
```
View Extra Storybook **Examples **
# Guides (/docs/guides)
item.href === '/docs/guides').items}
variant="heading"
/>
# Localization/i18n Guide (/docs/guides/localization)
Shadcn Table has full support for localization (i18n). Some locales are included by default, but if your language is not yet supported, you can still easily add your own custom translations to the `localization` table option.
### Relevant Table Options [#relevant-table-options]
### Built-in Locales [#built-in-locales]
English ships with the table. Every other locale is its own registry item, `shadcn-table-locale-`, installed after the table from the same base path of the registry (`/r/radix/` for a `radix-*` style in `components.json`, `/r/base/` for a `base-*` style):
```bash
pnpm dlx shadcn@latest add https://www.shadcn-table.com/r/radix/shadcn-table-locale-de.json # Radix UI
pnpm dlx shadcn@latest add https://www.shadcn-table.com/r/base/shadcn-table-locale-de.json # Base UI
```
With the `@st` namespace registered (see [Installation](/docs/getting-started/install)), `pnpm dlx shadcn@latest add @st/shadcn-table-locale-de` picks the path from the project's style. GitHub fallback: `anindosarker/shadcn-table/shadcn-table-locale-de` (Radix UI) and `anindosarker/shadcn-table/shadcn-table-base-locale-de` (Base UI).
The item lands in `components/ui/shadcn-table/locales/.ts` and is imported from `'@/components/ui/shadcn-table/locales/'`. Available codes:
`ar`, `az`, `bg`, `cs`, `da`, `de`, `el`, `en`, `es`, `et`, `fa`, `fi`, `fr`, `he`, `hr`, `hu`, `hy`, `id`, `it`, `ja`, `ko`, `nl`, `no`, `np`, `pl`, `pt`, `pt-BR`, `ro`, `ru`, `sk`, `sr-Cyrl-RS`, `sr-Latn-RS`, `sv`, `tr`, `uk`, `vi`, `zh-Hans`, `zh-Hant`
> If your language is not yet supported, please consider making a PR to add it to the library! See [here on GitHub](https://github.com/anindosarker/shadcn-table/tree/master/packages/shadcn-table/shared/components/ui/shadcn-table/locales). A locale is data shared by both bases: add one file under `shared/`.
#### Built-in Locale Examples [#built-in-locale-examples]
Scroll and find your language below to see an example of how to use it.
### Custom Non-Built-In Translations [#custom-non-built-in-translations]
If you want to use a language that is not included in the library, you can still easily add your own custom translations to the `localization` table option.
```jsx
const table = useShadcnTable({
columns,
data,
localization: {
language: 'pt', // BCP 47 language tag for number formatting
actions: 'Ações',
and: 'e',
cancel: 'Cancelar',
changeFilterMode: 'Alterar o modo de filtro',
changeSearchMode: 'Alterar o modo de pesquisa',
clearFilter: 'Limpar filtros',
clearSearch: 'Limpar pesquisa',
clearSort: 'Limpar classificações',
clickToCopy: 'Clique para copiar',
// ... and many more - see link below for full list of translation keys
},
});
return ;
```
For a full list of all available translation keys, see [here](https://github.com/anindosarker/shadcn-table/blob/master/packages/shadcn-table/shared/components/ui/shadcn-table/locales/en.ts).
Please note that each localization object should include a `language` property containing a valid BCP 47 language tag to ensure proper number formatting.
If you end up fully translating ST into another language that is not yet supported, please consider making a PR to add it to the library so that everyone can use it!
# Memoization Guide (/docs/guides/memoization)
Shadcn Table already uses some memoization techniques to improve performance for you automatically under the hood to improve performance during some events like column resizing. In this guide, we'll go over the basics of what is recommended to be memoized, what you need to keep in mind for performance, and how to tweak advanced memoization settings for your specific use case.
### Relevant Table Options [#relevant-table-options]
### What to Memoize [#what-to-memoize]
First of all, when these docs refer to memoization, we are not necessarily talking just about the React `useMemo` hook. Using the `useMemo` hook is just one way to give an variable a stable reference in React. `useMemo`, `useState`, `useReducer`, `useQuery` (React Query), storing state in a state management library like Zustand or Redux, or even just defining a variable outside of a component are all ways to give a variable a stable reference.
#### Give Data a Stable Reference [#give-data-a-stable-reference]
The only truly required thing that must have a stable reference for both Shadcn Table and TanStack Table is the `data` table option. If your `data` does not have a stable reference, the table will enter an infinite loop of re-rendering upon any state change.
> Failing to give `data` a stable reference can cause an infinite loop of re-renders.
##### _Why does this happen? Is this a bug in ST or TanStack Table?_ [#why-does-this-happen-is-this-a-bug-in-st-or-tanstack-table]
No, this is not a bug in either ST or TanStack Table. This is just fundamentally how React works. TanStack Table is designed to re-render whenever the `data` changes. You would probably be equally surprised and upset if ST only accepted what you passed in as `data` on the first render and never updated after a refetch or state change.
```jsx
export default function MyComponent() {
const columns = [
// ...
];
//😵 BAD: This will cause an infinite loop of re-renders because `data` is redefined as a new array on every render
const data = [
// ...
];
const table = useShadcnTable({
columns,
data, //data is defined as a const in the same scope as `useShadcnTable`, will cause infinite loop
});
return ;
}
```
##### I'm still getting an infinite loop of re-renders, but I'm using useMemo! [#im-still-getting-an-infinite-loop-of-re-renders-but-im-using-usememo]
Sometimes developers will properly memoize their `data`, but don't end up passing that memoized `data` to the table, and apply some extra transformation or filtering to the `data` before passing it to the table. This common mistake will still cause an infinite loop of re-renders.
```jsx
export default function MyComponent() {
const columns = [
// ...
];
//GOOD: Storing `data` in state gives it a stable reference
const [data, setData] = useState([
// ...
]);
//GOOD: This still preserves the stable reference of `data` and will not cause an infinite loop of re-renders
const filteredData = useMemo(
() => data.filter((row) => row.isActive),
[data],
);
const table = useShadcnTable({
columns,
//GOOD: Reading from the `data` state is stable
data,
//😵 BAD: This is ignoring the stable reference of `data` and re-creating a new array on every render
data: data.filter((row) => row.isActive),
//GOOD: This is still reading from the stable reference from a `useMemo` hook
data: filteredData,
});
return ;
}
```
#### Memoize Columns [#memoize-columns]
You will generally have better performance if you properly memoize your `columns` array, or give it a stable reference like you would with `data`. Not giving `columns` a stable reference will not cause an infinite loop of re-renders like `data`, but it will still cause the table to re-render more than necessary. The columns `useMemo` dependency array does not need to be an empty array. If your column definitions are derived from other state, you should include that state in the dependency array.
```jsx
const columns = useMemo(
() => [
// ...
],
[],
);
//OR
const columns = useMemo(
() => [
// ...
],
[dependency1, dependency2], //if column defs are derived from other state, don't forget to include that state in the dependency array
);
//OR
const [columns, setColumns] = useState(() => [
// ...
]);
```
#### Other Options to Memoize [#other-options-to-memoize]
Usually you will not need to memoize any other options besides `columns` and `data`. However, if you have any expensive logic in any of the `render*` options, you may benefit from wrapping that logic in a React `useCallback` hook. It is not recommended to start off by wrapping all of your `render*` options in `useCallback` hooks, as this usually does not even provide a noticeable performance improvement. This usually takes some experimentation.
Here's an example for how to render detail panels with a `useCallback` optimization. Getting the TypeScript types correct in the `useCallback` can be tricky, but here's how you can do it:
```tsx
const table = useShadcnTable({
columns,
data,
renderDetailPanel: useCallback<
Required>['renderDetailPanel'] //TS needed to get the correct type of the inner arrow function below
>(
({ row, table }) =>
[], //add proper dependencies here if needed
),
});
```
All of the other `st*Props` components could also be memoized with either `useMemo` or `useCallback` if you have complicated expensive logic in them for some reason in the same way as above.
### Memo Mode [#memo-mode]
Shadcn Table already memoizes columns and the entire `` component during column resizing, and column/row drag and drop events. This actually does make a significant performance improvement, and is why it is possible to have the performance that it does during these events.
If you need to reach into the internals and apply some custom memoization, ST exposes a limited way to do this. The Table Body, Table Rows, or all Table Cells can be memoized with the `memoMode` table option.
```jsx
const table = useShadcnTable({
columns,
data,
//memoize all cells. This value can be applied dynamically based on a certain scenario/condition if needed
memoMode: 'cells', // 'cells' | 'rows' | 'table-body'
});
```
Usually you should not ever need to do this, and be aware that this will stop certain features from functioning correctly. For example, if you memoize all table cells, the density toggle feature will not work anymore. If you memoize the entire table body, most features including virtualization will not work anymore. You'll be left with a table that is mostly frozen after first render, but it could improve the overall performance of your web page if you don't need any of these interactive features.
> Don't use `memoMode` unless you know what you're doing and have a specific use case for it.
### React Forget [#react-forget]
In the future, React may finally release their magical "React Forget" compiler that will make all memoization problems in React disappear, and you won't have to worry about any of this. But until then, the above solutions are the best we have.
View Extra Storybook Examples
# Pagination Guide (/docs/guides/pagination)
Client-side pagination is enabled by default in Shadcn Table. There are a number of ways to customize pagination, turn off pagination, or completely replace the built-in pagination with your own manual or server-side pagination logic.
### Relevant Table Options [#relevant-table-options]
### Relevant State Options [#relevant-state-options]
### Disable Pagination [#disable-pagination]
If you simply want to disable pagination, you can set the `enablePagination` table option to `false`. This will both hide the pagination controls and disable the pagination functionality.
If you only want to disable the pagination logic, but still want to show and use the pagination controls, take a look down below at the [Manual Pagination](#manual-or-server-side-pagination) docs.
```jsx
const table = useShadcnTable({
columns,
data,
enablePagination: false,
enableBottomToolbar: false, //hide the bottom toolbar as well if you want
});
```
### Customize Pagination [#customize-pagination]
#### Pagination State [#pagination-state]
If you want to change the rows per page value or the initial page index, you can do that through either the `initialState` or `state` table option.
```jsx
//using initialState if you do not need to manage the pagination state yourself
const table = useShadcnTable({
columns,
data,
initialState: { pagination: { pageSize: 25, pageIndex: 2 } }, //customize the default page size and page index
});
```
```jsx
//using state if you want to manage the pagination state yourself
const [pagination, setPagination] = useState({
pageIndex: 0,
pageSize: 5, //customize the default page size
});
const table = useShadcnTable({
columns,
data,
onPaginationChange: setPagination, //hoist pagination state to your state when it changes internally
state: { pagination }, //pass the pagination state to the table
});
```
> Note: Do not pass pagination state to both `initialState` and `state` at the same time. The `state` will overwrite the `initialState` values.
#### Customize Pagination Behavior [#customize-pagination-behavior]
There are a few table options that you can use to customize the pagination behavior. The first one is `autoResetPageIndex`. This table option is `true` by default, and causes a table to automatically reset the table back to the first page whenever sorting, filtering, or grouping occurs. This makes sense for most use cases, but if you want to disable this behavior, you can set this table option to `false`.
Next there is `paginateExpandedRows`, which works in conjunction expanding features. This table option is `true` by default, and forces the table to still only render the same number of rows per page that is set as the page size, even as sub-rows become expanded. However, this does cause expanded rows to sometimes not be on the same page as their parent row, so you can turn this off to keep sub rows with their parent row on the same page.
#### Customize Pagination Components [#customize-pagination-components]
You can customize the pagination component with the `stPaginationProps` table option to change things like the `rowsPerPageOptions` or whether or not to show the first and last page buttons, and more.
```jsx
const table = useShadcnTable({
columns,
data,
stPaginationProps: {
rowsPerPageOptions: [5, 10, 20],
showFirstButton: false,
showLastButton: false,
},
});
```
#### Alternate Pagination UI [#alternate-pagination-ui]
By default, Shadcn Table provides its own Table Pagination UI that is more compact and traditional for data tables. However, if you want to use the shadcn/ui Pagination component instead, it is as easy as setting the `paginationDisplayMode` table option to `pages`.
```jsx
const table = useShadcnTable({
columns,
data,
paginationDisplayMode: 'pages',
});
```
### Manual or Server-Side Pagination [#manual-or-server-side-pagination]
#### Manual Pagination [#manual-pagination]
The default pagination features are client-side. This means you have to have all of your data fetched and stored in the table all at once. This may not be ideal for large datasets, but do not worry, Shadcn Table supports server-side pagination.
When the `manualPagination` table option is set to `true`, Shadcn Table will assume that the `data` that is passed to the table already has had the pagination logic applied. Usually you would do this in your back-end logic.
#### Override Page Count and Row Count [#override-page-count-and-row-count]
If you are using manual pagination, the default page count and row count in the ST Pagination component will be incorrect, as it is only derived from the number of rows provided in the client-side `data` table option. Luckily, you can override these values and set your own page count or row count in the `pageCount` and `rowCount` table options.
```jsx
const table = useShadcnTable({
columns,
data,
manualPagination: true,
rowCount: data.meta.totalDBRowCount, //you can tell the pagination how many rows there are in your back-end data
});
```
### Manage Pagination State [#manage-pagination-state]
For either client-side or server-side pagination, you may want to have access to the pagination state yourself. You can do this like so with `state`:
```jsx
//store pagination state in your own state
const [pagination, setPagination] = useState({
pageIndex: 0,
pageSize: 5, //customize the default page size
});
useEffect(() => {
//do something when the pagination state changes
}, [pagination.pageIndex, pagination.pageSize]);
const table = useShadcnTable({
columns,
data,
onPaginationChange: setPagination, //hoist pagination state to your state when it changes internally
state: { pagination }, //pass the pagination state to the table
});
return ;
```
Alternatively, if all you care about is customizing the initial pagination state and do not need to react to its changes, like customizing the default page size or the page index, you can do that like so with `initialState`:
```jsx
const table = useShadcnTable({
columns,
data,
initialState: { pagination: { pageSize: 25, pageIndex: 2 } },
});
```
Here is the full Remote Data example showing off server-side filtering, **pagination**, and sorting.
View Extra Storybook **Examples **
# Building Production-Ready Tables (/docs/guides/production-ready-tables)
This guide shows the pattern for a real-world table. The data comes from your API through React Query. The server does the paging, sorting, search and filtering, and the table only shows one page at a time. You need four pieces:
1. **Service:** an async function that calls your API.
2. **Query hook:** `useUsersQuery(params)` wraps the service in React Query.
3. **`useTableQueryParams`:** owns the table state and turns it into the params your server accepts.
4. **Table:** turns off client-side processing and reads its state from the hook.
```text
table state
▼ useTableQueryParams
server params
▼ useUsersQuery
▼ service
GET /api/users
▼
{ data, total } ──▶ table
```
This guide assumes you know React and the basics of React Query.
Open the network tab and click around. Every page, sort, search or filter change sends one request to `/api/users`.
### Folder Layout [#folder-layout]
The live example is split the way you would split it in your app. Open the Code view above to browse the files:
```text
server-side-data/
├── TS.tsx # QueryClientProvider +
├── components/
│ └── UsersTable.tsx # 4. the table
├── hooks/
│ ├── useTableQueryParams.ts # 3. table state → server params
│ └── useUsersQuery.ts # 2. React Query hook
└── lib/
└── users.service.ts # 1. service + types
```
The demo server takes these params and returns `{ data, total, page, limit }`:
```text
GET /api/users?page=2&limit=10&search=tex&sortBy=lastName:desc&filter=state[$contains]=tex
```
### Write the Service [#write-the-service]
The service is a plain async function. It knows your API's URL and response shape, and nothing about tables. Type the params and the response.
```ts title="lib/users.service.ts"
export type UsersQueryParams = {
page: number; //1-based
limit: number;
search?: string;
sortBy?: string[]; //"lastName:desc"
filter?: string[]; //"state[$contains]=tex"
};
export type PaginatedResponse = {
data: T[];
total: number;
page: number;
limit: number;
};
export async function getUsers(
params: UsersQueryParams,
): Promise> {
const url = new URL('/api/users', location.origin);
for (const [key, value] of Object.entries(params)) {
if (value === undefined) continue;
//arrays become repeated keys: sortBy=a&sortBy=b
for (const item of [value].flat()) {
url.searchParams.append(key, String(item));
}
}
const response = await fetch(url);
if (!response.ok) throw new Error(`GET /api/users: ${response.status}`);
return response.json();
}
```
Throw on a bad status. `fetch` does not, and React Query only sees an error if the function throws.
If you use axios, it does the URL building for you. To send arrays as repeated keys, use `qs`:
```ts
const http = axios.create({
paramsSerializer: (params) => qs.stringify(params, { arrayFormat: 'repeat' }),
});
const { data } = await http.get>('/users', { params });
```
### Wrap It in a Query Hook [#wrap-it-in-a-query-hook]
```ts title="hooks/useUsersQuery.ts"
import { keepPreviousData, useQuery } from '@tanstack/react-query';
import { getUsers, type UsersQueryParams } from '../lib/users.service';
export function useUsersQuery(params: UsersQueryParams) {
return useQuery({
queryKey: ['users', params], //new params, new cache entry, new request
queryFn: () => getUsers(params),
placeholderData: keepPreviousData, //keep the old page on screen while the next one loads
});
}
```
- **`params` in the `queryKey`:** React Query refetches when the key changes. Every page, sort and filter gets its own cache entry, so going back to page 1 is instant.
- **`keepPreviousData`:** without it, `data` is `undefined` while the next page loads, and the table flashes empty.
### Turn Table State Into Server Params [#turn-table-state-into-server-params]
This hook is the link between the table and your server. It holds the table state you control, and it translates that state into the params your API expects. The table state is always the same. The params are whatever your server wants, so you shape this hook to your server.
Start small. If your server only takes `page`, `limit` and `search`, this is the whole hook:
```ts title="hooks/useTableQueryParams.ts"
export function useTableQueryParams({ pageSize = 10 } = {}) {
const [pagination, setPagination] = useState({
pageIndex: 0,
pageSize,
});
const [search, setSearch] = useState('');
const handlers = {
onPaginationChange: setPagination,
onGlobalFilterChange: (value: string | undefined) => {
setSearch(value ?? '');
setPagination((prev) => ({ ...prev, pageIndex: 0 }));
},
};
const params = {
page: pagination.pageIndex + 1, //table is 0-based, server is 1-based
limit: pagination.pageSize,
search: search || undefined,
};
return { params, handlers, state: { pagination, globalFilter: search } };
}
```
Then grow it. The full version adds sorting, column filters and filter modes. Each change handler sends you back to the first page:
```ts title="hooks/useTableQueryParams.ts"
//filter values can be strings, numbers or Dates (date filters)
const toParam = (value: unknown) =>
value instanceof Date ? value.toISOString() : String(value);
export function useTableQueryParams({
columns,
pageSize = 10,
}: {
columns: ST_ColumnDef[];
pageSize?: number;
}) {
const [pagination, setPagination] = useState({
pageIndex: 0,
pageSize,
});
const [search, setSearch] = useState('');
const [sorting, setSorting] = useState([]);
const [columnFilters, setColumnFilters] = useState(
[],
);
//one filter mode per column, from the column's filterFn or 'contains'
const [columnFilterFns, setColumnFilterFns] =
useState(() =>
Object.fromEntries(
columns.map((column) => [
column.id ?? String(column.accessorKey),
typeof column.filterFn === 'string' ? column.filterFn : 'contains',
]),
),
);
//new search, sort or filter: the old page number means nothing, go back to page 1
const resetPageIndex = () =>
setPagination((prev) => ({ ...prev, pageIndex: 0 }));
const handlers = {
onPaginationChange: setPagination,
onGlobalFilterChange: (value: string | undefined) => {
setSearch(value ?? '');
resetPageIndex();
},
onSortingChange: (updater: ST_Updater) => {
setSorting(updater);
resetPageIndex();
},
onColumnFiltersChange: (updater: ST_Updater) => {
setColumnFilters(updater);
resetPageIndex();
},
onColumnFilterFnsChange: (
updater: ST_Updater,
) => {
setColumnFilterFns(updater);
resetPageIndex();
},
};
//translate table state into the params YOUR server accepts
const params: UsersQueryParams = {
page: pagination.pageIndex + 1, //table is 0-based, server is 1-based
limit: pagination.pageSize,
search: search || undefined,
sortBy: sorting.map(({ id, desc }) => `${id}:${desc ? 'desc' : 'asc'}`),
filter: columnFilters.map(
({ id, value }) =>
`${id}[$${columnFilterFns[id] ?? 'contains'}]=${toParam(value)}`,
),
};
return {
params,
handlers,
state: {
pagination,
globalFilter: search,
sorting,
columnFilters,
columnFilterFns,
},
};
}
```
The table calls each `on*Change` handler with either a value or an updater function. `useState` setters accept both, so you can pass the updater straight through. If you need the new value first, unwrap it as shown in [State Management](/docs/guides/state-management#add-side-effects-in-set-state-callbacks).
**Shape it to your server.** Only the `params` block changes from one backend to another:
| Table state | Demo server param | Other common shapes |
| ----------------------------------- | ----------------------------- | --------------------------------------- |
| `pagination.pageIndex` (0-based) | `page` (1-based) | `offset = pageIndex * pageSize`, `skip` |
| `pagination.pageSize` | `limit` | `size`, `per_page`, `take` |
| `globalFilter` | `search` | `q`, `query` |
| `sorting` | `sortBy=lastName:desc` | `sort=-lastName`, `orderBy` + `order` |
| `columnFilters` + `columnFilterFns` | `filter=state[$contains]=tex` | `state=tex`, `filters` as JSON |
If your server has no filter operators, drop `columnFilterFns` and send `field=value`.
### Build the Table [#build-the-table]
Define your columns the regular way. Then wire the hooks into the table:
```tsx title="components/UsersTable.tsx"
const { params, handlers, state } = useTableQueryParams({ columns });
const { data, isError, isFetching, isLoading, refetch } = useUsersQuery(params);
const table = useShadcnTable({
columns,
data: data?.data ?? [],
rowCount: data?.total ?? 0, //the server knows the total, the table does not
manualPagination: true, //turn off client-side pagination
manualSorting: true, //turn off client-side sorting
manualFiltering: true, //turn off client-side filtering
enableColumnFilterModes: true,
//only the operators the server understands
columnFilterModeOptions: [
'contains',
'startsWith',
'endsWith',
'equals',
'notEquals',
],
...handlers,
stToolbarAlertBannerProps: isError
? {
className: 'border-destructive text-destructive',
children: 'Error loading data',
}
: undefined,
state: {
...state,
isLoading, //first load only: skeleton rows
showProgressBars: isFetching, //every later request: thin progress bar
showAlertBanner: isError,
},
});
```
- **`manual*` flags:** the rows you pass are already paged, sorted and filtered. Without these flags, the table does it again on one page of data.
- **`rowCount`:** the table only holds one page, so it cannot count the rows. The server tells it.
- **State and handlers:** pass both. If you pass a handler but not its state, the table never sees the change. See [State Management](/docs/guides/state-management#manage-individual-states-as-needed).
- **Loading:** `isLoading` is true only on the first load, when there is no data yet. Later requests keep the old page (thanks to `keepPreviousData`) and show a progress bar.
- **Errors:** `showAlertBanner` shows the error above the table. Without it, a failed request just looks like an empty table.
The search box and the text column filters already debounce their input when `manualFiltering` is on (500ms for search, 400ms for filters), so you don't need your own debounce.
### Adapting to Your Server [#adapting-to-your-server]
**Extra params that aren't table state.** Merge them in before you call the query hook:
```ts
const { params, handlers, state } = useTableQueryParams({ columns });
const query = useUsersQuery({ ...params, organizationId });
```
Add `organizationId` to `UsersQueryParams` so it ends up in the `queryKey` too.
**A fixed filter the user can't remove.** Prepend it to the translated filters:
```ts
const query = useUsersQuery({
...params,
filter: ['status[$equals]=active', ...(params.filter ?? [])],
});
```
**Pagination only.** Turn off the features your server doesn't support, and use the minimal hook:
```ts
useShadcnTable({
columns,
data: data?.data ?? [],
rowCount: data?.total ?? 0,
manualPagination: true,
enableSorting: false,
enableGlobalFilter: false,
enableColumnFilters: false,
...handlers,
state: { ...state, isLoading, showProgressBars: isFetching },
});
```
**Refresh after a mutation.** Invalidate by the key prefix. That marks every page, sort and filter combination as stale:
```ts
const queryClient = useQueryClient();
useMutation({
mutationFn: updateUser,
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }),
});
```
### Common Mistakes [#common-mistakes]
- **Not resetting the page.** You are on page 7, you type a search, and the result has 2 pages. Page 7 is empty. Reset `pageIndex` to 0 in every search, sort and filter handler.
- **Mixing up 0-based and 1-based pages.** The table's `pageIndex` starts at 0. Most servers start at 1. Convert in one place: the params block.
- **Passing `isFetching` as `isLoading`.** The whole table turns into skeletons on every page change. Use `isLoading` for the first load and `showProgressBars` for the rest.
- **Hiding errors.** Set `showAlertBanner` from `isError`, and make the service throw on a bad status.
- **Leaving params out of the `queryKey`.** The query never refetches, and the table is stuck on page 1.
- **Forgetting `rowCount`.** The pagination thinks there is only one page.
- **Adding your own debounce.** The built-in search and filter inputs already debounce in manual mode. A second debounce just adds lag.
### Related [#related]
- [State Management](/docs/guides/state-management)
- [Pagination: Manual or Server-Side Pagination](/docs/guides/pagination#manual-or-server-side-pagination)
- [Sorting: Manual Server-Side Sorting](/docs/guides/sorting#manual-server-side-sorting)
- [Column Filtering: Manual Server-Side Column Filtering](/docs/guides/column-filtering#manual-server-side-column-filtering)
- [Global Filtering: Manual Server-Side Global Filtering](/docs/guides/global-filtering#manual-server-side-global-filtering)
- [React Query Example](/docs/examples/react-query)
# Row Actions Guide (/docs/guides/row-actions)
The row actions feature is simply a pre-built [Display Column](/docs/guides/display-columns) feature that adds a column as a place to store either a row action menu or other row action buttons.
Using the built-in Row Actions column is optional, as you can simply create your own display columns, but this feature has some built-in conveniences that make it easy to add row actions to your table.
### Relevant Table Options [#relevant-table-options]
### Enable Row Actions [#enable-row-actions]
You can enable the row actions feature by setting the `enableRowActions` table option to `true`.
You can then add your row action components with either the `renderRowActions` or `renderRowActionMenuItems` props.
#### Add Row Actions Menu Items [#add-row-actions-menu-items]
If you want to embed all of your row actions into a single menu, you can use the `renderRowActionMenuItems` table option.
```jsx
const table = useShadcnTable({
columns,
data,
enableRowActions: true,
renderRowActionMenuItems: ({ row }) => [
console.info('Edit')}>
Edit
,
console.info('Delete')}>
Delete
,
],
});
return ;
```
#### Add Row Action Buttons [#add-row-action-buttons]
If you want to add row action buttons, you can use the `renderRowActions` table option.
```jsx
const table = useShadcnTable({
columns,
data,
enableRowActions: true,
renderRowActions: ({ row }) => (
console.info('Edit')}>
console.info('Delete')}>
),
});
return ;
```
### Customize Row Actions Column [#customize-row-actions-column]
#### Change Row Actions Display Column Options [#change-row-actions-display-column-options]
You can customize all column def options for the row actions column, including the column width, header text, and more using the `displayColumnDefOptions` table option.
```jsx
const table = useShadcnTable({
columns,
data,
enableRowActions: true,
displayColumnDefOptions: {
'st-row-actions': {
header: 'Change Account Settings', //change header text
size: 300, //make actions column wider
},
},
renderRowActions: ({ table }) => (
Button 1
Button 2
Button 3
),
});
```
#### Position Row Actions Column [#position-row-actions-column]
You can position the row actions column to the left or right (first or last column) of the table using the `positionActionsColumn` table option. The default is as the first column.
```jsx
const table = useShadcnTable({
columns,
data,
enableRowActions: true,
positionActionsColumn: 'last',
renderRowActions: ({ table }) => (
//...
),
})
```
View Extra Storybook **Examples **
# Row Numbers Guide (/docs/guides/row-numbers)
Shadcn Table has an easy to implement row number features. There are two row number modes that you can enable. You can have row numbers that are associated with the data in the table ([original mode](<#enable-row-numbers-(original-mode)>)), or you can have row numbers that are just statically part of the table ([static mode](<#enable-row-numbers-(static-mode)>)).
### Relevant Table Options [#relevant-table-options]
### Enable Row Numbers (Static Mode) [#enable-row-numbers-static-mode]
In the default rowNumberDisplayMode (`static`), row numbers are just a static part of the table in their own column. They act like the row numbers in an excel spreadsheet. Sorting and filtering will not affect the row numbers.
### Enable Row Numbers (Original Mode) [#enable-row-numbers-original-mode]
Alternatively, use the `"original"` rowNumberDisplayMode to have row numbers linked to the original index of the data array. This means that when you search or filter, the same row numbers will stay with the same rows as data is sorted and filtered.
View Extra Storybook **Examples **
# Row Ordering/DnD Guide (/docs/guides/row-ordering-dnd)
Shadcn Table has exposed all the APIs necessary to enable rich row drag-and-drop features that you can easily build to meet your needs. This includes the ability to reorder rows, drag rows to other tables, or drag rows to other UI in your application.
> This is not the [Sorting Guide](/docs/guides/sorting), which is a different feature.
### Relevant Table Options [#relevant-table-options]
### Relevant State [#relevant-state]
### Enable Row Ordering [#enable-row-ordering]
A common use for row drag and drop is to allow users to reorder rows in a table. This can be done by setting the `enableRowOrdering` table option to `true`, then setting up an `onDragEnd` event handler on the `stRowDragHandleProps` table option.
```jsx
const table = useShadcnTable({
columns,
data,
enableRowOrdering: true,
enableSorting: false, //usually you do not want to sort when re-ordering
stRowDragHandleProps: {
onDragEnd: (event, data) => {
//data re-ordering logic here
},
},
});
return ;
```
### Drag and Drop Rows to Other UI or Tables [#drag-and-drop-rows-to-other-ui-or-tables]
The drag-and-drop features are not limited to just internally within the same table. You can also use them to drag rows to other UI elements in your application or even to other tables. This can be done by setting the `enableRowDragging` table option to `true` and setting up an `onDragEnd` event handler on the `stRowDragHandleProps` table option to perform whatever logic you want to happen when a row is dropped.
View Extra Storybook **Examples **
# Row Pinning Guide (/docs/guides/row-pinning)
Shadcn Table has a powerful row pinning feature that allows you to either pin rows to the top and bottom of the table as a user scrolls, or pin rows to the top and bottom of the table regardless of scrolling.
### Relevant Table Options [#relevant-table-options]
### Relevant State [#relevant-state]
### Row Pinning Display Modes [#row-pinning-display-modes]
You can enable the row pinning feature with the `enableRowPinning` table option. There are multiple display modes that you can choose from with the `rowPinningDisplayMode` table option.
```jsx
const table = useShadcnTable({
columns,
data,
enableRowPinning: true,
rowPinningDisplayMode: 'sticky', // default
});
```
- `sticky` - This is the default display mode. It will make rows stick to the top and bottom of the table as a user scrolls.
- `top` - This will statically pin rows to the top of the table upon pinning.
- `bottom` - This will statically pin rows to the bottom of the table upon pinning.
- `top-and-bottom` - This allows the user to statically pin rows to either the top or bottom of the table.
- `select-sticky` - This pins selected rows to the top and bottom of the table as a user scrolls. (alternative pinning UI)
- `select-top` - This statically pins selected rows to the top of the table upon pinning. (alternative pinning UI)
- `select-bottom` - This statically pins selected rows to the bottom of the table upon pinning. (alternative pinning UI)
### Sticky Row Pinning [#sticky-row-pinning]
By default, the row pinning feature will add a position sticky style to the pinned rows. This means that the pinned rows will stay in place if the row is already in view. However, the rows will stay in view as the user scrolls up or down.
Sticky row pinning will work best if you give your rows a fixed height, or at least disable any kind of whitespace wrapping.
```jsx
const table = useShadcnTable({
columns,
data,
enableRowPinning: true,
stTableBodyRowProps: ({ row, table }) => {
const { density } = table.state;
return {
style: {
//Set a fixed height for pinned rows
height: row.getIsPinned()
? `${
//Default st row height estimates. Adjust as needed.
density === 'compact' ? 37 : density === 'comfortable' ? 53 : 69
}px`
: undefined,
},
};
},
rowPinningDisplayMode: 'sticky', // default
});
```
> Note: Sticky Row Pinning is not currently compatible with row virtualization.
### Static Row Pinning [#static-row-pinning]
Some of the other row pinning modes will show pinned rows at the top or bottom of the the table regardless of scrolling. This is a simpler implementation of the row pinning feature.
Static row pinning does not require set row heights, as they will render in a normal ` ` element that itself is what is sticky.
### Alternative Selection Row Pinning UI [#alternative-selection-row-pinning-ui]
By default, the row pinning feature adds a pinning display column with pinning buttons for each row. However, you can instead combine the row pinning feature with the row selection feature to pin selected rows.
```jsx
const table = useShadcnTable({
columns,
data,
enableRowPinning: true,
enableRowSelection: true,
rowPinningDisplayMode: 'select-sticky',
});
```
# Row Selection Guide (/docs/guides/row-selection)
Shadcn Table has a built-in row selection feature and makes it easy to manage the selection state yourself. This guide demonstrates how to enable row selection and customize the selection behavior.
### Relevant Table Options [#relevant-table-options]
### Relevant State [#relevant-state]
### Enable Row Selection [#enable-row-selection]
Selection checkboxes can be enabled with the `enableRowSelection` table option.
```jsx
const table = useShadcnTable({
columns,
data,
enableRowSelection: true,
});
return ;
```
#### Enable Row Selection Conditionally Per Row [#enable-row-selection-conditionally-per-row]
You can also enable row selection conditionally per row with the same `enableRowSelection` table option, but with a callback function instead of a boolean.
```jsx
const table = useShadcnTable({
columns,
data,
enableRowSelection: (row) => row.original.age > 18, //disable row selection for rows with age <= 18
});
```
### Batch Row Selection [#batch-row-selection]
By default, when the user holds down the Shift key and clicks a row, all rows between the last selected row and the clicked row will be selected. This is TanStack Table's range selection; it can be disabled with the `enableRowRangeSelection` table option, and the `isRowRangeSelectionEvent` option decides which events count as a range selection.
```jsx
const table = useShadcnTable({
columns,
data,
enableRowSelection: true,
enableRowRangeSelection: false, //disable batch row selection with shift key
});
```
### Access Row Selection State [#access-row-selection-state]
There are two ways to access the selection state. You can either manage the selection state yourself or read it from the table instance.
#### Manage Row Selection State [#manage-row-selection-state]
The row selection state is managed internally by default, but you will more than likely want to have access to that state yourself. Here is how you can simply get access to the row selection state, specifically:
```tsx
const [rowSelection, setRowSelection] = useState({}); //ts type available
const table = useShadcnTable({
columns,
data,
enableRowSelection: true,
onRowSelectionChange: setRowSelection,
state: { rowSelection },
});
return ;
```
#### Read Row Selection State from Table Instance [#read-row-selection-state-from-table-instance]
Alternatively, you can read the selection state directly from the `table` instance like so:
```jsx
const table = useShadcnTable({
columns,
data,
enableRowSelection: true,
renderTopToolbarCustomActions: ({ table }) => (
{
const rowSelection = table.state.rowSelection; //read state
const selectedRows = table.getSelectedRowModel().rows; //or read entire rows
}}
>
Download Selected Users
),
});
useEffect(() => {
//fetch data based on row selection state or something
}, [table.state.rowSelection]);
return ;
```
> Note: `table.getSelectedRowModel().rows` only really works with client-side pagination. Use `rowSelection` state if you are using server-side pagination. Row Models only contain rows based on the `data` you pass in.
#### Useful Row IDs [#useful-row-ids]
By default, the `row.id` for each row in the table is simply the index of the row in the table. You can override this and instruct Shadcn Table to use a more useful Row ID with the `getRowId` table option. For example, you may want something like this:
```jsx
const table = useShadcnTable({
columns,
data,
enableRowSelection: true,
getRowId: (originalRow) => originalRow.userId,
});
```
As rows get selected, the `rowSelection` state will look like this:
```tsx
{
"3f25309c-8fa1-470f-811e-cdb082ab9017": true,
"be731030-df83-419c-b3d6-9ef04e7f4a9f": true,
...
}
```
This can be very useful when you are trying to read your selection state and do something with your data as the row selection changes.
### Select Row on Row Click [#select-row-on-row-click]
By default, a row can only be selected by either clicking the checkbox or radio button in the `st-row-select` column. If you want to be able to select a row by clicking anywhere on the row, you can add your own `onClick` function to a table body row like this:
```jsx
const table = useShadcnTable({
columns,
data,
enableRowSelection: true,
//clicking anywhere on the row will select it
stTableBodyRowProps: ({ row, staticRowIndex, table }) => ({
onClick: (event) =>
getST_RowSelectionHandler({ row, staticRowIndex, table })(event), //import this helper function from shadcn-table
style: { cursor: 'pointer' },
}),
});
```
### Disable Select All [#disable-select-all]
By default, if you enable selection for each row, there will also be a select all checkbox in the header of the checkbox column. It can be hidden with the `enableSelectAll` table option.
```tsx
const table = useShadcnTable({
columns,
data,
enableRowSelection: true,
enableSelectAll: false,
});
```
### Position or Hide Alert Banner Selection Message [#position-or-hide-alert-banner-selection-message]
By default, an alert banner will be show as rows are selected. You can use the `positionToolbarAlertBanner` table option to show the alert banner in the top toolbar, bottom toolbar, as an overlay in the table head, or hide it completely.
```jsx
const table = useShadcnTable({
columns,
data,
enableRowSelection: true,
positionToolbarAlertBanner: 'head-overlay', //show alert banner over table head, top toolbar, or bottom toolbar
});
```
### Single Row Selection [#single-row-selection]
By default, the `enableMultiRowSelection` table option is set to `true`, which means that multiple rows can be selected at once with a checkbox. If you want to only allow a single row to be selected at a time, you can set this table option to `false` and a radio button will be used instead of a checkbox.
```jsx
const table = useShadcnTable({
columns,
data,
enableMultiRowSelection: false, //shows radio buttons instead of checkboxes
enableRowSelection: true,
});
```
### Customize Select Checkboxes or Radio Buttons [#customize-select-checkboxes-or-radio-buttons]
The selection checkboxes can be customized with the `stSelectCheckboxProps` table option. Any prop that can be passed to a shadcn/ui Checkbox component can be specified here. For example, you may want to use a different color for the checkbox or use some logic to disable certain rows from being selected.
```tsx
const table = useShadcnTable({
columns,
data,
enableRowSelection: true,
stSelectCheckboxProps: {
color: 'secondary',
},
});
```
### Manual Row Selection Without Checkboxes [#manual-row-selection-without-checkboxes]
You may have a use case where you want to be able to select rows by clicking them, but you do not want to show any checkboxes or radio buttons. You can do this by implementing a row selection feature yourself while keeping the `enableRowSelection` table option `false` so that the default selection behavior is disabled.
### Sticky Pinned Row Selection [#sticky-pinned-row-selection]
You can combine the row pinning feature with the row selection feature to create a cool user experience where selected rows always stay within view.
View Extra Storybook **Examples **
# Sorting Guide (/docs/guides/sorting)
Shadcn Table supports almost any sorting scenario you may have. Client-side sorting is enabled by default, but you can opt to implement your own server-side sorting logic or even replace the default client-side sorting with your own implementation.
### Relevant Table Options [#relevant-table-options]
### Relevant Column Options [#relevant-column-options]
### Relevant State Options [#relevant-state-options]
### Disable Sorting [#disable-sorting]
Sorting can be disabled globally by setting the `enableSorting` table option to `false`. This will disable sorting for all columns. You can also disable sorting for individual columns by setting the `enableSorting` column option to `false`.
```tsx
const columns = [
{
accessorKey: 'name',
header: 'Name',
enableSorting: false, // disable sorting for this column
},
];
const table = useShadcnTable({
columns,
data,
enableSorting: false, //disable sorting for all columns
});
```
### Initial/Default Sorting [#initialdefault-sorting]
You can sort by a column or multiple columns by default by setting the `sorting` state option in either the `initialState` or `state` props.
```jsx
const table = useShadcnTable({
columns,
data,
initialState: {
sorting: [
{
id: 'age', //sort by age by default on page load
desc: true,
},
{
id: 'lastName', //then sort by lastName if age is the same
desc: true,
},
],
},
});
```
### Default Sorting Features [#default-sorting-features]
Client-side sorting is enabled by default. When sorting is toggled on for a column, the table will be sorted by the [`basic` sorting algorithm](https://tanstack.com/table/latest/docs/framework/react/guide/sorting#sorting-rowmodelfns) by default.
#### Multi-Sorting [#multi-sorting]
Multi-sorting is also enabled by default, which means you can sort by multiple columns at once. You can do this by clicking on a column header while holding down the `shift` key. The table will then be sorted by the previously sorted column, followed by the newly clicked column. Alternatively, if you want multi-sorting to be the default click behavior without the need to hold `shift`, you can set the `isMultiSortEvent` table option to `() => true`.
```jsx
const table = useShadcnTable({
columns,
data,
isMultiSortEvent: () => true, //multi-sorting will be the default click behavior without the need to hold shift
});
```
You can limit the number of columns that can be sorted at once by setting the `maxMultiSortColCount` prop, or you can disable multi-sorting entirely by setting the `enableMultiSort` table option to `false`.
#### Sorting Removal [#sorting-removal]
By default, users can remove a sort on a column by clicking through the sort direction options or selecting "Clear Sort" from the column actions menu. You can disable this feature by setting the `enableSortingRemoval` table option to `false`.
```jsx
const table = useShadcnTable({
columns,
data,
enableSortingRemoval: false, //users will not be able to remove a sort on a column
});
```
#### Sort Direction [#sort-direction]
By default, columns with `string` datatypes will sort alphabetically in ascending order, but columns with `number` datatypes will sort numerically in descending order. You can change the default sort direction per column by specifying the `sortDescFirst` column option to either `true` or `false`. You can also change the default sort direction globally by setting the `sortDescFirst` table option to either `true` or `false`.
### Sorting Functions [#sorting-functions]
By default, Shadcn Table will use the `basic` sorting function for all columns.
There are six built-in sorting functions you can choose from: ` alphanumeric`, `alphanumericCaseSensitive`, `text`, `textCaseSensitive`, `datetime`, and `basic`. You can learn more about these built-in sorting functions in the [TanStack Table Sorting docs](https://tanstack.com/table/latest/docs/framework/react/guide/sorting#sorting-rowmodelfns).
#### Add Custom Sorting Functions [#add-custom-sorting-functions]
If none of these sorting functions meet your needs, you can add your own custom sorting functions by specifying more sorting functions in the `sortFns` table option.
```jsx
const table = useShadcnTable({
columns,
data,
sortFns: {
//will add a new sorting function to the list of other sorting functions already available
myCustomSortingFn: (rowA, rowB, columnId) => // your custom sorting logic
},
})
```
#### Change Sorting Function Per Column [#change-sorting-function-per-column]
You can now choose a sorting function for each column by either passing a string value of the built-in sorting function names to the `sortFn` column option or by passing a custom sorting function to the `sortFn` column option.
```tsx
const columns = [
{
accessorKey: 'name',
header: 'Name',
sortFn: 'textCaseSensitive', //use the built-in textCaseSensitive sorting function instead of the default basic sorting function
},
{
accessorKey: 'age',
header: 'Age',
//use your own custom sorting function instead of any of the built-in sorting functions
sortFn: (rowA, rowB, columnId) => // your custom sorting logic
},
];
```
### Manual Server-Side Sorting [#manual-server-side-sorting]
If you are working with large data sets, you may want to let your back-end APIs handle all of the sorting and pagination processing instead of doing it client-side. You can do this by setting the `manualSorting` table option to `true`. This will disable the default client-side sorting and pagination features and will let you implement your own sorting and pagination logic.
> When `manualSorting` is set to `true`, Shadcn Table assumes that your `data` is already sorted by the time you are passing it to the table.
If you need to sort your data in a back-end API, then you will also probably need access to the internal `sorting` state from the table. You can do this by managing the `sorting` state yourself and then passing it to the table via the `state` table option. You can also pass a callback function to the `onSortingChange` prop, which will be called whenever the `sorting` state changes internally in the table
```jsx
const [sorting, setSorting] = useState([]);
const table = useShadcnTable({
columns,
data,
manualSorting: true,
state: { sorting },
onSortingChange: setSorting,
});
useEffect(() => {
//do something with the sorting state when it changes
//or use table.state.sorting
}, [sorting]);
return ;
```
#### Remote Sorting Example [#remote-sorting-example]
Here is the full Remote Data example showing how to implement server-side sorting, filtering, and pagination with Shadcn Table.
View Extra Storybook **Examples **
# State Management Guide (/docs/guides/state-management)
Shadcn Table does not try to hide any of its internal state from you. You can initialize state with custom initial values, manage individual states yourself as you discover the need to have access to them, or read any of the state from the table instance itself.
This is all optional, of course. If you do not need access to any of the internal state, you do not need to do anything and it will just automatically be managed internally.
See the [State Options API Docs](/docs/api/state-options) for more information on which states are available for you to manage.
### Relevant Table Options [#relevant-table-options]
### Populate Initial State [#populate-initial-state]
If all you care about is setting parts of the initial or default state when the table mounts, then you may be able to specify that state in the `initialState` table option and not have to worry about managing the state yourself.
For example, let's say you do not need access to the `showColumnFilters` state, but you want to set the default value to `true` when the table mounts. You can do that with the `initialState` table option:
```jsx
const table = useShadcnTable({
columns,
data,
initialState: {
density: 'xs', //set default density to compact
expanded: true, //expand all rows by default
pagination: { pageIndex: 0, pageSize: 15 }, //set different default page size
showColumnFilters: true, //show filters by default
sorting: [{ id: 'name', desc: false }], //sort by name ascending by default
},
});
return ;
```
> Note: If you use both `initialState` and `state`, the state initializer in `state` table option will take precedence and overwrite the same state values in `initialState`. So just use either `initialState` or `state`, not both for the same states.
### Manage Individual States as Needed [#manage-individual-states-as-needed]
It is pretty common to need to manage certain state yourself, so that you can react to changes in that state, or have easy access to it when sending it to an API.
You can pass in any state that you are managing yourself to the `state` table option, and it will be used instead of the internal state. Each state property option also has a corresponding `on[StateName]Change` callback that you can use set/update your managed state as it changes internally in the table.
For example, let's say you need to store the pagination, sorting, and row selection states in a place where you can easily access it in order to use it in parameters for an API call.
```jsx
const [pagination, setPagination] = useState({
pageIndex: 0,
pageSize: 15, //set different default page size by initializing the state here
});
const [rowSelection, setRowSelection] = useState({});
const [sorting, setSorting] = useState([{ id: 'name', desc: false }]);
//see example at bottom of page for alternatives to useEffect here
useEffect(() => {
//do something when the pagination state changes
}, [pagination]);
const table = useShadcnTable({
columns,
data,
getRowId: (originalRow) => row.username,
onPaginationChange: setPagination,
onRowSelectionChange: setRowSelection,
onSortingChange: setSorting,
state: { pagination, rowSelection, sorting }, //must pass states back down if using their on[StateName]Change callbacks
});
return ;
```
### Add Side Effects in Set State Callbacks [#add-side-effects-in-set-state-callbacks]
In React 18 and beyond, it is becoming more discouraged to use `useEffect` to react to state changes, because in React Strict Mode (and maybe future versions of React), the useEffect hook may run twice per render. Instead, more event driven functions are recommended to be used. Here is an example for how that looks here. The callback signature for the `on[StateName]Change` works just like a React setState callback from the `useState` hook. This means that you have to check if the updater is a function or not, and then call the setState function with the updater callback if it is a function.
```tsx
const [pagination, setPagination] = useState({
pageIndex: 0,
pageSize: 15,
});
const handlePaginationChange = (updater: ST_Updater) => {
//call the setState as normal, but need to check if using an updater callback with a previous state
setPagination((prevPagination) =>
//if updater is a function, call it with the previous state, otherwise just use the updater value
updater instanceof Function ? updater(prevPagination) : updater,
);
//put more code for your side effects here, guaranteed to only run once, even in React Strict Mode
};
const table = useShadcnTable({
columns,
data,
onPaginationChange: handlePaginationChange,
state: { pagination },
});
return ;
```
### Read From the Table Instance [#read-from-the-table-instance]
The `useShadcnTable` hook returns the table instance. The ` ` needs the table instance for all of its internal logic, but you can also use it for your own purposes.
```jsx
const table = useShadcnTable({
columns,
data,
//...
});
const someEventHandler = (event) => {
console.info(table.getRowModel().rows); //example - get access to all page rows in the table
console.info(table.getSelectedRowModel()); //example - get access to all selected rows in the table
console.info(table.state.sorting); //example - get access to the current sorting state without having to manage it yourself
};
return (
Export or Something
);
```
The table instance is the same object that you will also see as a provided parameter in many of the other callback functions throughout Shadcn Table, such as all the `render...` props or the `Cell` or `Header` render overrides in the column definition options.
```jsx
const columns = useMemo(
() => [
{
Header: 'Name',
accessor: 'name',
Cell: ({ cell, table }) => {cell.getValue()} ,
//The `table` parameter from the Cell option params and the `table` are the same object
},
],
[],
);
const table = useShadcnTable({
columns,
data,
renderTopToolbarCustomActions: ({ table }) => {
//The `table` parameter here and the table returned from the hook are the same object
return Button ;
},
});
return ;
```
### Persistent State [#persistent-state]
Persistent state is not a built-in feature of Shadcn Table, but it is an easy feature to implement yourself using the above patterns with the `state` table option and the `on[StateName]Change` callbacks. Here is an example of how you might implement persistent state using `sessionStorage`:
# Sticky Header/Footer Guide (/docs/guides/sticky-header)
The sticky header and footer feature allows you to keep the header and footer of the table visible while scrolling through the table. This is useful when you have a large table and want to keep the header and footer visible at all times.
### Relevant Table Options [#relevant-table-options]
### Enable Sticky Header [#enable-sticky-header]
Enabling the sticky header is as simple as setting the `enableStickyHeader` table option to `true`. This will make the header of the table stick to the top and remain visible while scrolling through the table.
When the sticky header is enabled, you will probably also want to give the table a maxHeight so that the table can scroll vertically and keep the header visible. You can do this by styling the table container with the `stTableContainerProps` table option.
> If no maxHeight is specified, the table container will default to a `100vh` maxHeight when `enableStickyHeader` is enabled.
```tsx
```
### Enable Sticky Footer [#enable-sticky-footer]
Similarly, enabling the sticky footer is as simple as setting the `enableStickyFooter` table option to `true`. This will make the footer of the table stick to the bottom of the table and always be visible, even before the table is scrolled to the bottom.
```tsx
```
### Enable Sticky Header and Footer Demo [#enable-sticky-header-and-footer-demo]
View Extra Storybook **Examples **
# Table Event Listeners Guide (/docs/guides/table-event-listeners)
You can pretty much add ANY event listener to ANY component in Shadcn Table.
You can do this by passing props to any of the `st...Props` props. This guide gives a few examples of the most common use cases, but there are limitless possibilities.
### Add Event Listeners to any of the shadcn/ui Components [#add-event-listeners-to-any-of-the-shadcnui-components]
In the [customize components docs](/docs/guides/customize-components#props-and-types), we explained how to pass any prop you need to pass to any exposed shadcn/ui component that is in the table. The list of props that you can pass to shadcn/ui components includes any event listener.
Here are a few common examples of some useful event listeners you might want to add to the table, although the possibilities are countless.
#### Add an onClick to a Table Row [#add-an-onclick-to-a-table-row]
```jsx
const table = useShadcnTable({
columns,
data,
stTableBodyRowProps: ({ row }) => ({
onClick: (event) => {
console.info(event, row.id);
},
style: {
cursor: 'pointer', //you might want to change the cursor too when adding an onClick
},
}),
});
```
#### Add an onDoubleClick to a Table Cell [#add-an-ondoubleclick-to-a-table-cell]
```jsx
const table = useShadcnTable({
columns,
data,
stTableBodyCellProps: ({ cell }) => ({
onDoubleClick: (event) => {
console.info(event, cell.id);
},
}),
});
```
#### Add an onBlur to an Edit TextField [#add-an-onblur-to-an-edit-textfield]
```jsx
//add to every edit text field
const table = useShadcnTable({
columns,
data,
stEditTextFieldProps: ({ cell }) => ({
onBlur: (event) => {
console.info(event, cell.id);
},
}),
});
```
```jsx
//or add to just edit text fields in a specific column
const columns = [
{
accessorKey: 'name',
header: 'Name',
stEditTextFieldProps: ({ cell }) => ({
onBlur: (event) => {
console.info(event);
},
}),
},
];
```
#### Add an onChange to an Edit TextField [#add-an-onchange-to-an-edit-textfield]
> Warning: Be careful when using onChange, as it can trigger re-renders with every keystroke, which can lead to performance issues
```jsx
const table = useShadcnTable({
columns,
data,
stEditTextFieldProps: ({ cell }) => ({
onChange: (event) => {
console.info(event, cell.id);
},
}),
});
```
```jsx
//or add to just edit text fields in a specific column
const columns = [
{
accessorKey: 'name',
header: 'Name',
stEditTextFieldProps: ({ cell }) => ({
onChange: (event) => {
console.info(event);
},
}),
},
];
```
# Toolbar Customization Guide (/docs/guides/toolbar-customization)
This guide shows you how to [hide](#hide-or-disable-toolbars), [customize](#customize-toolbar-buttons), or [override](#override-with-custom-toolbar-components) the top and bottom toolbars in Shadcn Table.
### Relevant Table Options [#relevant-table-options]
### Relevant State [#relevant-state]
### Hide or Disable Toolbars [#hide-or-disable-toolbars]
There are `enableTopToolbar` and `enableBottomToolbar` table options that you can use to show or hide the toolbars.
```jsx
const table = useShadcnTable({
data,
columns,
enableTopToolbar: false,
enableBottomToolbar: false,
});
return ;
```
Alternatively, you could just use a different ST component that does not have the toolbars built-in. For example, use the ` ` or ` ` components instead of the ` ` component.
```jsx
const table = useShadcnTable({
data,
columns,
});
//This ST sub-component does not contain the code for the toolbars. ST_TableLayout and ShadcnTable do.
return ;
```
#### No Toolbars Example [#no-toolbars-example]
### Customize Toolbar buttons [#customize-toolbar-buttons]
Everything in the toolbars are customizable. You can add your own buttons or change the order of the built-in buttons.
#### Customize Built-In Internal Toolbar Button Area [#customize-built-in-internal-toolbar-button-area]
The `renderToolbarInternalActions` table option allows you to redefine the built-in buttons that usually reside in the top right of the top toolbar. You can reorder the icon buttons or even insert your own custom buttons. All of the built-in buttons are available to be imported from '@/components/ui/shadcn-table'.
```jsx
import {
ShadcnTable,
ST_ShowHideColumnsButton,
ST_ToggleFullScreenButton,
} from '@/components/ui/shadcn-table';
const table = useShadcnTable({
data,
columns,
renderToolbarInternalActions: ({ table }) => (
<>
{/* add your own custom print button or something */}
showPrintPreview(true)}>
{/* built-in buttons (must pass in table prop for them to work!) */}
>
),
});
return ;
```
#### Add Custom Toolbar Buttons/Components [#add-custom-toolbar-buttonscomponents]
The `renderTopToolbarCustomActions` and `renderBottomToolbarCustomActions` table options allow you to add your own custom buttons or components to the top and bottom toolbar areas. These props are functions that return a ReactNode. You can add your own buttons or whatever components you want.
In all of these `render...` table options, you get access to the `table` instance that you can use to perform actions or extract data from the table.
```jsx
const table = useShadcnTable({
data,
columns,
enableRowSelection: true,
//Simply adding a table title to the top-left of the top toolbar
renderTopToolbarCustomActions: () => (
Customer's Table
),
//Adding a custom button to the bottom toolbar
renderBottomToolbarCustomActions: ({ table }) => (
handleDownloadRows(table.getSelectedRowModel().rows)}
>
Download Selected Rows
),
});
return ;
```
#### Custom Toolbar Actions Example [#custom-toolbar-actions-example]
### Position Toolbar Areas [#position-toolbar-areas]
The `positionToolbarAlertBanner`, `positionGlobalFilter`, `positionPagination`, and `positionToolbarDropZone` table options allow you to swap the default position of certain areas of the toolbars. Experiment moving them around until you find a layout that works for you.
```jsx
const table = useShadcnTable({
data,
columns
//if rendering top toolbar buttons, sometimes you want alerts to be at the bottom
positionToolbarAlertBanner: 'bottom',
positionGlobalFilter: 'left', //move the search box to the left of the top toolbar
positionPagination: 'top',
renderTopToolbarCustomActions: () => ... ,
});
return ;
```
### Customize Toolbar Props and Styles [#customize-toolbar-props-and-styles]
The `stTopToolbarProps`, `stBottomToolbarProps`, `stToolbarAlertBannerProps`, and `stToolbarAlertBannerChipProps` table options allow you to customize the props and styles of the underlying shadcn/ui components that make up the toolbar components. Remember that you can pass CSS overrides to their `className` or `style` props. Some have found this useful for forcing `position: absolute` on alerts, etc.
### Customize Linear Progress Bars [#customize-linear-progress-bars]
The progress bars that display in both the top and bottom toolbars become visible when either the `isLoading` or `showProgressBars` state options are set to `true`. You can customize the progress bars by passing in props to the `stLinearProgressProps` table option. By default, the progress bars have a full animated progress bar, but you can set the `value` table option to a number between 0 and 100 to show real progress values if your table is doing some complicated long running tasks that you want to show progress for. Visit the [shadcn/ui Progress docs](https://ui.shadcn.com/docs/components/progress) to learn more.
```jsx
const table = useShadcnTable({
data,
columns,
stLinearProgressProps: ({ isTopToolbar }) => ({
progressComponentProps: {
className: '[&>[data-slot=progress-indicator]]:bg-amber-500',
style: { display: isTopToolbar ? 'block' : 'none' }, //only show top toolbar progress bar
value: fetchProgress, //show precise real progress value if you so desire
},
}),
state: {
isLoading,
showProgressBars,
},
});
return ;
```
### Customize Toolbar Alert Banner [#customize-toolbar-alert-banner]
The toolbar alert banner is an internal component used to display alerts to the user. By default, it will automatically show messages around the number of selected rows or grouping state.
However, you can repurpose this alert banner to show your own custom messages too. You can force the alert banner to show by setting the `showAlertBanner` state option to `true`. You can then customize the messages and other stylings using the `stToolbarAlertBannerProps` to create your custom message. You probably saw this in the [Remote Data](/docs/examples/remote) or [React Query](/docs/examples/react-query) examples.
```jsx
const table = useShadcnTable({
data,
columns,
//show a custom error message if there was an error fetching data in the top toolbar
stToolbarAlertBannerProps: isError
? {
className: 'border-destructive text-destructive',
children: 'Network Error. Could not fetch data.',
}
: undefined,
state: {
showAlertBanner: isError,
showProgressBars: isFetching,
},
});
return ;
```
### Override with Custom Toolbar Components [#override-with-custom-toolbar-components]
If you want to completely override the default toolbar components, you can do so by passing in your own custom components to the `renderTopToolbar` and `renderBottomToolbar` props.
The drawback to this approach is that you will not get all the automatic features of the default toolbar components, such as the automatic alert banner, progress bars, etc. You will have to implement all of that yourself if you still want those features. Though you can also just import those ST components and use them in your custom toolbar.
```jsx
import {
ST_GlobalFilterTextInput, //import ST sub components!
ST_TablePagination,
ShadcnTable,
useShadcnTable,
} from '@/components/ui/shadcn-table';
const table = useShadcnTable({
data,
columns,
renderTopToolbar: ({ table }) => (
),
renderBottomToolbar: ({ table }) => (
Download
),
});
return ;
```
### Build Your Own Toolbar [#build-your-own-toolbar]
Instead of overriding the toolbar components up above, you may want 100% control over the layout and styling of your table controls and where they are on the page. You can do this by just using a ST sub component such as ` ` for the table component, which does not have the internal toolbar components built-in. Optionally, build your own custom toolbar components using the other ST sub components.
# Virtualization Guide (/docs/guides/virtualization)
Virtualization is useful when you have a lot of data you want to display client-side all at once without having to use pagination. Shadcn Table makes this as simple as possible, thanks to [`@tanstack/react-virtual`](https://tanstack.com/virtual/v3) with both [row virtualization](#enable-row-virtualization) and [column virtualization](#enable-column-virtualization) support.
> NOTE: You should only enable row virtualization if you have a large number of rows. Depending on the size of the table, if you are rendering less than a couple dozen rows at a time, you will actually just be adding extra overhead to the table renders. Virtualization only becomes necessary when you have more than 50 rows or so at the same time with no pagination.
### Relevant Table Options [#relevant-table-options]
### What is Virtualization? [#what-is-virtualization]
Virtualization, or virtual scrolling, works by only rendering the rows or columns that are visible on the screen. This is useful for performance and user experience, as we can make it appear that there are hundreds, thousands, or even tens of thousands of rows in the table all at once, but in reality, the table will only render the couple dozen rows that are visible on the screen, or the handful of columns that are visible on the screen.
For more reading on the concept of virtualization, we recommend this [blog post](https://blog.logrocket.com/virtual-scrolling-core-principles-and-basic-implementation-in-react/) by LogRocket.
### Does Your Table Even Need Virtualization? [#does-your-table-even-need-virtualization]
If your table is paginated or you are not going to render more than 50 rows at once, you probably do not need row virtualization.
If your table does not have more than 12 columns, you probably do not need column virtualization.
There is a slight amount of extra overhead that gets added to your table's rendering when virtualization is enabled, so do not just enable it for every table. That being said, if your table does have well more than 100 rows that it is trying to render all at once without pagination, performance should improve significantly once it has been enabled.
### Enable Row Virtualization [#enable-row-virtualization]
Enabling row virtualization is as simple as setting the `enableRowVirtualization` table option to `true`. However, you will probably also want to turn off pagination, which you can do by setting `enablePagination` to `false`.
```tsx
```
Take a look at the example below with 10,000 rows!
### Enable Column Virtualization [#enable-column-virtualization]
Enabling column virtualization is also as simple as setting the `enableColumnVirtualization` table option to `true`.
```tsx
```
Take a look at the example below with 500 columns!
> WARNING: Do not enable row or column virtualization conditionally. This could break React's Rule of Hooks and/or cause other UI jumpiness.
### Virtualization Side Effects [#virtualization-side-effects]
When either row or column virtualization is enabled, a few other props automatically get set internally.
#### `layoutMode` Prop [#layoutmode-prop]
In ST Versions 1.3 and earlier, a CSS `table-layout: fixed` style was automatically added to the `` element to prevent columns from wiggling back and forth during scrolling due to body cells having varying widths.
But now in ST Versions 1.4 and later, the `layoutMode` table option is automatically set to the `'grid'` value when either row or column virtualization is enabled, which means that all of the table markup will use CSS Grid and Flexbox instead of the traditional semantic styles that usually come with table tags. This is necessary to make the virtualization work properly with decent performance.
#### `enableStickyHeader` Prop [#enablestickyheader-prop]
The `enableStickyHeader` table option is automatically set to `true` when row virtualization is enabled. This keeps the table header sticky and visible while scrolling and adds a default max-height of 100vh to the table container.
### Customize Virtualizer Props [#customize-virtualizer-props]
You can adjust some of the virtualizer props that are used internally with the `rowVirtualizerOptions` and `columnVirtualizerOptions` props. The most useful virtualizer props are the `overscan` and `estimateSize` options. You may want to adjust these values if you have unusual row heights or column widths that are causing the default scrolling to behave strangely.
```jsx
400, //if your columns are wider or , try tweaking this value to make scrollbar size more accurate
}}
rowVirtualizerOptions={{
overscan: 10, //adjust the number or rows that are rendered above and below the visible area of the table
estimateSize: () => 100, //if your rows are taller than normal, try tweaking this value to make scrollbar size more accurate
}}
/>
```
See the official TanStack [Virtualizer Options API Docs](https://tanstack.com/virtual/v3/docs/api/virtualizer#optional-options) for more information.
### Access Underlying Virtualizer Instances [#access-underlying-virtualizer-instances]
In a similar way that you can [access the underlying table instance](/docs/guides/table-state-management#access-the-underlying-table-instance-reference), you can also access the underlying virtualizer instances. This can be useful for accessing methods like the `scrollToIndex` method, which can be used to programmatically scroll to a specific row or column.
```tsx
const columnVirtualizerInstanceRef = useRef(null);
const rowVirtualizerInstanceRef = useRef(null);
useEffect(() => {
if (rowVirtualizerInstanceRef.current) {
//scroll to the top of the table when sorting changes
rowVirtualizerInstanceRef.current.scrollToIndex(0);
}
}, [sorting]);
return (
);
```
See the official TanStack [Virtualizer Instance API Docs](https://tanstack.com/virtual/v3/docs/api/virtualizer#virtualizer-instance) for more information.
### Full Row and Column Virtualization Example [#full-row-and-column-virtualization-example]
Try out the performance of the [fully virtualized example](/docs/examples/virtualized) with **10,000 rows** and over a dozen columns! Filtering, search, and sorting also maintain usable performance.
View Extra Storybook **Examples **
### Headless Virtualization with ST Hooks [#headless-virtualization-with-st-hooks]
If you are building a custom table in headless mode, you can still take advantage of some of the built-in virtualization functionality via the `useST_ColumnVirtualizer` and `useST_RowVirtualizer` hooks, which are wrappers for the TanStack Virtual `useVirtualizer` hook.
If you go this route, you will need to consult the [TanStack Virtual Docs](https://tanstack.com/virtual/v3) for more information on the exact styles and markup needed for the virtualization to work properly.
Here is the basics of how to use the ST virtualization hooks:
```tsx
import {
useShadcnTable,
useST_Rows,
useST_RowVirtualizer,
useST_ColumnVirtualizer,
} from '@/components/ui/shadcn-table';
const table = useShadcnTable({
columns,
data,
enableColumnVirtualization: true,
enableRowVirtualization: true,
columnVirtualizerOptions: {
//...virtualizer options
},
rowVirtualizerOptions: {
//...virtualizer options
},
});
const rows = useST_Rows(table); //alternative to table.getRowModel()
const columnVirtualizer = useST_ColumnVirtualizer(table);
const rowVirtualizer = useST_RowVirtualizer(table);
//down in your row render
const virtualRows = rowVirtualizer.getVirtualItems();
virtualRows.map((virtualRow) => {
const row = rows[virtualRow.index];
return
;
});
```
Alternatively, just use the `useVirtualizer` hook directly from `@tanstack/react-virtual` if you want to build your own virtualization logic from scratch. The ST hooks are provided as opinionated wrappers for your potential convenience.