Pinned rows appear either above or below the normal rows of a table. This is sometimes also known as Frozen Rows or Floating Rows. Rows can be pinned via the Context Menu or Grid Options.
Enabling Row Pinning Copy Link
To enable row pinning, set enableRowPinning to true. To restrict pinning to only one direction, set it to 'top' or 'bottom'.
const gridOptions = {
enableRowPinning: true,
// other grid options ...
} Pinning Rows on First Render Copy Link
To have a row appear as pinned when the grid initially renders data, use the isRowPinned callback. Returning 'top' or 'bottom' from this callback will pin the row to the top or bottom respectively, and returning null or undefined will leave the row unpinned. As an example, the snippet below will pin all rows whose country field is null in the top container, and all the other rows will be unpinned.
const gridOptions = {
enableRowPinning: true,
isRowPinned: (rowNode) => {
return rowNode.data?.country == null ? 'top' : null;
},
// other grid options ...
}This is illustrated in the example below. Rows that are pinned appear fixed at the top (or bottom) of the grid, as well as remaining in the main viewport. Pinned rows are styled bold by default to visually distinguish them.
import {
ClientSideRowModelModule,
ColDef,
GridApi,
GridOptions,
ModuleRegistry,
PinnedRowModule,
createGrid,
enableDevValidations,
themeQuartz,
} from "ag-grid-community";
import { ContextMenuModule } from "ag-grid-enterprise";
import { IOlympicData } from "./interfaces";
// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
enableDevValidations();
}
ModuleRegistry.registerModules([
ClientSideRowModelModule,
ContextMenuModule,
PinnedRowModule,
]);
const columnDefs: ColDef[] = [
{ field: "athlete" },
{ field: "country" },
{ field: "sport" },
];
let gridApi: GridApi<IOlympicData>;
const gridOptions: GridOptions<IOlympicData> = {
defaultColDef: {
flex: 1,
},
columnDefs: columnDefs,
rowData: null,
enableRowPinning: true,
isRowPinned: (rowNode) => {
return rowNode.data?.country == null ? "top" : null;
},
theme: themeQuartz.withParams({
pinnedRowBorder: {
width: 2,
},
}),
};
const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(gridDiv, gridOptions);
fetch("https://www.ag-grid.com/example-assets/small-olympic-winners.json")
.then((response) => response.json())
.then((data: IOlympicData[]) => gridApi!.setGridOption("rowData", data));
<div id="myGrid" style="height: 100%"></div>
export interface IOlympicData {
athlete: string,
age: number,
country: string,
year: number,
date: string,
sport: string,
gold: number,
silver: number,
bronze: number,
total: number
} Note that all the examples on this page also apply additional styling to visually separate the pinned rows from the rows in the main viewport.
const gridOptions = {
theme: themeQuartz.withParams({
pinnedRowBorder: {
width: 2
},
}),
// other grid options ...
}To see what other parameters are available for the theming of pinned rows, see the Theme Builder.
Pinning Rows via the Context Menu Copy Link
This approach requires the Context Menu which is an Enterprise feature.
To pin a row, use right-click to bring up the Context Menu and select one of the options in the "Pin Row" submenu. In the example below, try the following:
- Pin a row to the top.
- Switch the row to being pinned to the bottom.
- Unpin the row.
import {
ClientSideRowModelModule,
ColDef,
GridApi,
GridOptions,
ModuleRegistry,
PinnedRowModule,
createGrid,
enableDevValidations,
themeQuartz,
} from "ag-grid-community";
import { ContextMenuModule } from "ag-grid-enterprise";
import { IOlympicData } from "./interfaces";
// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
enableDevValidations();
}
ModuleRegistry.registerModules([
PinnedRowModule,
ClientSideRowModelModule,
ContextMenuModule,
]);
const columnDefs: ColDef[] = [
{ field: "athlete" },
{ field: "country" },
{ field: "sport" },
];
let gridApi: GridApi<IOlympicData>;
const gridOptions: GridOptions<IOlympicData> = {
defaultColDef: {
flex: 1,
},
columnDefs: columnDefs,
rowData: null,
enableRowPinning: true,
theme: themeQuartz.withParams({
pinnedRowBorder: {
width: 2,
},
}),
};
const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(gridDiv, gridOptions);
fetch("https://www.ag-grid.com/example-assets/small-olympic-winners.json")
.then((response) => response.json())
.then((data: IOlympicData[]) => gridApi!.setGridOption("rowData", data));
<div id="myGrid" style="height: 100%"></div>
export interface IOlympicData {
athlete: string,
age: number,
country: string,
year: number,
date: string,
sport: string,
gold: number,
silver: number,
bronze: number,
total: number
} Preventing Rows from being Pinnable Copy Link
To prevent a user from pinning a row via the context menu, use the isRowPinnable callback. As an example, the snippet below will prevent any rows being pinned where the sport field is 'Swimming'.
const gridOptions = {
enableRowPinning: true,
isRowPinnable: (rowNode) => {
return rowNode.data?.sport != 'Swimming';
},
// other grid options ...
}This is illustrated in the example below. Note that the "Pin Rows" submenu does not appear in the context menu for rows whose sport field is 'Swimming'.
import {
ClientSideRowModelModule,
ColDef,
GridApi,
GridOptions,
ModuleRegistry,
PinnedRowModule,
createGrid,
enableDevValidations,
themeQuartz,
} from "ag-grid-community";
import { ClipboardModule, ContextMenuModule } from "ag-grid-enterprise";
import { IOlympicData } from "./interfaces";
// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
enableDevValidations();
}
ModuleRegistry.registerModules([
PinnedRowModule,
ClientSideRowModelModule,
ContextMenuModule,
ClipboardModule,
]);
const columnDefs: ColDef[] = [
{ field: "athlete" },
{ field: "country" },
{ field: "sport" },
];
let gridApi: GridApi<IOlympicData>;
const gridOptions: GridOptions<IOlympicData> = {
defaultColDef: {
flex: 1,
},
columnDefs: columnDefs,
rowData: null,
enableRowPinning: true,
isRowPinnable: (rowNode) => {
return rowNode.data?.sport != "Swimming";
},
theme: themeQuartz.withParams({
pinnedRowBorder: {
width: 2,
},
}),
};
const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(gridDiv, gridOptions);
fetch("https://www.ag-grid.com/example-assets/small-olympic-winners.json")
.then((response) => response.json())
.then((data: IOlympicData[]) => gridApi!.setGridOption("rowData", data));
<div id="myGrid" style="height: 100%"></div>
export interface IOlympicData {
athlete: string,
age: number,
country: string,
year: number,
date: string,
sport: string,
gold: number,
silver: number,
bronze: number,
total: number
} Sorting and Filtering Pinned Rows Copy Link
When sorts and filters are applied to the grid, they will also be applied to pinned rows.
import {
ClientSideRowModelModule,
ColDef,
ColumnApiModule,
GridApi,
GridOptions,
ModuleRegistry,
PinnedRowModule,
createGrid,
enableDevValidations,
themeQuartz,
} from "ag-grid-community";
import { ContextMenuModule, SetFilterModule } from "ag-grid-enterprise";
import { IOlympicData } from "./interfaces";
// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
enableDevValidations();
}
ModuleRegistry.registerModules([
PinnedRowModule,
ClientSideRowModelModule,
ContextMenuModule,
SetFilterModule,
ColumnApiModule,
]);
const columnDefs: ColDef[] = [
{ field: "athlete" },
{ field: "country" },
{ field: "sport", filter: true, floatingFilter: true },
{ field: "gold" },
];
let gridApi: GridApi<IOlympicData>;
const gridOptions: GridOptions<IOlympicData> = {
defaultColDef: {
flex: 1,
},
columnDefs: columnDefs,
rowData: null,
enableRowPinning: true,
isRowPinned: (node) => (!node.data?.country ? "top" : null),
theme: themeQuartz.withParams({
pinnedRowBorder: {
width: 2,
},
}),
};
const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(gridDiv, gridOptions);
fetch("https://www.ag-grid.com/example-assets/small-olympic-winners.json")
.then((response) => response.json())
.then((data: IOlympicData[]) => {
gridApi!.setGridOption("rowData", data);
filterSwimming(gridApi);
sortGold(gridApi);
});
function filterSwimming(api: GridApi<IOlympicData>) {
api
.setColumnFilterModel("sport", { values: ["Swimming"] })
.then(() => api.onFilterChanged());
}
function sortGold(api: GridApi<IOlympicData>) {
api.applyColumnState({ state: [{ colId: "gold", sort: "desc" }] });
}
<div id="myGrid" style="height: 100%"></div>
export interface IOlympicData {
athlete: string,
age: number,
country: string,
year: number,
date: string,
sport: string,
gold: number,
silver: number,
bronze: number,
total: number
} Selecting Pinned Rows Copy Link
Pinned rows can be selected just as normal rows can be selected. The selection state of a pinned row will mirror the selection state of the original row.
import {
ClientSideRowModelModule,
ColDef,
GridApi,
GridOptions,
ModuleRegistry,
PinnedRowModule,
RowApiModule,
RowSelectionModule,
createGrid,
enableDevValidations,
themeQuartz,
} from "ag-grid-community";
import { ContextMenuModule } from "ag-grid-enterprise";
import { IOlympicData } from "./interfaces";
// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
enableDevValidations();
}
ModuleRegistry.registerModules([
ClientSideRowModelModule,
PinnedRowModule,
RowSelectionModule,
RowApiModule,
ContextMenuModule,
]);
const columnDefs: ColDef[] = [
{ field: "athlete" },
{ field: "country" },
{ field: "sport" },
];
let gridApi: GridApi<IOlympicData>;
const gridOptions: GridOptions<IOlympicData> = {
defaultColDef: {
flex: 1,
},
columnDefs: columnDefs,
rowData: null,
enableRowPinning: true,
isRowPinned: (node) => (!node.data?.country ? "top" : null),
rowSelection: {
mode: "multiRow",
},
onFirstDataRendered: () => {
["1", "3", "5"].forEach((id) => {
gridApi.getRowNode(id)?.setSelected(true);
});
},
theme: themeQuartz.withParams({
pinnedRowBorder: {
width: 2,
},
}),
};
const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(gridDiv, gridOptions);
fetch("https://www.ag-grid.com/example-assets/small-olympic-winners.json")
.then((response) => response.json())
.then((data: IOlympicData[]) => gridApi!.setGridOption("rowData", data));
<div id="myGrid" style="height: 100%"></div>
export interface IOlympicData {
athlete: string,
age: number,
country: string,
year: number,
date: string,
sport: string,
gold: number,
silver: number,
bronze: number,
total: number
} Pinning the Grand Total Row Copy Link
The Grand Total Rows can be pinned in three ways.
- Setting the value of the
grandTotalRowgrid option to either'pinnedTop'or'pinnedBottom'. - Manually pin the grand total row via the context menu when
grandTotalRowis either'top'or'bottom'. - Return
'top'or'bottom'from theisRowPinnedcallback when it's called on the grand total row node.
import {
ClientSideRowModelModule,
ColDef,
GridApi,
GridOptions,
ModuleRegistry,
PinnedRowModule,
RowPinnedType,
createGrid,
enableDevValidations,
themeQuartz,
} from "ag-grid-community";
import { ContextMenuModule, RowGroupingModule } from "ag-grid-enterprise";
import { IOlympicData } from "./interfaces";
// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
enableDevValidations();
}
ModuleRegistry.registerModules([
ClientSideRowModelModule,
RowGroupingModule,
ContextMenuModule,
PinnedRowModule,
]);
const columnDefs: ColDef[] = [
{ field: "athlete" },
{ field: "country", rowGroup: true, hide: true },
{ field: "sport" },
{ field: "gold", aggFunc: "sum" },
];
let gridApi: GridApi<IOlympicData>;
const gridOptions: GridOptions<IOlympicData> = {
defaultColDef: {
flex: 1,
},
autoGroupColumnDef: {
headerName: "Country",
},
columnDefs,
rowData: null,
enableRowPinning: true,
onFirstDataRendered: () => {
const value = getGrandTotalRow();
if (value === "isRowPinned") {
setGrandTotalRow(gridApi, "bottom");
setIsRowPinned(gridApi, "top");
} else {
setGrandTotalRow(gridApi, value);
}
},
theme: themeQuartz.withParams({
pinnedRowBorder: {
width: 2,
},
}),
};
const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(gridDiv, gridOptions);
fetch("https://www.ag-grid.com/example-assets/small-olympic-winners.json")
.then((response) => response.json())
.then((data: IOlympicData[]) => gridApi!.setGridOption("rowData", data));
function getGrandTotalRow() {
return document.querySelector<HTMLSelectElement>("#select-grand-total-row")
?.value as GridOptions["grandTotalRow"] | "isRowPinned";
}
function setGrandTotalRow(
api: GridApi<IOlympicData>,
value: GridOptions["grandTotalRow"],
) {
api.setGridOption("grandTotalRow", value);
}
function setIsRowPinned(api: GridApi<IOlympicData>, value: RowPinnedType) {
api.setGridOption("isRowPinned", (node) => {
if (node.level === -1 && node.footer) {
return value;
}
});
}
function update() {
const value = getGrandTotalRow();
if (value === "isRowPinned") {
setGrandTotalRow(gridApi, "bottom");
setIsRowPinned(gridApi, "top");
} else {
setGrandTotalRow(gridApi, value);
}
}
if (typeof window !== "undefined") {
// Attach external event handlers to window so they can be called from index.html
(<any>window).update = update;
}
.example-wrapper {
display: flex;
flex-direction: column;
height: 100%;
}
.example-header {
margin-bottom: 10px;
}
#myGrid {
flex: 1 1 0px;
width: 100%;
}
<div class="example-wrapper">
<div class="example-header">
<select id="select-grand-total-row" onchange="update()">
<option value="pinnedBottom">pinnedBottom</option>
<option value="pinnedTop">pinnedTop</option>
<option value="bottom">bottom</option>
<option value="top">top</option>
<option value="isRowPinned">isRowPinned</option>
</select>
</div>
<div id="myGrid" style="height: 100%"></div>
</div>
export interface IOlympicData {
athlete: string,
age: number,
country: string,
year: number,
date: string,
sport: string,
gold: number,
silver: number,
bronze: number,
total: number
} Note that when grandTotalRow is 'pinnedTop' or 'pinnedBottom' the user is not able to unpin the grand total row.
Providing Pinned Row Data Copy Link
You cannot provide pinned row data at the same time as using enableRowPinning to manually pin rows.
Pinned row data may be provided directly to the grid via Grid Options. Providing pinned rows this way means they cannot be altered by end users.
import {
ClientSideRowModelModule,
ColDef,
GridApi,
GridOptions,
ModuleRegistry,
PinnedRowModule,
createGrid,
enableDevValidations,
themeQuartz,
} from "ag-grid-community";
import { IOlympicData } from "./interfaces";
// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
enableDevValidations();
}
ModuleRegistry.registerModules([PinnedRowModule, ClientSideRowModelModule]);
const columnDefs: ColDef[] = [
{ field: "athlete" },
{ field: "country" },
{ field: "sport" },
];
let gridApi: GridApi<IOlympicData>;
const gridOptions: GridOptions<IOlympicData> = {
defaultColDef: {
flex: 1,
},
columnDefs: columnDefs,
rowData: null,
theme: themeQuartz.withParams({
pinnedRowBackgroundColor:
"color-mix(in srgb, var(--ag-background-color), #ffeb3b 18%)",
}),
// no rows to pin to start with
pinnedTopRowData: [
{
athlete: "TOP (athlete)",
country: "TOP (country)",
sport: "TOP (sport)",
},
],
pinnedBottomRowData: [
{
athlete: "BOTTOM (athlete)",
country: "BOTTOM (country)",
sport: "BOTTOM (sport)",
},
],
};
const gridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(gridDiv, gridOptions);
fetch("https://www.ag-grid.com/example-assets/olympic-winners.json")
.then((response) => response.json())
.then((data: IOlympicData[]) => gridApi!.setGridOption("rowData", data));
<div id="myGrid" style="height: 100%"></div>
export interface IOlympicData {
athlete: string,
age: number,
country: string,
year: number,
date: string,
sport: string,
gold: number,
silver: number,
bronze: number,
total: number
} Set Pinned Rows using grid attributes pinnedTopRowData and pinnedBottomRowData.
Update the pinned rows using api.setGridOption('pinnedTopRowData', rows) and api.setGridOption('pinnedBottomRowData', rows).
Data to be displayed as pinned top rows in the grid. |
Data to be displayed as pinned bottom rows in the grid. |
Unsupported Features Copy Link
When providing pinned row data directly via pinnedTopRowData and pinnedBottomRowData, the following are not possible:
- Sorting: Pinned rows cannot be sorted.
- Filtering: Pinned rows are not filtered.
- Row Grouping: Pinned rows cannot be grouped.
- Row Selection: Pinned rows cannot be selected.
API Reference Copy Link
The pinned row state can be saved and restored as part of Grid State.
Determines whether manual row pinning is enabled via the row context menu.
Set to true to allow pinning rows to top or bottom.
Set to 'top' to allow pinning rows to the top only.
Set to 'bottom' to allow pinning rows to the bottom only. |
Return true if the grid should allow the row to be manually pinned.
Return false if the grid should prevent the row from being pinned
When not defined, all rows default to pinnable. |
Called for every row in the grid.
Return "top", "bottom" if the row should be initially pinned to the top or bottom respectively.
Return null or undefined otherwise.
User interactions can subsequently still change the pinned state of a row. |
Events Copy Link
A row has been pinned to top or bottom, or unpinned. |