# TableFolders

**Category:** Base Components/Collections/TableFolders/TableFolders

## Design

### Description

The `TableFolders` component displays a table-like view of folders and items.

The state object obtained from the `useTableFolders()` hook manages the internal state of the component and provides information about the current state of the table.


```tsx
import { TableFolders, useTableFolders } from '@wix/patterns';
```

### Demo

```tsx
import React from 'react';
import { queryContacts } from '@wix/ambassador-contacts-v4-contact/http';
import { Contact } from '@wix/ambassador-contacts-v4-contact/types';
import {
  OffsetQuery,
  useCollection,
  TableFolders,
  PageWrapper,
  useTableFolders,
} from '@wix/patterns';
import { useHttpClient } from '@wix/yoshi-flow-bm';
import { Page } from '@wix/design-system';

function Basic() {
  const httpClient = useHttpClient();

  const collection = useCollection<Contact>({
    queryName: 'tableitems',
    itemKey: (item) => item.id as string,
    itemName: (item) => item.name as string,
    fetchData: (query: OffsetQuery) => {
      const { limit, offset, search, filters } = query;
      return httpClient
        .request(
          queryContacts({
            query: { paging: { limit, offset }, filter: filters },
            search,
          }),
        )
        .then((res) => {
          const {
            data: { contacts = [], pagingMetadata },
          } = res;
          return {
            items: contacts,
            total: pagingMetadata?.total,
          };
        });
    },
    fetchErrorMessage: ({ err }) => String(err),
  });

  const foldersCollection = useCollection<Contact>({
    queryName: 'tablefolders',
    paginationMode: 'offset',
    itemName: (item) => item.name as string,
    fetchData: (query: OffsetQuery) => {
      const { limit, offset, search, filters } = query;
      return httpClient
        .request(
          queryContacts({
            query: { paging: { limit: 20, offset }, filter: filters },
            search,
          }),
        )
        .then((res) => {
          const {
            data: { contacts = [], pagingMetadata },
          } = res;
          return {
            items: contacts,
            total: 20,
          };
        });
    },
    fetchErrorMessage: ({ err }) => String(err),
  });

  const componentState = useTableFolders({
    items: collection,
    folders: foldersCollection,
  });

  return (
    <PageWrapper>
      <Page height="400px">
        <Page.Header title="Folders and items" />

        <Page.Content>
          <TableFolders
            state={componentState}
            columns={[
              {
                title: 'Name',
                width: '250px',
                render: (contact) => contact.name,
                renderFolder: (folder) => folder.name,
              },
              {
                title: 'Last Seen',
                width: '250px',
                render: (contact) => contact.lastSeen?.toLocaleString(),
                renderFolder: (folder) => folder.lastSeen?.toLocaleString(),
              },
            ]}
          />
        </Page.Content>
      </Page>
    </PageWrapper>
  );
}
```

---

### Variations

### Table Folders

Table representation that shows folders and their content

```tsx
import React from 'react';
import { queryContacts } from '@wix/ambassador-contacts-v4-contact/http';
import { Contact } from '@wix/ambassador-contacts-v4-contact/types';
import {
  OffsetQuery,
  useCollection,
  TableFolders,
  PageWrapper,
  useTableFolders,
} from '@wix/patterns';
import { useHttpClient } from '@wix/yoshi-flow-bm';
import { Page } from '@wix/design-system';

function Basic() {
  const httpClient = useHttpClient();

  const collection = useCollection<Contact>({
    queryName: 'tableitems',
    itemKey: (item) => item.id as string,
    itemName: (item) => item.name as string,
    fetchData: (query: OffsetQuery) => {
      const { limit, offset, search, filters } = query;
      return httpClient
        .request(
          queryContacts({
            query: { paging: { limit, offset }, filter: filters },
            search,
          }),
        )
        .then((res) => {
          const {
            data: { contacts = [], pagingMetadata },
          } = res;
          return {
            items: contacts,
            total: pagingMetadata?.total,
          };
        });
    },
    fetchErrorMessage: ({ err }) => String(err),
  });

  const foldersCollection = useCollection<Contact>({
    queryName: 'tablefolders',
    paginationMode: 'offset',
    itemName: (item) => item.name as string,
    fetchData: (query: OffsetQuery) => {
      const { limit, offset, search, filters } = query;
      return httpClient
        .request(
          queryContacts({
            query: { paging: { limit: 20, offset }, filter: filters },
            search,
          }),
        )
        .then((res) => {
          const {
            data: { contacts = [], pagingMetadata },
          } = res;
          return {
            items: contacts,
            total: 20,
          };
        });
    },
    fetchErrorMessage: ({ err }) => String(err),
  });

  const componentState = useTableFolders({
    items: collection,
    folders: foldersCollection,
  });

  return (
    <PageWrapper>
      <Page height="400px">
        <Page.Header title="Folders and items" />

        <Page.Content>
          <TableFolders
            state={componentState}
            columns={[
              {
                title: 'Name',
                width: '250px',
                render: (contact) => contact.name,
                renderFolder: (folder) => folder.name,
              },
              {
                title: 'Last Seen',
                width: '250px',
                render: (contact) => contact.lastSeen?.toLocaleString(),
                renderFolder: (folder) => folder.lastSeen?.toLocaleString(),
              },
            ]}
          />
        </Page.Content>
      </Page>
    </PageWrapper>
  );
}
```

---

### With Tags

This example shows a `TableFolders` component with tags. Note that you can only assign tags to table items and not to folders. [See Tags docs](./?path=/story/features-enrich-tags--tags-overview).

```tsx
import React from 'react';
import { CollectionPage } from '@wix/patterns/page';
import {
  TagsFolders,
  MultiBulkActionToolbar,
  useCollection,
  TableFolders,
  useTableFolders,
  CustomColumns,
  OffsetQuery,
} from '@wix/patterns';
import { Tags as TagsType } from '@wix/ambassador-cairo-dummyservice-v1-dummy-entity/types';
import { queryContacts } from '@wix/ambassador-contacts-v4-contact/http';
import { Contact } from '@wix/ambassador-contacts-v4-contact/types';
import { useHttpClient } from '@wix/yoshi-flow-bm';

function WithTags() {
  const httpClient = useHttpClient();

  const collection = useCollection<Contact>({
    queryName: 'tableitems',
    fqdn: 'wix.patterns.dummyservice.v1.dummy_entity',
    itemKey: (item) => item.id as string,
    itemName: (item) => item.name as string,
    fetchData: (query: OffsetQuery) => {
      const { limit, offset, search, filters } = query;
      return httpClient
        .request(
          queryContacts({
            query: { paging: { limit, offset }, filter: filters },
            search,
          }),
        )
        .then((res) => {
          const {
            data: { contacts = [], pagingMetadata },
          } = res;
          return {
            items: contacts,
            total: pagingMetadata?.total,
          };
        });
    },
    fetchErrorMessage: ({ err }) => String(err),
  });

  // Note: This is only for demo/mock purposes.
  // In real usage, you should use your actual API method to fetch folders
  const foldersCollection = useCollection<Contact>({
    queryName: 'tablefolders',
    paginationMode: 'offset',
    itemName: (item) => item.name as string,
    fetchData: (query: OffsetQuery) => {
      const { limit, offset, search, filters } = query;
      return httpClient
        .request(
          queryContacts({
            query: { paging: { limit: 2, offset }, filter: filters },
            search,
          }),
        )
        .then((res) => {
          const {
            data: { contacts = [], pagingMetadata },
          } = res;
          return {
            items: contacts.map((contact) => ({
              ...contact,
              id: `folder-${contact.id}`,
            })),
            total: 2,
          };
        });
    },
    fetchErrorMessage: ({ err }) => String(err),
  });

  const componentState = useTableFolders({
    items: collection,
    folders: foldersCollection,
  });

  const handleBulkUpdateTags = ({
    entityIds,
    assignTags,
    unassignTags,
  }: {
    entityIds: string[];
    assignTags: TagsType;
    unassignTags: TagsType;
  }) => {
    // your code to update the server
    return Promise.resolve();
  };

  return (
    <CollectionPage height="400px">
      <CollectionPage.Header title={{ text: 'Contacts' }} />
      <CollectionPage.Content>
        <TableFolders
          state={componentState}
          tags={
            <TagsFolders
              entityTypeName="Contact"
              bulkUpdateTags={handleBulkUpdateTags}
            />
          }
          customColumns={<CustomColumns />}
          columns={[
            {
              id: 'name',
              hideable: false,
              title: 'Name',
              render: (contact) => contact.name,
              renderFolder: (folder) => folder.name,
              width: '250px',
            },
          ]}
          bulkActionToolbar={() => <MultiBulkActionToolbar />}
        />
      </CollectionPage.Content>
    </CollectionPage>
  );
}
```

## API

### Props

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `dataHook` | `string` | No | - | Applies a data-hook HTML attribute to be used in the tests |
| `state` | `TableFoldersState<T1, F1, T2, F2>` | Yes | - | A TableFoldersState instance created using [useTableFolders](./?path=/story/base-components-collections-tablefolders-usetablefolders--usetablefolders). |
| `columns` | `TableFoldersColumn<T1, T2>[]` | Yes | - | The same as [Table.columns](./?path=/story/base-components-collections-table-table--table), with the addition of `renderFolder` property. |
| `iconColumn` | `TableFoldersColumn<T1, T2>` | No | - | Specific entry for rendering the item icon, defaults to simple `Avatar` (for folders a `Folder` icon is rendered). |
| `onFolderRowClick` | `((item: T2, index: number) => void)` | No | - | Callback for folder row click |
| `selectionDisabled` | `((item: T1 \| T2) => void)` | No | - | Disables row checkbox |
| `bulkActionToolbar` | `MultiCollectionBulkActionToolbarRenderProps` | No | - | A toolbar for performing bulk actions on multiple items. Supported parameters: + `selectedValues`: Selected item values. + `uncheckedValues`: Unselected item values. + `isSelectAll`: [bool] Whether the **Select All** option is selected. + `openModal`: Opens a modal that contains the component from `bulkActionModal` render prop. + `clearSelection`: Clears all selections. Call this after the action on the selected items finishes. + `closeModal`: Closes the modal. + `openConfirmModal`: Opens a `<MessageModalLayout />` to confirm an action to be applied to the selected items. + `query`: [object] Instance of [ComputedQuery](./?path=/story/common-types--computedquery) that represents the query that resulted in an empty state. Representation of the current paging, sorting and filtering applied. Usually for using together with bulk action such as `updateAll` and `deleteAll`. |
| `bulkActionModal` | `MultiCollectionBulkActionModalRenderProps` | No | - | Render a modal to be opened from `bulkActionToolbar`.<br> The function accepts the following parameters * `allSelected`: Indicates whether the "Select All" checkbox is checked * `closeModal`: Closes the a modal |
| `search` | `boolean \| FilterElement<any> \| CollectionSearchElement` | No | `true` | Displays a search input on the right side of the table toolbar.<br> <br> Accepts a boolean or a custom React element as a parameter. When passing `false`, no search input is displayed. |
| `title` | `ToolbarTitleElement` | No | - | Displays a title at the left of a toolbar. Accepts a [ToolbarTitle](./?path=/story/features-display-toolbar--toolbartitle) component. |
| `skin` | `"standard" \| "neutral"` | No | - | @deprecated The table skin is fixed to `"neutral"` and any value passed here is ignored. This prop will be removed in a future release. |
| `tags` | `TagsElement<T1, Partial<F1 & F2>>` | No | - | Enables tags feature. In this element you can pass the configuration |
| `errorState` | `((err: unknown, params: { isOnline: boolean; retry: () => void; }) => ReactElement<any, string \| JSXElementConstructor<any>>)` | No | - | A render function to be rendered when there's an error fetching data from the server.<br> The function accepts the following parameters <br> * `err`: The error thrown by the [fetchData](./?path=/story/common-hooks--usecollection) function <br> * `isOnline`: Indicates whether internet connection is available <br> * `retry`: Retry collection fetch on failed response, similar to `collection.retryFetch` <br> Returns [`EmptyState`](https://www.docs.wixdesignsystem.com/?path=/story/components-layout--emptystate) component. |
| `maxSelection` | `number` | No | - | Limits the amount of rows a visitor can select. |
| `views` | `CollectionViewsDropdownElement<Partial<F1 & F2>>` | No | - | Adds view options to the table headers. Accepts a [Views](./?path=/story/features-display-views--views) component. |
| `filters` | `CollectionToolbarFiltersElement` | No | - | Adds filters to the toolbar and/or a sliding panel. Accepts a `ToolbarFilters` component, which must have the `panelTitle` prop defined. |
| `tabs` | `TabsFilterElement<TabsFilterProps<T1>>` | No | - | Displays tabs at the left of a toolbar. Accepts a [TabsFilter](./?path=/story/features-filter-components--tabsfilter) component. |
| `minCardHeight` | `MinHeight<string \| number>` | No | `300px` | Minimum height for a card's container when showing:<br> - A loader<br> - Empty state<br> - Error state <br><br> If no unit is specified, `px` is used. |
| `renderError` | `((params: { err: unknown; isOnline: boolean; retry: () => void; }) => ReactElement<any, string \| JSXElementConstructor<any>>)` | No | - | A render function to be rendered when there's an error fetching data from the server.<br> The function accepts the following parameters <br> * `err`: The error thrown by the [fetchData](./?path=/story/common-hooks--usecollection) function <br> * `isOnline`: Indicates whether internet connection is available <br> * `retry`: Retry collection fetch on failed response, similar to `collection.retryFetch` <br> Returns [`EmptyState`](https://www.docs.wixdesignsystem.com/?path=/story/components-layout--emptystate) component. @deprecated Use `errorState` instead. |
| `emptyState` | `ReactNode` | No | - | Renders when there are no items to display. Accepts a [`CollectionEmptyState`](./?path=/story/features-display-empty-states--collectionemptystate) component. |
| `noResultsState` | `ReactNode \| ((params: { hasAvailableItems: boolean; query: ComputedQuery<Partial<F1 & F2>>; }) => ReactNode)` | No | - | Shown when a search or filter has no results. Accepts a [`CollectionNoResultsState`](./?path=/story/features-display-empty-states--collectionnoresultsstate) component, or a function that accepts the following parameters: - `hasAvailableItems`: Indicates whether other items may be shown using other filter or search parameters. - `query`: An instance of [`ComputedQuery`](./?path=/story/common-models--computedquery) representing the query that resulted in the empty state. The function should return [`CollectionNoResultsState`](./?path=/story/features-display-empty-states--collectionnoresultsstate). |
| `summaryBar` | `ReactNode` | No | - | Display a summary bar below the toolbar. Accepts a [`SummaryBar`](./?path=/story/features-display-toolbar--summarybar) component. |
| `dragAndDropSubmit` | `DragAndDropSubmit<T1, Partial<F1 & F2>>` | No | - | Function that submits a drop event to the server. <br> <br> The parameter is an event object containing the following details: <br> -`from: {item: T; index: number}`: The dragged item. <br> -`after: null \| [item: T; iindex: number}`: The previous dropped item. If this is the first item to be dropped, this argument is `null`. <br> -`filters`: Applied [Drag and Drop](./?path=/story/features-sort-drag-and-drop--overview) filters. @returns Promise |
| `dragAndDropBulkSubmit` | `DragAndDropBulkSubmit<T1, Partial<F1 & F2>>` | No | - | Function that submits an array of drop events to the server. <br> <br> The parameter is an array of event objects containing the following details: <br> -`from: {item: T; index: number}`: The dragged item. <br> -`after: null \| [item: T; index: number}`: The previous dropped item. If this is the first item to be dropped, this argument is `null`. <br> -`filters`: Applied [Drag and Drop](./?path=/story/features-sort-drag-and-drop--overview) filters. <br> @returns Promise |
| `dragAndDropCancel` | `DragAndDropCancel<T1, Partial<F1 & F2>>` | No | - | A function that cancels a drop event. <br> Supported arguments: <br> - `from: {item: T; index: number}`: The dragged item. - `after: null \| {item: T; index: number}`: The item that the dragged item was dropped after. If dropped at the head of the collection, this argument will be `null`. - `filters`: Currently applied filters. Used in cases where your server holds a unique ordering per filtering. See the `dragAndDropCategories` prop. If the move is cancelled, the function returns a toast config object with a message. Otherwise, the function will return a null value. |
| `extensionMenuItems` | `ExtensionMenuItemsType` | No | - | @deprecated It doesn't need to be passed implicitly anymore. |
| `dragAndDropReorderModeToolbar` | `ReorderModeToolbarElement \| null` | No | - | Drag and drop reorder mode toolbar. This prop accepts a `ReorderModeToolbar` component.<br> The `ReorderModeToolbar` component accepts the following props:<br> + `learnMoreUrl`: [string] A URL in the toolbar.<br> + `reorderModeTitle`: [string] A title in the toolbar. |
| `customColumns` | `CustomColumnsElement \| null` | No | - | Adds functionality to allow visitors to create choose which columns to display and in what order. Accepts a [CustomColumns](./?path=/story/features-display--customcolumns) component. |
| `multiLevelSorting` | `MultiLevelSortingElement` | No | - | Adds functionality to allow visitors to sort multiple columns simultaneously. Accepts a [MultiLevelSorting](./?path=/story/features-sort-sortable-columns-multilevelsorting--multilevelsorting) component. |
| `primaryActionButton` | `PrimaryActionButtonElement` | No | - | Adds functionality for a primary action button in the toolbar. Accepts a [PrimaryActionButton](./?path=/story/features-actions-primary-actions--primary-action-button) component. |
| `secondaryActions` | `ToolbarSecondaryActionsElement` | No | - | Secondary buttons w/o PopoverMenu component to be rendered on the toolbar. |
| `moreActions` | `MoreActionsElement` | No | - | A [`MoreActions`](./?path=/story/features-actions-more-actions--more-actions) component to be rendered on the header. |
| `AddApplyFiltersButton` | `AddApplyFiltersButtonType` | No | - | Apply filter button implementation: `import { AddApplyFiltersButton } from '@wix/patterns';` <br> Add `Apply` button to the filters panel footer. Panel filters will be applied only after the button is clicked. |
| `internalScroll` | `boolean` | No | - | Indicates whether the table should have an [internal scroll](./?path=/story/features-display-internal-scroll--internal-scroll) instead of the default page scroll. |
| `showFieldTypeIcons` | `boolean` | No | `false` | When true, renders prefix icons for field types in column headers, filters, and the customize-columns panel. Each column and filter should have a `fieldType` property set so Cairo can render the matching icon. |
| `showTitleBar` | `boolean` | No | `true` | Indicates whether to show Table Title Bar or not. Note: if you pass `showTitleBar = false` title bar is not removed from the DOM. For better accessibility column titles should be defined as well. |
| `exportButton` | `ReactElement<any, string \| JSXElementConstructor<any>>` | No | - | A [`<ExportButton />`](./?path=/story/features-export--exportto) react element to be added to the toolbar |
| `importButton` | `ReactElement<any, string \| JSXElementConstructor<any>>` | No | - | A [`<ImportButton />`] react element to be added to the toolbar. |
| `exportModal` | `ReactElement<any, string \| JSXElementConstructor<any>>` | No | - | A [`<ExportModal />`](./?path=/story/features-export--exportto#CTA_is_out_of_the_table) react element to be rendered in table's context. |
| `importModal` | `ReactElement<any, string \| JSXElementConstructor<any>>` | No | - | A [`<ImportModal />`] react element to be rendered in table's context. |
| `dragAndDropCategories` | `(keyof F1 \| keyof F2)[][] \| null` | No | - | By default, drag and drop works only without filters. To enable drag and drop with filters, pass an array of filters to this prop. The functionality will then work only for the specified filters, and to enable without filters, you must pass an empty array. For more information, see the [Drag and Drop Overview](//?path=/story/features-sort-drag-and-drop--overview). |
| `hideBulkSelectionCheckbox` | `boolean` | No | - | Hides bulk selection checkbox in the table titlebar, If the [`MultiBulkActionToolbar`](./?path=/story/features-actions-bulk-actions--multibulkactiontoolbar) feature is enabled, this flag will have no effect. |
| `onSelectionChanged` | `OnSelectionChangedFn` | No | - | Defines a callback function which is called when selection happens. <br /> There are 3 types of selections: * `ALL` - bulk selection checkbox has been triggered to select all items * `SINGLE_TOGGLE` - a checkbox to select one item from the list has been triggered. The change object will also include an `id` prop with the unique item identifier and a `value` prop with the new boolean selection state of the item. * `NONE` - bulk selection has been triggered to unselect all items. |
| `onSelectionStarted` | `(() => void)` | No | - | Called when item selection triggered but not changed yet. |
| `showSelection` | `boolean` | No | `false` | Displays selection checkbox as a first column in each row. To hide the selection checkbox from a specific row, set its `row.unselectable` value to true in the data array. |
| `showRowNumbers` | `boolean` | No | `false` | When true and showSelection is enabled, displays row numbers in the selection column. The checkbox appears on hover or when the row is selected. |
| `bulkSelectionCheckboxRef` | `Ref<HTMLInputElement>` | No | - | A ref to the bulk selection checkbox in the table titlebar |
| `selectedIds` | `string[] \| number[]` | No | - | Defines an array of selected rows |
| `deselectRowsByDefault` | `boolean` | No | `false` | Controls bulk selection checkbox behaviour in indeterminate state. By default, first click on indeterminate state selects all items in the table. If set to `true`, first click will deselect all items instead. |
| `withWrapper` | `boolean` | No | `true` | When false then Table would not create a `<div/>` wrapper around it's children. Useful when using `<Table/>` to wrap a `<Page/>` component, in that case we use the `<Table/>` only as a context provider and it does not render anything to the DOM by itself. |
| `onSortClick` | `((colData: TableColumn<any>, colNum: number) => void)` | No | - | Defines a callback function which is called on column title click |
| `totalSelectableCount` | `number` | No | - | Specifies the total number of selectable items in the table (including items not loaded yet). When ‘Select all’ is triggered in infinite scroll tables, newly loaded items will be selected automatically. In this case, `SelectionContext` holds not-selected items rather than the selected ones. SelectionContext.infiniteBulkSelected is true and SelectionContext.selectedCount is the value of totalSelectableCount minus the count of unselected items. |
| `rowStatus` | `((row: KeyedItem<RowItem<T1, T2>>, rowNum: number) => TableRowStatusValue \| null)` | No | - | When provided, renders a fixed-width status slot per row to the right of the selection/row-number column. The callback returns a `{ status, messages }` config for rows that need a status indicator, or `null`/`undefined` for an empty but width-reserved slot — the slot never collapses. Only `error` and `warning` statuses are supported; the slot is exclusively for those two indicator kinds, rendered via the WDS `<StatusIndicator/>`. |
| `stickySelectionColumn` | `boolean` | No | - | Whether to add a sticky column. <br> Sticky columns allow you to display specific columns at all times while the visitor scrolls the table horizontally. |
| `actionCell` | `ActionCell<T1, F1>` | No | - | Use this property to add an action at the end of each row. For example, a delete button to delete a table item. Accepts an [`ActionCellProps`](./?path=/story/features-actions-action-cell--actioncellprops) object, or a function that returns an `ActionCellProps` object. |
| `actionCellWidth` | `string \| number` | No | - | Width of the column containing the `actionCell` component. |
| `actionCellProps` | `ActionCellPropsType<T1>` | No | - | Extra columns props for the actions cell. |

