Skip to content

FTGrid

FTGrid is a Fast Track-themed wrapper around AG Grid Community. It accepts a column definition + row data and renders a sortable, filterable, paginated table with built-in cell renderers (text, number, currency, date, avatar, status, badge, flag), an optional utility column (checkbox / star / pin / drag), and an opt-in column picker and CSV export.

It is not a drop-in for FTTable or FTDataTable — pick FTGrid when you need AG Grid's column ergonomics (resize, pin, reorder, external filter model). Use FTTable for simple lists and FTDataTable for the FT design-system tabular layout.

Usage

vue
<script lang="ts" setup>
import { FTGrid } from '@fasttrack-solutions/vue-components-lib';
import type { IFTGridColumnDef } from '@fasttrack-solutions/vue-components-lib';

const columns: IFTGridColumnDef[] = [
  { field: 'name', headerName: 'Player', cellType: 'text', filterType: 'condition' },
  { field: 'balance', headerName: 'Balance', cellType: 'currency', currencySymbol: '€' },
];
const items = [
  { id: 1, name: 'Alice', balance: 1240.5 },
  { id: 2, name: 'Bob', balance: 320 },
];
</script>

<template>
  <FTGrid :items="items" :columns="columns" />
</template>
<script lang="ts" setup>
import { FTGrid } from '@fasttrack-solutions/vue-components-lib';
import type { IFTGridColumnDef } from '@fasttrack-solutions/vue-components-lib';

const columns: IFTGridColumnDef[] = [
  { field: 'name', headerName: 'Player', cellType: 'text', filterType: 'condition' },
  { field: 'balance', headerName: 'Balance', cellType: 'currency', currencySymbol: '€' },
];
const items = [
  { id: 1, name: 'Alice', balance: 1240.5 },
  { id: 2, name: 'Bob', balance: 320 },
];
</script>

<template>
  <FTGrid :items="items" :columns="columns" />
</template>

Playground

1-66

Properties

Default
Zebra
Checkbox
Relative
25
<script setup lang="ts">
import { ref } from 'vue';

const items = ref([
  {
    "id": 1,
    "name": "Alice Johnson",
    "status": "Active",
    "tier": "Gold",
    "balance": 1240.5,
    "lastSeen": "2026-05-20"
  },
  {
    "id": 2,
    "name": "Bob Smith",
    "status": "Pending",
    "tier": "Silver",
    "balance": 320,
    "lastSeen": "2026-05-18"
  },
  {
    "id": 3,
    "name": "Charlie Brown",
    "status": "Inactive",
    "tier": "Bronze",
    "balance": 0,
    "lastSeen": "2025-12-04"
  },
  {
    "id": 4,
    "name": "Diana Prince",
    "status": "Active",
    "tier": "Gold",
    "balance": 5612.25,
    "lastSeen": "2026-05-22"
  },
  {
    "id": 5,
    "name": "Evan Wright",
    "status": "Active",
    "tier": "Silver",
    "balance": 88.4,
    "lastSeen": "2026-05-21"
  },
  {
    "id": 6,
    "name": "Foxy Wallace",
    "status": "Pending",
    "tier": "Bronze",
    "balance": 12,
    "lastSeen": "2026-04-30"
  }
]);
const columns = ref([
  {
    "field": "name",
    "headerName": "Player",
    "cellType": "text",
    "filterType": "condition"
  },
  {
    "field": "status",
    "headerName": "Status",
    "cellType": "status",
    "filterType": "set",
    "statusMap": {
      "Active": "#16a34a",
      "Pending": "#f59e0b",
      "Inactive": "#9ca3af"
    }
  },
  {
    "field": "tier",
    "headerName": "Tier",
    "cellType": "badge",
    "badgeMap": {
      "Gold": {
        "bg": "#fef3c7",
        "fg": "#92400e"
      },
      "Silver": {
        "bg": "#e5e7eb",
        "fg": "#374151"
      },
      "Bronze": {
        "bg": "#fde68a",
        "fg": "#78350f"
      }
    }
  },
  {
    "field": "balance",
    "headerName": "Balance",
    "cellType": "currency",
    "currencySymbol": "€",
    "align": "right"
  },
  {
    "field": "lastSeen",
    "headerName": "Last Seen",
    "cellType": "date"
  }
]);
</script>

<template>
  <FTGrid
    :items="items"
    :columns="columns"
  />
</template>

Cell types

Set cellType per column. Defaults to text.

  • text — plain string
  • number — thousands-separated number (use align: 'right' to right-align)
  • currencycurrencySymbol prepended (e.g. )
  • date — formatted via dateFormat prop (relative / absolute / iso)
  • avatar — image from avatarField row key, with first-letter fallback
  • status — coloured dot + label, colours from statusMap
  • badge — pill with bg/fg colours from badgeMap
  • flag — country-code flag SVG from flagCdnBase

Utility column

Set utility to add a pinned-left column:

  • none — no utility column
  • checkbox (default) — multi-select with header checkbox
  • star — toggleable favourite
  • pin — pinned rows float to the top
  • drag — manual row reorder

Row styles

Set rowStyle:

  • zebra (default) — alternating row backgrounds
  • lines — single-pixel borders, no zebra
  • swimlanes — wide white gaps between rows
  • none — flat background

Props

PropTypeDefaultDescription
itemsRecord<string, unknown>[]Row data
columnsIFTGridColumnDef[]Column definitions
density'compact' | 'default' | 'comfort''default'Row height and spacing
rowStyle'zebra' | 'lines' | 'swimlanes' | 'none''zebra'Row decoration
utility'none' | 'checkbox' | 'star' | 'pin' | 'drag''checkbox'Pinned utility column
dateFormat'relative' | 'absolute' | 'iso''relative'Formatter for date cells
pageSizenumber25Initial rows per page
pageSizeOptionsnumber[][10,25,50,100]Choices in the page-size selector
showColumnPickerbooleantrueRender the toolbar column picker
lockedColumnsstring[][]Field keys that can't be hidden or reordered — always visible, pinned first
defaultVisibleColumnsstring[] | undefinedundefinedField keys visible on first render + restored by "Reset to default" (locked always included). When omitted, all columns show
initialVisibleColumnsstring[] | undefinedundefinedSaved visible set that seeds the initial render (e.g. user prefs); falls back to the default
initialColumnOrderstring[] | undefinedundefinedSaved column order that seeds the initial render; falls back to the authored order
exportFileNamestring | undefinedundefinedWhen set, exposes CSV export (file name)
loadingbooleanfalseShow AG Grid's loading overlay
errorboolean | stringfalseReplace the grid with the #error slot; a string is shown as the default message
cellRendererOverridesRecord<string, CellRenderer>{}Per-field override of the default cellType renderer (wrapper-component use)
simplePaginationbooleanfalseEnable server-side (controlled) pagination — see below
totalItemsnumber | nullnullServer's grand total row count (server-side mode)
pagenumber | nullnullControlled current page, 1-indexed (server-side mode)

Events

EventPayloadDescription
selection-changedrows: Record<string, unknown>[]Fired when utility: 'checkbox' selection changes
page-changedpage: numberCurrent page (1-indexed)
size-changedsize: numberNew page size
sorting-changed{ sortKey: string; sortOrder: 'asc' | 'desc' | '' }Active sort (server-side mode); empty when cleared
columns-changed{ visible: string[]; order: string[] }Visible set / order changed via the picker — persist for per-user prefs

Column management

The toolbar column picker lets users show/hide columns, drag to reorder, Select all, and Reset to default. Three props shape it, and one event lets you persist the result:

vue
<script setup lang="ts">
import { ref } from 'vue';
import { FTGrid } from '@fasttrack-solutions/vue-components-lib';

// load from your user-prefs store (or null for a first-time user)
const saved = loadPrefs();

function persist(state) {
  savePrefs(state); // { visible: string[], order: string[] }
}
</script>

<template>
  <FTGrid
    :items="rows"
    :columns="columns"
    :locked-columns="['name']"
    :default-visible-columns="['name', 'status', 'trigger']"
    :initial-visible-columns="saved?.visible"
    :initial-column-order="saved?.order"
    @columns-changed="persist"
  />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { FTGrid } from '@fasttrack-solutions/vue-components-lib';

// load from your user-prefs store (or null for a first-time user)
const saved = loadPrefs();

function persist(state) {
  savePrefs(state); // { visible: string[], order: string[] }
}
</script>

<template>
  <FTGrid
    :items="rows"
    :columns="columns"
    :locked-columns="['name']"
    :default-visible-columns="['name', 'status', 'trigger']"
    :initial-visible-columns="saved?.visible"
    :initial-column-order="saved?.order"
    @columns-changed="persist"
  />
</template>
  • lockedColumns — always visible, pinned first, and non-draggable in the picker (the checkbox is disabled and the row can't be dropped above).
  • defaultVisibleColumns — the initial set for a new user and the target of "Reset to default" (which is disabled when the current state already matches). Locked columns are always included.
  • initialVisibleColumns / initialColumnOrder — seed the first render from saved preferences, kept distinct from the reset-default so "Reset" still returns to the design default rather than the user's last state.
  • columns-changed — fires on apply with the new { visible, order }; store it against the user and feed it back via the initial* props on the next visit.

Rendering the picker outside the toolbar

Set :show-column-picker="false" and place FTGridColumnPicker anywhere (e.g. a page action bar) using the grid's exposed state and handlers via a template ref:

vue
<FTGridColumnPicker
  :columns="grid.pickerColumns"
  :visible-keys="grid.pickerVisible"
  :locked-keys="grid.pickerLocked"
  :default-keys="grid.pickerDefault"
  :default-order="grid.pickerOrder"
  @apply="grid.applyColumns"
  @reorder="grid.reorderColumns"
/>
<FTGrid ref="grid" :items="rows" :columns="columns" :show-column-picker="false" />
<FTGridColumnPicker
  :columns="grid.pickerColumns"
  :visible-keys="grid.pickerVisible"
  :locked-keys="grid.pickerLocked"
  :default-keys="grid.pickerDefault"
  :default-order="grid.pickerOrder"
  @apply="grid.applyColumns"
  @reorder="grid.reorderColumns"
/>
<FTGrid ref="grid" :items="rows" :columns="columns" :show-column-picker="false" />

Server-side pagination

By default FTGrid paginates, sorts, and filters client-side over the full items array. For large datasets, switch to server-side (controlled) pagination — the same model as FTTable. Set simplePagination and supply totalItems, then pass only the current page of rows as items. The grid disables its internal pagination, drives the pager from totalItems, and emits page-changed / size-changed / sorting-changed so you can refetch.

vue
<script lang="ts" setup>
import { ref } from 'vue';
import { FTGrid } from '@fasttrack-solutions/vue-components-lib';

const page = ref(1);
const pageSize = ref(25);
const rows = ref([]);
const total = ref(0);

async function fetchPage() {
  const res = await api.getPlayers({ page: page.value, pageSize: pageSize.value });
  rows.value = res.data; // just this page
  total.value = res.total; // server's grand total
}

function onPageChanged(p: number) {
  page.value = p;
  fetchPage();
}
function onSizeChanged(size: number) {
  pageSize.value = size;
  fetchPage();
}
function onSortingChanged(sort: { sortKey: string; sortOrder: 'asc' | 'desc' | '' }) {
  // re-query ordered by sort.sortKey / sort.sortOrder, then fetchPage()
}
</script>

<template>
  <FTGrid
    :items="rows"
    :columns="columns"
    simplePagination
    :totalItems="total"
    :page="page"
    :pageSize="pageSize"
    @page-changed="onPageChanged"
    @size-changed="onSizeChanged"
    @sorting-changed="onSortingChanged"
  />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { FTGrid } from '@fasttrack-solutions/vue-components-lib';

const page = ref(1);
const pageSize = ref(25);
const rows = ref([]);
const total = ref(0);

async function fetchPage() {
  const res = await api.getPlayers({ page: page.value, pageSize: pageSize.value });
  rows.value = res.data; // just this page
  total.value = res.total; // server's grand total
}

function onPageChanged(p: number) {
  page.value = p;
  fetchPage();
}
function onSizeChanged(size: number) {
  pageSize.value = size;
  fetchPage();
}
function onSortingChanged(sort: { sortKey: string; sortOrder: 'asc' | 'desc' | '' }) {
  // re-query ordered by sort.sortKey / sort.sortOrder, then fetchPage()
}
</script>

<template>
  <FTGrid
    :items="rows"
    :columns="columns"
    simplePagination
    :totalItems="total"
    :page="page"
    :pageSize="pageSize"
    @page-changed="onPageChanged"
    @size-changed="onSizeChanged"
    @sorting-changed="onSortingChanged"
  />
</template>

Slots

SlotDescription
toolbarCustom content rendered in the toolbar, before the column picker (only renders when showColumnPicker is true or exportFileName is set)