gp-grid-logo
Examples

Custom Renderers

Render custom Vue 3 components inside grid cells and headers, including badges, charts, action menus, and any other interactive UI you need to embed today.

Customize how cells and headers are rendered.

Interactive Demo

Cell Renderers

gp-grid for Vue accepts two renderer styles and you can mix them freely in the same grid:

  • h() render functions — great for small inline renderers
  • Single-File Components — great for anything with template syntax, scoped styles, or reusable logic

Mixing both in one grid

StatusCell.vue
<script setup lang="ts">
import { type CellRendererParams } from "@gp-grid/vue";

const props = defineProps<CellRendererParams>();
</script>

<template>
  <div class="flex items-center h-full px-2">
    <span
      class="w-2 h-2 rounded-full mr-2"
      :style="{ backgroundColor: props.value === 'Active' ? 'green' : 'red' }"
    />
    {{ props.value }}
  </div>
</template>
PeopleGrid.vue
<script setup lang="ts">
import { Grid, type CellRendererParams, type ColumnDefinition } from "@gp-grid/vue";
import { h } from "vue";
import StatusCell from "./StatusCell.vue";

// h() render function — concise, no separate file
const SalaryRenderer = (params: CellRendererParams) => {
  const value = params.value as number;
  return h("div", {
    class: "flex items-center justify-end h-full px-2 tabular-nums",
  }, `$${value.toLocaleString()}`);
};

const columns: ColumnDefinition[] = [
  { field: "name", cellDataType: "text", width: 150 },
  {
    field: "status",
    cellDataType: "text",
    width: 120,
    // SFC passed directly — gp-grid handles the component lifecycle
    cellRenderer: StatusCell,
  },
  {
    field: "salary",
    cellDataType: "number",
    width: 140,
    // h() function for the same `cellRenderer` slot
    cellRenderer: SalaryRenderer,
  },
];

const data = [
  { name: "Giovanni", status: "Active", salary: 82000 },
  { name: "Luca", status: "Inactive", salary: 64000 },
];
</script>

<template>
  <div style="height: 400px">
    <Grid :columns="columns" :row-data="data" :row-height="36" />
  </div>
</template>

Both styles receive the same CellRendererParams and participate in the same virtualization pool — there is no performance penalty for mixing them.

Filters, sort comparisons, and copy-paste operate on the formatted value — the string users actually see — not the raw cell value. A cellRenderer only changes how a cell is painted; it does not change the value that powers filtering. Pair every custom renderer with a matching valueFormatter so the two stay in sync:

{
  field: "salary",
  cellDataType: "number",
  width: 140,
  cellRenderer: SalaryRenderer,
  valueFormatter: (value) => `$${(value as number).toLocaleString()}`,
}

Without the formatter, users filtering for "$82,000" on the Salary column would match nothing because the filter sees the raw 82000. See the full valueFormatter reference for signature, caveats, and more examples.

Registry shorthand

When the same renderer is used across multiple columns, register it once on the grid and reference it by key:

<script setup lang="ts">
import { Grid } from "@gp-grid/vue";
import StatusCell from "./StatusCell.vue";

const columns: ColumnDefinition[] = [
  { field: "name", cellDataType: "text", width: 150 },
  { field: "status", cellDataType: "text", width: 120, cellRenderer: "status" },
  { field: "manager", cellDataType: "text", width: 120, cellRenderer: "status" },
];
</script>

<template>
  <Grid
    :columns="columns"
    :row-data="data"
    :row-height="36"
    :cell-renderers="{ status: StatusCell }"
  />
</template>

The value of the registry map can itself be either an SFC or an h() function.

Header Renderers

Headers accept the same two styles as cells — h() functions or SFCs. The snippet below uses h() for brevity:

<script setup lang="ts">
import { type HeaderRendererParams } from "@gp-grid/vue";
import { h } from "vue";

const CustomHeader = (params: HeaderRendererParams) => {
  return h("div", {
    class: "flex items-center justify-between w-full h-full px-2",
  }, [
    h("span", { class: "font-bold" }, params.column.headerName),
    params.sortDirection && h("span", {},
      params.sortDirection === "asc" ? "▲" : "▼"
    ),
  ]);
};
</script>

<template>
  <Grid
    :columns="columns"
    :row-data="data"
    :row-height="36"
    :header-renderer="CustomHeader"
  />
</template>

Renderer Parameters

CellRendererParams

PropertyTypeDescription
valueCellValueCurrent cell value
rowDataTDataFull row data object
columnColumnDefinitionColumn definition
rowIndexnumberRow index
colIndexnumberColumn index
isActivebooleanIs the active cell
isSelectedbooleanIs in selection range
isEditingbooleanIs being edited

HeaderRendererParams

PropertyTypeDescription
columnColumnDefinitionColumn definition
colIndexnumberColumn index
sortDirectionSortDirection | undefinedCurrent sort
sortIndexnumber | undefinedMulti-sort order
sortablebooleanIs column sortable
filterablebooleanIs column filterable
hasFilterbooleanHas active filter
onSortfunctionTrigger sort
onFilterClickfunctionOpen filter popup