Skip to content

DataGrid

FuiDataGrid is built on @tanstack/vue-table with a bordered rounded container, sortable column headers with always-visible sort icons, row selection footer, and an empty state message.

Basic

Basic DataGrid
Name
Age
Role
Alice30Engineer
Bob25Designer
Charlie35PM

Sorting

Sortable DataGrid
Name
Age
Role
Alice30Engineer
Bob25Designer
Charlie35PM
Diana28Engineer
Eve32Designer
Sorting: (none)

Selection

Multi row selection
Name
Age
Role
Alice30Engineer
Bob25Designer
Charlie35PM
Single row selection
Name
Age
Role
Alice30Engineer
Bob25Designer
Charlie35PM

Custom cell rendering

Custom cell rendering
Name
Age
Role
Status
Actions
Alice30Engineer<span style="display:inline-flex;align-items:center;gap:4px"> <span style="width:8px;height:8px;border-radius:50%;background:#107c10"></span> active </span><button style="padding:2px 8px;border:1px solid var(--fui-color-neutral-stroke-1);border-radius:4px;background:var(--fui-color-neutral-background-1);cursor:pointer;font-size:12px;font-family:var(--fui-font-family-base)">Edit</button>
Bob25Designer<span style="display:inline-flex;align-items:center;gap:4px"> <span style="width:8px;height:8px;border-radius:50%;background:#ff8c00"></span> away </span><button style="padding:2px 8px;border:1px solid var(--fui-color-neutral-stroke-1);border-radius:4px;background:var(--fui-color-neutral-background-1);cursor:pointer;font-size:12px;font-family:var(--fui-font-family-base)">Edit</button>
Charlie35PM<span style="display:inline-flex;align-items:center;gap:4px"> <span style="width:8px;height:8px;border-radius:50%;background:#107c10"></span> active </span><button style="padding:2px 8px;border:1px solid var(--fui-color-neutral-stroke-1);border-radius:4px;background:var(--fui-color-neutral-background-1);cursor:pointer;font-size:12px;font-family:var(--fui-font-family-base)">Edit</button>
Diana28Engineer<span style="display:inline-flex;align-items:center;gap:4px"> <span style="width:8px;height:8px;border-radius:50%;background:#0078d4"></span> vacation </span><button style="padding:2px 8px;border:1px solid var(--fui-color-neutral-stroke-1);border-radius:4px;background:var(--fui-color-neutral-background-1);cursor:pointer;font-size:12px;font-family:var(--fui-font-family-base)">Edit</button>
Eve32Designer<span style="display:inline-flex;align-items:center;gap:4px"> <span style="width:8px;height:8px;border-radius:50%;background:#107c10"></span> active </span><button style="padding:2px 8px;border:1px solid var(--fui-color-neutral-stroke-1);border-radius:4px;background:var(--fui-color-neutral-background-1);cursor:pointer;font-size:12px;font-family:var(--fui-font-family-base)">Edit</button>
Frank40Engineer<span style="display:inline-flex;align-items:center;gap:4px"> <span style="width:8px;height:8px;border-radius:50%;background:#ff8c00"></span> away </span><button style="padding:2px 8px;border:1px solid var(--fui-color-neutral-stroke-1);border-radius:4px;background:var(--fui-color-neutral-background-1);cursor:pointer;font-size:12px;font-family:var(--fui-font-family-base)">Edit</button>
Grace27PM<span style="display:inline-flex;align-items:center;gap:4px"> <span style="width:8px;height:8px;border-radius:50%;background:#107c10"></span> active </span><button style="padding:2px 8px;border:1px solid var(--fui-color-neutral-stroke-1);border-radius:4px;background:var(--fui-color-neutral-background-1);cursor:pointer;font-size:12px;font-family:var(--fui-font-family-base)">Edit</button>
Henry33Designer<span style="display:inline-flex;align-items:center;gap:4px"> <span style="width:8px;height:8px;border-radius:50%;background:#0078d4"></span> vacation </span><button style="padding:2px 8px;border:1px solid var(--fui-color-neutral-stroke-1);border-radius:4px;background:var(--fui-color-neutral-background-1);cursor:pointer;font-size:12px;font-family:var(--fui-font-family-base)">Edit</button>

Filterable

Filterable DataGrid
Name
Age
Role
Alice30Engineer
Bob25Designer
Charlie35PM
Diana28Engineer
Eve32Designer

Column visibility

Toggle column visibility
Name
Age
Role
Alice30Engineer
Bob25Designer
Charlie35PM
Diana28Engineer
Eve32Designer

Pagination

With pagination
Name
Age
Role
User 120Engineer
User 221Designer
User 322PM
User 423QA
User 524Engineer
User 625Designer
User 726PM
User 827QA
User 928Engineer
User 1029Designer
Pagination with custom page size
Name
Age
Role
User 120Engineer
User 221Designer
User 322PM
User 423QA
User 524Engineer

Loading

Loading state
Name
Age
Role
Alice30Engineer
Bob25Designer
Charlie35PM
Diana28Engineer
Eve32Designer

Expandable rows

Expandable rows
Name
Age
Role
Alice30Engineer
Bob25Designer
Charlie35PM

Striped rows

Striped rows
Name
Age
Role
Alice30Engineer
Bob25Designer
Charlie35PM
Diana28Engineer
Eve32Designer

Sizes

Three density presets control cell padding, header height, selection column width, and font size.

sizeDensityHeader heightBody padding (vertical)Body padding (horizontal)Selection col width
smallCompact36px6px12px36px
mediumStandard48px12px16px48px
largeComfortable56px16px20px56px
Compact (small)
Name
Age
Role
Alice30Engineer
Bob25Designer
Charlie35PM
Diana28Engineer
Eve32Designer
Standard (medium)
Name
Age
Role
Alice30Engineer
Bob25Designer
Charlie35PM
Diana28Engineer
Eve32Designer
Comfortable (large)
Name
Age
Role
Alice30Engineer
Bob25Designer
Charlie35PM
Diana28Engineer
Eve32Designer

Empty state

DataGrid with no data
Name
Age
Role
No results.
All features combined
Name
Age
Role
Email
Department
Status
Actions
Alice30Engineeralice@example.comEngineering<span style="display:inline-flex;align-items:center;gap:4px"> <span style="width:8px;height:8px;border-radius:50%;background:#107c10"></span> active </span>
Bob25Designerbob@example.comDesign<span style="display:inline-flex;align-items:center;gap:4px"> <span style="width:8px;height:8px;border-radius:50%;background:#107c10"></span> active </span>
Charlie35PMcharlie@example.comProduct<span style="display:inline-flex;align-items:center;gap:4px"> <span style="width:8px;height:8px;border-radius:50%;background:#0078d4"></span> vacation </span>
Diana28Engineerdiana@example.comEngineering<span style="display:inline-flex;align-items:center;gap:4px"> <span style="width:8px;height:8px;border-radius:50%;background:#ff8c00"></span> away </span>
Eve32Designereve@example.comDesign<span style="display:inline-flex;align-items:center;gap:4px"> <span style="width:8px;height:8px;border-radius:50%;background:#107c10"></span> active </span>
Frank40Engineerfrank@example.comEngineering<span style="display:inline-flex;align-items:center;gap:4px"> <span style="width:8px;height:8px;border-radius:50%;background:#107c10"></span> active </span>
Grace27PMgrace@example.comProduct<span style="display:inline-flex;align-items:center;gap:4px"> <span style="width:8px;height:8px;border-radius:50%;background:#0078d4"></span> vacation </span>
Henry33Designerhenry@example.comDesign<span style="display:inline-flex;align-items:center;gap:4px"> <span style="width:8px;height:8px;border-radius:50%;background:#ff8c00"></span> away </span>
Ivy29QAivy@example.comQA<span style="display:inline-flex;align-items:center;gap:4px"> <span style="width:8px;height:8px;border-radius:50%;background:#107c10"></span> active </span>
Jack31Engineerjack@example.comEngineering<span style="display:inline-flex;align-items:center;gap:4px"> <span style="width:8px;height:8px;border-radius:50%;background:#107c10"></span> active </span>

Default selection

DataGrid with pre-selected rows
Name
Age
Role
Alice30Engineer
Bob25Designer
Charlie35PM
Diana28Engineer
Eve32Designer

Fixed columns

Table with fixed left and right columns
ID
Name
Age
Role
Email
Phone
Department
Location
Start Date
Salary
1User 120Engineeruser1@example.com555-0101EngineeringNew York2020-01-1550000
2User 221Designeruser2@example.com555-0102DesignSeattle2021-02-1555000
3User 322PMuser3@example.com555-0103ProductSan Francisco2022-03-1560000
4User 423QAuser4@example.com555-0104QAAustin2023-04-1565000
5User 524Engineeruser5@example.com555-0105EngineeringNew York2020-05-1570000
6User 625Designeruser6@example.com555-0106DesignSeattle2021-06-1575000
7User 726PMuser7@example.com555-0107ProductSan Francisco2022-07-1580000
8User 827QAuser8@example.com555-0108QAAustin2023-08-1585000
9User 928Engineeruser9@example.com555-0109EngineeringNew York2020-09-1590000
10User 1029Designeruser10@example.com555-0110DesignSeattle2021-01-1595000

Row borders only

By default, FuiDataGrid applies borders to rows (<tr>) rather than cells (<td>/<th>), giving a cleaner horizontal-line look.

Row borders only
Name
Age
Role
Alice30Engineer
Bob25Designer
Charlie35PM

Custom header background

Use the header property of a column definition to render custom background colors for individual column headers. The render function uses h() with negative margins to fill the full header cell.

Custom header background
Name
Age
Role
Email
Alice30Engineeralice@example.com
Bob25Designerbob@example.com
Charlie35PMcharlie@example.com

Column header filter

Set meta.filterable on a column to show a filter icon in its header. Clicking opens a popover with checkboxes (via meta.filterOptions). The component does not filter data locally — it emits @update:filters so you can handle filtering on the server side.

Column header filter
Name
Age
Role
Alice30Engineer
Bob25Designer
Charlie35PM
Diana28Engineer
Eve32Designer
Frank40Engineer
Grace27PM
Henry33Designer

Server-side filtering

Listen to @update:filters to react when the user applies or clears a column filter, then fetch filtered data from your API.

vue
<script setup lang="ts">
const serverData = ref([])
const columns = [
  ch.accessor('role', {
    header: 'Role',
    meta: {
      filterable: true,
      filterOptions: [
        { label: 'Engineer', value: 'Engineer' },
        { label: 'Designer', value: 'Designer' },
        { label: 'PM', value: 'PM' },
      ],
    },
  }),
]

function handleFilters(filters: { id: string; value: any[] }[]) {
  // filters = [{ id: 'role', value: ['Engineer', 'PM'] }]
  fetchData({ filters }).then(res => { serverData.value = res.data })
}
</script>

<template>
  <FuiDataGrid :data="serverData" :columns="columns" @update:filters="handleFilters" />
</template>

Interaction

Row Selection

When selectable is enabled, the following interactions are supported:

Actionselectable="single"selectable="true" / 'multi'
Click rowSelect this rowToggle this row, preserve others
Ctrl / Cmd + click rowSame as clickToggle this row, preserve others
Shift + click rowSame as clickSelect range from last clicked row to current row
Click checkboxToggle this rowToggle this row

Interactive elements inside cells (<button>, <a>, <input>, etc.) do not trigger row selection when clicked.

Fluent 2 design recommends using the checkbox alone to indicate selected rows. Background color changes are an additional visual aid.

Sorting

Click a sortable column header to toggle ascending / descending / unsorted. The active sort column shows a sort arrow icon.

Filtering

  • Toolbar filter: When filterable is enabled, a search input appears in the toolbar for real-time data filtering.
  • Column header filter: Set meta.filterable on a column definition to enable a filter popover with multi-select checkboxes.

Pagination

When pagination is enabled, pagination controls appear at the bottom. Use pageSize and pageSizeOptions to customize the page size.

Fixed Columns

Set meta.fixed: 'left' | 'right' on a column to pin it. Fixed columns remain visible during horizontal scroll with shadow indicators. All cells use opaque backgrounds in fixed-column mode to mask scrolled content.

Expandable Rows

When expandable is enabled, use the #expanded-row slot to customize expanded content. Clicking a row does not affect the expanded state.

Props

ComponentPropTypeDefaultDescription
ColumnDefmeta.fixed'left' | 'right'Pin column to left/right side
ColumnDefmeta.filterablebooleanShow header filter icon + popover for this column
ColumnDefmeta.filterOptions{ label, value }[]Options shown as checkboxes in the filter popover
DataGriddataRecord<string, any>[]Row data array
DataGridcolumnsColumnDef[]TanStack column definitions
DataGridsortablebooleanfalseEnable column sorting
DataGridselectableboolean | 'single' | 'multi'falseEnable row selection
DataGridfilterablebooleanfalseShow search filter in toolbar
DataGridfilterColumnstring''Column id to filter by
DataGridcolumnVisibilitybooleanfalseShow column toggle menu in toolbar
DataGridpaginationboolean | { pageSize?, pageSizeOptions? }falseEnable pagination
DataGridexpandablebooleanfalseEnable expandable rows
DataGridloadingbooleanfalseShow loading overlay
DataGridstripedbooleanfalseAlternating row colors
DataGridsize'small' | 'medium' | 'large''medium'Table density preset (compact / standard / comfortable)
DataGridstickyHeaderbooleanfalseSticky header row
DataGridemptyMessagestring'No results.'Empty state text
DataGridrowKeystring | FunctionRow ID accessor
DataGridsortingSortingStatev-model for sort state
DataGridrowSelectionRecord<string, boolean>v-model for selection state

Events

EventPayloadDescription
update:filters{ id: string; value: any[] }[]Emitted when column filter is applied or cleared. For server-side filtering, listen to this event and request filtered data from your API.

Slots

SlotPropsDescription
toolbarCustom toolbar content
expanded-row{ row }Expanded row content
emptyCustom empty state
loadingCustom loading content

Methods

MethodParamsDescription
downloadCsvfilename?: stringExport data as CSV
tableTanStack VueTable instance

Installation

@tanstack/vue-table is bundled as a dependency. No additional install needed — define columns via createColumnHelper and pass to <FuiDataGrid>.

vue
<script setup>
import { createColumnHelper } from '@tanstack/vue-table'
</script>