Appearance
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
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) - currency —
currencySymbolprepended (e.g.€) - date — formatted via
dateFormatprop (relative/absolute/iso) - avatar — image from
avatarFieldrow 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
| Prop | Type | Default | Description |
|---|---|---|---|
items | Record<string, unknown>[] | — | Row data |
columns | IFTGridColumnDef[] | — | 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 |
pageSize | number | 25 | Initial rows per page |
pageSizeOptions | number[] | [10,25,50,100] | Choices in the page-size selector |
showColumnPicker | boolean | true | Render the toolbar column picker |
lockedColumns | string[] | [] | Field keys that can't be hidden or reordered — always visible, pinned first |
defaultVisibleColumns | string[] | undefined | undefined | Field keys visible on first render + restored by "Reset to default" (locked always included). When omitted, all columns show |
initialVisibleColumns | string[] | undefined | undefined | Saved visible set that seeds the initial render (e.g. user prefs); falls back to the default |
initialColumnOrder | string[] | undefined | undefined | Saved column order that seeds the initial render; falls back to the authored order |
exportFileName | string | undefined | undefined | When set, exposes CSV export (file name) |
loading | boolean | false | Show AG Grid's loading overlay |
error | boolean | string | false | Replace the grid with the #error slot; a string is shown as the default message |
cellRendererOverrides | Record<string, CellRenderer> | {} | Per-field override of the default cellType renderer (wrapper-component use) |
simplePagination | boolean | false | Enable server-side (controlled) pagination — see below |
totalItems | number | null | null | Server's grand total row count (server-side mode) |
page | number | null | null | Controlled current page, 1-indexed (server-side mode) |
Events
| Event | Payload | Description |
|---|---|---|
selection-changed | rows: Record<string, unknown>[] | Fired when utility: 'checkbox' selection changes |
page-changed | page: number | Current page (1-indexed) |
size-changed | size: number | New 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 theinitial*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
| Slot | Description |
|---|---|
toolbar | Custom content rendered in the toolbar, before the column picker (only renders when showColumnPicker is true or exportFileName is set) |