---
product: "AG Studio"
title: "Table Widgets"
description: "Show data in AG Studio Tables and Pivot Tables: which fields set columns, rows and values, total rows and columns, and Table Format settings."
framework: react
version: "3.1.0"
related:
    - title: "Using the Catalogue"
      url: "https://www.ag-grid.com/studio/analysts/widget-catalogue/"
    - title: "Value Widgets"
      url: "https://www.ag-grid.com/studio/analysts/value-widgets/"
    - title: "Filter Widgets"
      url: "https://www.ag-grid.com/studio/analysts/filter-widgets/"
    - title: "Static Content"
      url: "https://www.ag-grid.com/studio/analysts/static-content-widgets/"
llms: "https://www.ag-grid.com/studio/llms.txt"
---

# Table Widgets

Table Widgets display data in rows and columns.

**Table** lists rows and columns with sortable and filterable headers. Use it for row-level exploration of detailed data.

**Pivot Table** groups and pivots data across row and column axes. Use it for hierarchical drill-down and cross-tabulated summaries.

When the data changes while the report is open, a Table or Pivot Table updates in place, keeping selected rows and open row groups as they were. This only happens when your developer has connected live data to the report.

The example below shows a flat list of product sales above a pivoted view of the same data, with units sold and returned for each quarter.

#### Table

```ts
import {
  AgReportState,
  AgStudioApi,
  AgStudioProperties,
  createStudio,
  enableStudioDevValidations,
} from "ag-studio";
import { getData } from "./data.ts";

if (process.env.NODE_ENV !== "production") {
  // Enable extended validations only for development
  enableStudioDevValidations();
}

const initialState: AgReportState = {
  pages: [
    {
      id: "page1",
      widgets: {
        grid: {
          type: "grid",
          dataMapping: {
            cols: [
              { id: "sales.product" },
              { id: "sales.category" },
              { id: "sales.units", aggregation: "sum" },
              { id: "sales.revenue", aggregation: "sum" },
              { id: "sales.returns", aggregation: "sum" },
            ],
          },
          format: {
            title: {
              enabled: true,
              text: "Table",
            },
            subtitle: {
              enabled: true,
              text: "Annual sales and returns by product",
            },
          },
        },
        "pivot-grid": {
          type: "pivot-grid",
          dataMapping: {
            rows: [{ id: "sales.product" }],
            columns: [{ id: "sales.quarter" }],
            values: [
              { id: "sales.units", aggregation: "sum" },
              { id: "sales.returns", aggregation: "sum" },
            ],
          },
          format: {
            title: {
              enabled: true,
              text: "Pivot Table",
            },
            subtitle: {
              enabled: true,
              text: "Units sold and returned by product and quarter",
            },
          },
        },
      },
      widgetLayout: {
        grid: {
          xTrack: 0,
          yTrack: 0,
          xSpan: 24,
          ySpan: 20,
        },
        "pivot-grid": {
          xTrack: 0,
          yTrack: 20,
          xSpan: 24,
          ySpan: 20,
        },
      },
    },
  ],
  selectedPageId: "page1",
  panels: {
    filters: {
      collapsed: true,
    },
    data: {
      collapsed: true,
    },
    edit: {
      collapsed: true,
    },
  },
};

const studioProperties: AgStudioProperties = {
  mode: "edit",
  initialState,
  data: getData(),
};

let studioApi: AgStudioApi;

// setup Studio after the page has finished loading
const studioDiv = document.querySelector<HTMLElement>("#myStudio")!;
studioApi = createStudio(studioDiv, studioProperties);
```

[Live example: Table](https://www.ag-grid.com/studio/examples/table-widgets/table/typescript/)

## Table Setup

| Slot | Required | Accepts | What it does |
| --- | --- | --- | --- |
| Columns | Yes | One or more fields | Adds one column per field, in the order listed. Text and date fields group the rows. Numbers are aggregated, such as summed, for each row. |

A Table with only text and date fields lists each distinct combination of their values once. Adding a number adds its aggregated value for each combination. To list numbers without aggregating them, change the field's aggregation to Group By. See [Aggregation](https://www.ag-grid.com/studio/analysts/fields-in-widgets/#aggregation).

The example below shows two Tables over the same sales. The left Table has Category as its only text field, so it shows one row per category. The right Table adds Product, which splits each category into a row per product, and the numbers are summed for each row.

#### Table Columns

```ts
import {
  AgReportState,
  AgStudioApi,
  AgStudioProperties,
  createStudio,
  enableStudioDevValidations,
} from "ag-studio";
import { getData } from "./data.ts";

if (process.env.NODE_ENV !== "production") {
  // Enable extended validations only for development
  enableStudioDevValidations();
}

const initialState: AgReportState = {
  pages: [
    {
      id: "page1",
      widgets: {
        "by-category": {
          type: "grid",
          dataMapping: {
            cols: [
              { id: "sales.category" },
              { id: "sales.units", aggregation: "sum" },
              { id: "sales.revenue", aggregation: "sum" },
            ],
          },
          format: {
            title: {
              enabled: true,
              text: "Grouped by Category",
            },
            subtitle: {
              enabled: true,
              text: "Columns: Category, Units Sold (Sum), Revenue (Sum)",
            },
          },
        },
        "by-category-product": {
          type: "grid",
          dataMapping: {
            cols: [
              { id: "sales.category" },
              { id: "sales.product" },
              { id: "sales.units", aggregation: "sum" },
              { id: "sales.revenue", aggregation: "sum" },
            ],
          },
          format: {
            title: {
              enabled: true,
              text: "Grouped by Category and Product",
            },
            subtitle: {
              enabled: true,
              text: "Columns: Category, Product, Units Sold (Sum), Revenue (Sum)",
            },
          },
        },
      },
      widgetLayout: {
        "by-category": { xTrack: 0, yTrack: 0, xSpan: 12, ySpan: 22 },
        "by-category-product": { xTrack: 12, yTrack: 0, xSpan: 12, ySpan: 22 },
      },
    },
  ],
  selectedPageId: "page1",
  panels: {
    filters: {
      collapsed: true,
    },
    data: {
      collapsed: true,
    },
    edit: {
      collapsed: true,
    },
  },
};

const studioProperties: AgStudioProperties = {
  mode: "edit",
  initialState,
  data: getData(),
};

let studioApi: AgStudioApi;

// setup Studio after the page has finished loading
const studioDiv = document.querySelector<HTMLElement>("#myStudio")!;
studioApi = createStudio(studioDiv, studioProperties);
```

[Live example: Table Columns](https://www.ag-grid.com/studio/examples/table-widgets/table-columns/typescript/)

## Pivot Table Setup

A Pivot Table summarises one or more numeric fields across a row axis and, optionally, a column axis.

| Slot | Required | Accepts | What it does |
| --- | --- | --- | --- |
| Rows | No | One or more categories | Groups the rows, outer to inner. Each field adds a level of row grouping. |
| Columns | No | One or more categories | Pivots the Values onto a column axis, outer to inner, instead of listing them as flat columns. |
| Values | Yes | One or more numeric fields | Aggregates each field for each row and, once pivoted, for each row and column combination. |

In the example below, the Setup tab is open beside a Pivot Table with Region and Subcategory in the Rows slot, Channel in the Columns slot, and Net Sales and Gross Sales in the Values slot.

#### Pivot Table

```ts
import {
  AgReportState,
  AgStudioApi,
  AgStudioProperties,
  createStudio,
  enableStudioDevValidations,
} from "ag-studio";
import { getData } from "./data.ts";

if (process.env.NODE_ENV !== "production") {
  // Enable extended validations only for development
  enableStudioDevValidations();
}

const initialState: AgReportState = {
  pages: [
    {
      id: "a",
      widgets: {
        "sales-pivot": {
          type: "pivot-grid",
          dataMapping: {
            rows: [{ id: "sales.region" }, { id: "sales.subcategory" }],
            columns: [{ id: "sales.channel" }],
            values: [
              { id: "sales.net_sales", aggregation: "sum" },
              { id: "sales.gross_sales", aggregation: "sum" },
            ],
          },
          format: {
            title: {
              enabled: true,
              text: "Net Sales and Gross Sales by Region, Subcategory and Channel",
            },
          },
          sort: [{ field: { id: "sales.subcategory" }, direction: "asc" }],
        },
      },
      widgetLayout: {
        "sales-pivot": { xTrack: 0, yTrack: 0, xSpan: 24, ySpan: 41 },
      },
      selection: { type: "widget", id: "sales-pivot" },
    },
  ],
  selectedPageId: "a",
};

const studioProperties: AgStudioProperties = {
  mode: "edit",
  initialState,
  panels: {
    edit: {
      right: ["edit"],
    },
  },
  data: getData(),
};

let studioApi: AgStudioApi;

// setup Studio after the page has finished loading
const studioDiv = document.querySelector<HTMLElement>("#myStudio")!;
studioApi = createStudio(studioDiv, studioProperties);
```

[Live example: Pivot Table](https://www.ag-grid.com/studio/examples/table-widgets/pivot-table/typescript/)

### Columns

Row groups start expanded when no field is mapped to Columns, and collapsed once one is. Click a row to expand or collapse it, and expanding a row loads its child rows. Column groups always start collapsed, and you can click them to expand them.

The example below shows two Pivot Tables with Category and Product in the Rows slot. The left one has no field in Columns, so its categories start expanded and each value is a flat column. The right one adds Quarter to Columns, so its categories start collapsed and the values are pivoted by quarter.

#### Pivot Table Columns

```ts
import {
  AgReportState,
  AgStudioApi,
  AgStudioProperties,
  createStudio,
  enableStudioDevValidations,
} from "ag-studio";
import { getData } from "./data.ts";

if (process.env.NODE_ENV !== "production") {
  // Enable extended validations only for development
  enableStudioDevValidations();
}

const initialState: AgReportState = {
  pages: [
    {
      id: "page1",
      widgets: {
        "rows-only": {
          type: "pivot-grid",
          dataMapping: {
            rows: [{ id: "sales.category" }, { id: "sales.product" }],
            columns: [],
            values: [
              { id: "sales.units", aggregation: "sum" },
              { id: "sales.returns", aggregation: "sum" },
            ],
          },
          format: {
            title: {
              enabled: true,
              text: "Rows Only",
            },
            subtitle: {
              enabled: true,
              text: "Rows: Category, Product. Values: Units Sold, Returns",
            },
          },
        },
        "rows-and-columns": {
          type: "pivot-grid",
          dataMapping: {
            rows: [{ id: "sales.category" }, { id: "sales.product" }],
            columns: [{ id: "sales.quarter" }],
            values: [
              { id: "sales.units", aggregation: "sum" },
              { id: "sales.returns", aggregation: "sum" },
            ],
          },
          format: {
            title: {
              enabled: true,
              text: "Rows and Columns",
            },
            subtitle: {
              enabled: true,
              text: "Rows: Category, Product. Columns: Quarter. Values: Units Sold, Returns",
            },
          },
        },
      },
      widgetLayout: {
        "rows-only": { xTrack: 0, yTrack: 0, xSpan: 12, ySpan: 22 },
        "rows-and-columns": { xTrack: 12, yTrack: 0, xSpan: 12, ySpan: 22 },
      },
    },
  ],
  selectedPageId: "page1",
  panels: {
    filters: {
      collapsed: true,
    },
    data: {
      collapsed: true,
    },
    edit: {
      collapsed: true,
    },
  },
};

const studioProperties: AgStudioProperties = {
  mode: "edit",
  initialState,
  data: getData(),
};

let studioApi: AgStudioApi;

// setup Studio after the page has finished loading
const studioDiv = document.querySelector<HTMLElement>("#myStudio")!;
studioApi = createStudio(studioDiv, studioProperties);
```

[Live example: Pivot Table Columns](https://www.ag-grid.com/studio/examples/table-widgets/pivot-columns/typescript/)

### Empty Cells

A Pivot Table holds a cell for every combination of row and column member, but the data rarely fills all of them. Where a combination has no rows behind it, there is nothing to aggregate, so the cell shows `N/A` instead of a number. A cell showing `0` means the opposite: rows exist for that combination, and their values add up to zero.

Both appear in the Pivot Table in the first example on this page. Trail Runner X was launched in Q3, so it shows `N/A` for Q1 and Q2. A product that sold in a quarter but had nothing returned shows `0` in its Returns column.

## Format

Table Widgets have these settings of their own on the Format tab, beside the title and appearance settings every Widget has. Your developer can change the defaults shown.

| Setting | Where | Default | What it does |
| --- | --- | --- | --- |
| Auto Width | Table > Layout | On | Sizes each column to its content, widened to fill the Widget. |
| Column Width | Table > Layout | 100 | Sets the width of every column, in pixels, when Auto Width is off. |
| Wrap Text | Table > Header Row, Table only | Off | Wraps long column headers onto more than one line. |
| Total Row | Table section | On for a Table, off for a Pivot Table | Pins a total row at the bottom. See [Totals](https://www.ag-grid.com/studio/analysts/table-widgets/#totals). |
| Total Columns | Table section, Pivot Table only | Off | Adds a total column for each field in Values. See [Totals](https://www.ag-grid.com/studio/analysts/table-widgets/#totals). |
| Highlight Changes | Table section | Off | Draws attention to numbers that change while the Widget is open. See [Highlight Changes](https://www.ag-grid.com/studio/analysts/table-widgets/#highlight-changes). |

The Column Borders, Row Borders and Header Row groups in the Table section change the colour and width of the grid lines.

### Totals

Two independent totals are available:

- **Total Row** - a grand total pinned as the last row, summing every value column across the whole table. A Table shows it when it has a number column and at least one other column. A Pivot Table shows it once a field is mapped to Rows.
- **Total Columns** - available on a Pivot Table once a field is mapped to Columns. It adds a column summing each field in Values across every column member. Set **Placement** to **Beginning** or **End** (the default).

The Tables in the [Table Setup](https://www.ag-grid.com/studio/analysts/table-widgets/#table-setup) example show the Total Row a Table has by default. The example below turns it off on a Table, and shows each total turned on in its own Pivot Table, with Quarter in Rows and Category in Columns.

#### Totals

```ts
import {
  AgReportState,
  AgStudioApi,
  AgStudioProperties,
  createStudio,
  enableStudioDevValidations,
} from "ag-studio";
import { getData } from "./data.ts";

if (process.env.NODE_ENV !== "production") {
  // Enable extended validations only for development
  enableStudioDevValidations();
}

const initialState: AgReportState = {
  pages: [
    {
      id: "page1",
      widgets: {
        "table-total-row-off": {
          type: "grid",
          dataMapping: {
            cols: [
              { id: "sales.category" },
              { id: "sales.units", aggregation: "sum" },
              { id: "sales.revenue", aggregation: "sum" },
            ],
          },
          format: {
            title: {
              enabled: true,
              text: "Table: Total Row Off",
            },
            subtitle: {
              enabled: true,
              text: "Table > Total Row off",
            },
            style: {
              grandTotalRow: {
                enabled: false,
              },
            },
          },
        },
        "pivot-total-row": {
          type: "pivot-grid",
          dataMapping: {
            rows: [{ id: "sales.quarter" }],
            columns: [{ id: "sales.category" }],
            values: [{ id: "sales.revenue", aggregation: "sum" }],
          },
          format: {
            title: {
              enabled: true,
              text: "Pivot Table: Total Row",
            },
            subtitle: {
              enabled: true,
              text: "Table > Total Row on",
            },
            style: {
              grandTotalRow: {
                enabled: true,
              },
            },
          },
        },
        "pivot-total-columns": {
          type: "pivot-grid",
          dataMapping: {
            rows: [{ id: "sales.quarter" }],
            columns: [{ id: "sales.category" }],
            values: [{ id: "sales.revenue", aggregation: "sum" }],
          },
          format: {
            title: {
              enabled: true,
              text: "Pivot Table: Total Columns",
            },
            subtitle: {
              enabled: true,
              text: "Table > Total Columns on, Placement End",
            },
            style: {
              totalColumns: true,
            },
          },
        },
        "pivot-total-columns-beginning": {
          type: "pivot-grid",
          dataMapping: {
            rows: [{ id: "sales.quarter" }],
            columns: [{ id: "sales.category" }],
            values: [{ id: "sales.revenue", aggregation: "sum" }],
          },
          format: {
            title: {
              enabled: true,
              text: "Pivot Table: Total Columns at Beginning",
            },
            subtitle: {
              enabled: true,
              text: "Table > Total Columns on, Placement Beginning",
            },
            style: {
              totalColumns: true,
              totalColumnsPlacement: "before",
            },
          },
        },
      },
      widgetLayout: {
        "table-total-row-off": { xTrack: 0, yTrack: 0, xSpan: 12, ySpan: 20 },
        "pivot-total-row": { xTrack: 12, yTrack: 0, xSpan: 12, ySpan: 20 },
        "pivot-total-columns": { xTrack: 0, yTrack: 20, xSpan: 24, ySpan: 18 },
        "pivot-total-columns-beginning": {
          xTrack: 0,
          yTrack: 38,
          xSpan: 24,
          ySpan: 18,
        },
      },
    },
  ],
  selectedPageId: "page1",
  panels: {
    filters: {
      collapsed: true,
    },
    data: {
      collapsed: true,
    },
    edit: {
      collapsed: true,
    },
  },
};

const studioProperties: AgStudioProperties = {
  mode: "edit",
  initialState,
  data: getData(),
};

let studioApi: AgStudioApi;

// setup Studio after the page has finished loading
const studioDiv = document.querySelector<HTMLElement>("#myStudio")!;
studioApi = createStudio(studioDiv, studioProperties);
```

[Live example: Totals](https://www.ag-grid.com/studio/examples/table-widgets/table-totals/typescript/)

### Highlight Changes

When data changes while the Widget is open, a Table can draw attention to the numbers that moved. To use it, turn on **Highlight Changes**, the last toggle in the Table section of the Format tab. It is off by default, and is available on both the Table and the Pivot Table.

- A value that increases shows the difference beside it with an up arrow, and a value that decreases shows it with a down arrow. The new value is highlighted with a background that fades, and the difference disappears after about two seconds.
- Nothing is animated when the Widget first loads, only when values change later, including a change caused by a filter. A value that changes from 0 shows the difference but no highlight.
- Aggregated numeric columns are animated. Columns the Table groups its rows by are not, unless the data source identifies each row, in which case every numeric column is animated.
- If your developer has customised how a column displays its values, their version is used instead of the animation.
- The Highlight Changes group holds three colour pickers: **Up Color** for an increase, **Down Color** for a decrease, and **Background Color** for the highlight. Each starts from the theme's colour, which your developer can set, and changes it for this Widget only.

In the example below, both Tables show units sold by product and have Highlight Changes on. The right-hand Table also sets the three colours. Click **Start Updates** to change the orders behind the totals.

#### Highlight Changes

```ts
import {
  AgReportState,
  AgStudioApi,
  AgStudioProperties,
  createStudio,
  enableStudioDevValidations,
} from "ag-studio";
import {
  createRandomTransaction,
  getInitialOrders,
  orderFields,
} from "./data.ts";

if (process.env.NODE_ENV !== "production") {
  // Enable extended validations only for development
  enableStudioDevValidations();
}

const initialState: AgReportState = {
  pages: [
    {
      id: "page1",
      widgets: {
        "default-colours": {
          type: "grid",
          dataMapping: {
            cols: [
              { id: "orders.product" },
              { id: "orders.units", aggregation: "sum" },
            ],
          },
          format: {
            title: {
              enabled: true,
              text: "Highlight Changes",
            },
            subtitle: {
              enabled: true,
              text: "Table > Highlight Changes on",
            },
            style: {
              animateValueChanges: true,
            },
          },
        },
        "custom-colours": {
          type: "grid",
          dataMapping: {
            cols: [
              { id: "orders.product" },
              { id: "orders.units", aggregation: "sum" },
            ],
          },
          format: {
            title: {
              enabled: true,
              text: "Custom Colours",
            },
            subtitle: {
              enabled: true,
              text: "Highlight Changes > all three colours set",
            },
            style: {
              animateValueChanges: true,
              theme: {
                valueChangeUpColor: "#1e88e5",
                valueChangeDownColor: "#f57c00",
                valueChangeHighlightBackgroundColor: "#fff176",
              },
            },
          },
        },
      },
      widgetLayout: {
        "default-colours": { xTrack: 0, yTrack: 0, xSpan: 12, ySpan: 18 },
        "custom-colours": { xTrack: 12, yTrack: 0, xSpan: 12, ySpan: 18 },
      },
    },
  ],
  selectedPageId: "page1",
  panels: {
    filters: {
      collapsed: true,
    },
    data: {
      collapsed: true,
    },
    edit: {
      collapsed: true,
    },
  },
};

const studioProperties: AgStudioProperties = {
  mode: "edit",
  initialState,
  data: {
    sources: [
      {
        id: "orders",
        name: "Orders",
        transactions: "all",
        primaryKeyField: "id",
        data: getInitialOrders(),
        fields: orderFields,
      },
    ],
  },
};

let studioApi: AgStudioApi;
let updateTimer: ReturnType<typeof setInterval> | undefined;

function applyRandomChange() {
  const engine = studioApi.getDataEngine();
  engine?.applyTransaction?.(createRandomTransaction());
}

function startUpdates() {
  if (updateTimer == null) {
    updateTimer = setInterval(applyRandomChange, 800);
  }
}

function stopUpdates() {
  clearInterval(updateTimer);
  updateTimer = undefined;
}

// setup Studio after the page has finished loading
const studioDiv = document.querySelector<HTMLElement>("#myStudio")!;
studioApi = createStudio(studioDiv, studioProperties);

if (typeof window !== "undefined") {
  // Attach external event handlers to window so they can be called from index.html
  (<any>window).startUpdates = startUpdates;
  (<any>window).stopUpdates = stopUpdates;
}
```

[Live example: Highlight Changes](https://www.ag-grid.com/studio/examples/table-widgets/highlight-changes/typescript/)
