HomeGetting StartedColor SystemTypographySpacingIconographyLayoutInstallationUsageAngularBlazorReactSvelteVueThemingTypographyCustomizationCSS-Only ComponentsFormsIconsIllustrationsAccessibilityGlobal ConfigurationTailwind CSSSass LibraryForge MCP ServerDropdown OptionsFocus Indicator ClippingAll componentsButtonButton AreaChipFloating Action ButtonIcon ButtonSplit ButtonAutocompleteButton ToggleCalendarCheckboxChip FieldColor PickerDate PickerDate Range PickerFile PickerLabelRadioSelectSliderSwitchText FieldTime PickerApp BarApp LauncherDrawerMenuPaginatorSkip LinkStepperTabsView SwitcherAccordionApp LayoutCardDividerExpansion PanelScaffoldSplit ViewStackStructured CardToolbarBadgeBannerBusy IndicatorCircular ProgressDialogInline MessageLinear ProgressPage StateSkeletonToastTooltipAvatarIconKeyLabel ValueListMeterSecretTableTimelineTimestampTreeKeyboard ShortcutPopoverIntroductionColorTypographySpacingShapeBorderElevationLayeringAnimationUsageIcon LibraryBlocksRoadmapChangelogBlog

Table

Tables display sets of data in sortable, filterable, selectable rows and columns, configured through data and column definitions.

NameRoleStatus
Ada LovelaceEngineerActive
Grace HopperAdmiralActive
Alan TuringResearcherAway

Usage

forge-table renders rows and columns from two JS properties — data (an array of row objects) and columnConfigurations (column definitions) — rather than static HTML attributes, plus select/multiselect, filter, dense/roomy, resizable, and fixedHeaders. It’s a basic data table, not a fully-featured grid — use it when content has three or more well-defined, sortable dimensions and users need to compare across rows. For content that’s primarily scanned rather than compared, or that’s mostly viewed on mobile, use a list instead. And if you need virtual or infinite scrolling, or more advanced filtering than a single input per column, reach for a dedicated grid library like AG Grid instead — forge-table intentionally stays basic.

Rules

Do
Order columns left to right by importance, so the most critical information needs the least scrolling.Use sentence case for column headers.Use the dense variant for data-dense applications where more rows need to fit on screen.Pair a table with a paginator when the data set is too large to show all at once.
Don't
Don't use a table when users primarily scan and identify a single item — a list is simpler.Don't use a table as the primary view on mobile — it doesn't adapt well to narrow screens.Don't display more than two custom header actions inline — tuck the rest into an overflow menu.

Basic usage

Set data and columnConfigurations as JS properties to populate the table — they can’t be expressed as static HTML attributes, so explore the live behavior in the Storybook playground above. The markup below shows the table’s underlying structure using the plain, CSS-only escape hatch instead of the full component.

The table's row/column structure, shown with the CSS-only markup fallback.

Inside a card

Place a table in a forge-card no-padding with a forge-scaffold, so it runs edge to edge. Put the table title and icon actions (filter, export, more) in the header toolbar, and a forge-paginator in an inverted footer toolbar. The primary page action, like “New order”, goes in the page’s body header, not the card.

<forge-card no-padding>
  <forge-scaffold>
    <forge-toolbar slot="header">
      <h3 slot="start" class="forge-typography--heading3">Orders</h3>
      <forge-icon-button slot="end" density="medium" aria-label="Filter orders">
        <forge-icon name="filter_list"></forge-icon>
      </forge-icon-button>
    </forge-toolbar>
    <forge-table slot="body"></forge-table>
    <forge-toolbar slot="footer" inverted>
      <forge-paginator slot="end"></forge-paginator>
    </forge-toolbar>
  </forge-scaffold>
</forge-card>

Configuring columns

Each entry in columnConfigurations describes one column: header (and optional footer) text, the property to read from each row in data, and whether it’s sortable (with initialSort marking the column sorted by default) or filter-able.

import type { IColumnConfiguration } from "@tylertech/forge";

const columnConfigurations: IColumnConfiguration[] = [
  { header: "First name", property: "firstName", sortable: true, initialSort: true },
  { header: "Last name", property: "lastName", sortable: true },
  { header: "Age", property: "age", sortable: true },
];

tableEl.columnConfigurations = columnConfigurations;
tableEl.data = data;

Column filters

Set filter: true on a column and give it a filterDelegate to render a filter input in the header. Use the built-in TextFieldComponentDelegate or SelectComponentDelegate for common input types, or build a custom delegate by extending BaseComponentDelegate or FormFieldComponentDelegate — either way, the delegate is a consistent interface for getting/setting a value and toggling invalid/disabled/required on whatever component it wraps.

import { TextFieldComponentDelegate, SelectComponentDelegate } from "@tylertech/forge";

table.columnConfigurations = [
  {
    property: "name",
    header: "Name",
    filter: true,
    filterDelegate: () => new TextFieldComponentDelegate({ options: { placeholder: "Filter name..." } }),
  },
  {
    property: "color",
    header: "Favorite color",
    filter: true,
    filterDelegate: () => new SelectComponentDelegate({ props: { placeholder: "Filter color..." } }),
  },
];

Set includeFooter to add a footer row, then give each column either a static footer string or a footerTemplate callback for custom content, like a computed average. Style footer cells individually via footerCellStyle.

tableEl.includeFooter = true;
tableEl.columnConfigurations = [
  { header: "First name", property: "firstName", footer: "Total" },
  {
    header: "Age",
    property: "age",
    footerTemplate: () => {
      const span = document.createElement("span");
      span.textContent = `Avg: ${average}`;
      return span;
    },
  },
];

Transforming values

Use a column’s transform callback to map a raw value to display text — a boolean to “Enabled”/“Disabled”, for example — without changing the underlying data.

{
  property: "active",
  header: "Status",
  transform: (value) => (value ? "Enabled" : "Disabled"),
}

Tables are used to display sets of data. They organize information into rows and columns, making it easier to read, compare, and analyze. The Forge table provides a configuration-based approach to building data tables with means for sorting, filtering, selecting, and customizing the display of tabular data.

import "@tylertech/forge/table";
Properties
PropertyAttributeTypeDefaultDescription
data—any[][]The data to be display in the table body.
columnConfigurations—IColumnConfiguration[][]The column configuration options.
selectselectbooleanfalseControls the visibility of the select column.
multiselectmultiselectbooleantrueControls the visibility of the select all checkbox (only applied when `select` is `true`).
selectKeyselect-keystring | string[]—The row key for matching data to selections.
tooltipSelecttooltip-selectstring | TableSelectTooltipCallback—The tooltip to display when hovering over the select column.
tooltipSelectAlltooltip-select-allstring—The tooltip to display when hovering over the select all checkbox.
densedensebooleanfalseControls whether the table is dense or not.
roomyroomybooleanfalseControls whether the table is roomy or not.
filterfilterbooleanfalseControls whether the table shows its column filter row.
fixedHeadersfixed-headersbooleanfalseControls whether the table applies fixed headers when in scroll containers.
layoutTypelayout-typeTableLayoutType'auto'Controls the table layout algorithm.
wrapContentwrap-contentbooleantrueControls whether the content in each cell wraps or not (true by default).
includeFooterinclude-footerbooleanfalseControls whether to render a table footer.
resizableresizablebooleanfalseControls whether the columns are resizable or not.
minResizeWidthmin-resize-widthnumber100Gets/sets the minimum width that a column can be resized to by the user dragging the resize handle.
allowRowClickallow-row-clickbooleanfalseGets/sets whether the rows respond to (and emit) row click events.
multiColumnSortmulti-column-sortbooleanfalseGets/sets whether the table supports multi-column sorting.
rowCreated—TableRowCreatedCallback—Callback for when a row is clicked. This allows for custom logic to run after each `<tr>` is created.
cellCreated—TableCellCreatedCallback—Callback for when a cell is clicked. This allows for custom logic to run after each `<td>` is created.
selectAllTemplate—TableHeaderSelectAllTemplate—The template to use for the select all checkbox in the header.
selectCheckboxAlignmentselect-checkbox-alignment`${CellAlign}`"center"Controls the alignment of the select checkbox.
Events
NameTypeDescription
forge-table-row-clickCustomEvent<ITableRowClickEventData>Dispatched when a row is clicked. Only applies when `allow-row-click` is specified.
forge-table-selectCustomEvent<ITableSelectEventData>Dispatched when a row is selected. Only applies when `select` is specified.
forge-table-select-doubleCustomEvent<ITableSelectDoubleEventData>Dispatched when a row is double-clicked. Only applies when `select` is specified.
forge-table-select-allCustomEvent<ITableSelectAllEventData>Dispatched when the select all checkbox is toggled. Only applies when `select` and `multiselect` is specified.
forge-table-sortCustomEvent<ITableSortEventData | ITableSortMultipleEventData>Dispatched when a column is sorted.
forge-table-filterCustomEvent<ITableFilterEventData>Dispatched when a column is filtered. Only applies when `filter` is specified.
forge-table-initializedCustomEvent<void>Dispatched when the table is initialized in the DOM for the first time.
forge-table-column-resizeCustomEvent<ITableColumnResizeEventData>Dispatched when a column is resized.
forge-table-before-body-renderedCustomEvent<void>Dispatched before the table body is rendered.
forge-table-body-renderedCustomEvent<void>Dispatched when the table body is rendered.
Methods
NameReturnsDescription
hideColumn(columnIndex)voidHides a column from the table.
showColumn(columnIndex)voidShows a hidden column in th table.
isColumnHidden(columnIndex)booleanDetermines if a column at the given index is hidden or not.
getSelectedRows()any[]Returns the selected row instances.
selectRow(data)voidSelects a row in the table. Only applicable if `select` is true.
selectRows(data, preserveExisting)voidSelects one or more rows in the table. Only applicable if `select` is true.
deselectRow(data)voidDeselects a single row in the table.
deselectRows(data)voidDeselects one or more rows in the table.
clearSelections()voidClears all selected table rows.
render()voidForces the table to re-render based on its current configuration.
expandRow(rowIndex, template)Promise<void>Expands a collapsed row.
collapseRow(rowIndex)Promise<void>Collapses an expanded row.
isRowExpanded(rowIndex)booleanChecks if a row is expanded or not.
selectRowsByIndex(indexes, preserveExisting)voidSelects a rows by an index or array of indexes.
deselectRowsByIndex(indexes)voidDeselects a rows by an index or array of indexes.
isRowSelected(rowData)booleanChecks if a row is selected or not.

Don’t use a table for layout

Reserve forge-table for actual tabular data — using it to lay out unrelated content makes the page harder to parse for screen reader users, who rely on table semantics to mean an actual data grid.

Row selection

The selection checkbox in each row automatically receives an aria-label="Select row", so no extra labeling is needed there.

Interactive cell content

If a column renders an interactive control (a button, a link, a form field), make sure it’s reachable and operable by keyboard, the same as it would be with a mouse.

Loading Forge chat…