Skip to content

React customization API

TableCustomizationOptions and the types below are exported by @inertiax/react-table. Pass customization options to <TableProvider customization={...}> or <AppTable customization={...} />. Resolution is built-in → provider → component, isolated from sibling tables.

prop Link to prop
Named host prop; required by Table and your app’s AppTable binding
host Link to host
Structural query service; required by Table, supplied by AppTable
envelope Link to envelope
Supplied response; required by the direct InertiaXRenderer
debounceMs Link to debounceMs
Shared search/filter delay, default 300 ms; nonnegative; 0 dispatches immediately
controllerRef Link to controllerRef
Ref to the table controller; cleared on unmount
customization Link to customization
Theme, text, regions, renderers, and event handlers
pipeline Link to pipeline
Advanced transformations of validated envelopes
children Link to children
React content or a function receiving the session; replaces the default table UI

See Direct envelope for the supplied-envelope workflow and ownership rules.

The built-in toolbar and TableFilters show the filters marked pinned() in Laravel. customization.pinnedFilters?: readonly string[] replaces that list with filter keys: a component list replaces the provider list, and [] pins nothing. Missing keys, blank keys, and duplicates report INERTIAX_REACT_TABLE_CUSTOMIZATION_INVALID.

A pinned filter shows a select when its equals clause takes one select value. Pinned editing supports Basic mode and Equals conditions in a flat AND expression. Choosing an option replaces the existing condition in place, or appends one when absent. Clearing removes only that condition. Each change uses the normal filter request delay and resets the page; applied chips and restored URL state use the same filter state.

Number 0, string "0", and Boolean false remain distinct. A filter without such a clause shows Edit filters instead, as do multiple() selects, repeated conditions, other comparisons such as Is empty, unavailable option values, and Advanced mode. The action opens the regular filter controls; Basic conditions remain editable through their chips. Pinning does not simplify or rewrite these expressions.

A custom toolbar region owns placement: built-in pinned controls are omitted, while the regular filter control remains available below the replacement. Pinned controls use the same filterInputs, option labels, and translated texts as the filter editor. A custom input emits undefined to clear that condition.

Set theme.tokens to a partial token object. Unknown token keys fail validation.

Surfaces and color Link to Surfaces and color
Token keys background, foreground, surface, surfaceForeground, popover, popoverForeground, muted, mutedForeground, accent, accentForeground, primary, primaryForeground, destructive, border, input, ring
Geometry and spacing Link to Geometry and spacing
Token keys controlRadius, tableRadius, controlHeight, cellPaddingX, cellPaddingY
Table states Link to Table states
Token keys headerBackground, rowHoverBackground, rowSelectedBackground, skeletonBackground, stateBackground
Type and elevation Link to Type and elevation
Token keys fontFamily, fontSize, shadow

Set theme.classNames using these targets:

Outer controls Link to Outer controls
Theme keys root, toolbar, filters, footer, pagination, selectionSummary, filterGroup
Table structure Link to Table structure
Theme keys tableWrapper, table, tableHeader, tableHeaderRow, tableHeaderCell, tableBody, tableBodyRow, tableBodyCell
State and cell content Link to State and cell content
Theme keys stateRow, stateCell, cell

A string appends classes. An object accepts add: string, remove: string | readonly string[], and replace: string. Replacement supplies the whole class value for that target.

Each group is partial except clauses, which is a comparison-key-to-label map.

Keys label, placeholder, clear
columns Link to columns
Keys label, searchPlaceholder, empty, toggle
sorting Link to sorting
Keys ascending, descending, label, none
filters Link to filters
Keys label, add, addGroup, clear, apply, applyRange, and, or, any, match, conditions, groupOperator, field, clause, remove, removeGroup, empty, rangeFrom, rangeTo, valuePlaceholder, optionsTruncated, editFilters, clearPinned, addTitle, editTitle, save, cancel, close, value, searchFields, noFields, activeCount, more, less, editDescription, removeDescription, invalidated
boolean Link to boolean
Keys true, false; filter choices and fallback cell labels
refresh, loading, empty Link to refresh, loading, empty
Keys label
error Link to error
Keys label, retry
problem Link to problem
Keys See problem wording
selection Link to selection
Keys summary, all, row
result Link to result
Keys summary
pagination Link to pagination
Keys rowsPerPage, pageSummary, first, previous, next, last
copy Link to copy
Keys label, success, error

Server-supplied clause and Boolean cell labels take precedence over texts.clauses and texts.boolean. See Labels from Laravel for translation.

Filter text templates use these placeholders:

Keys in texts.filters Placeholder
activeCount, more {count}: number of active or hidden conditions, respectively
editDescription, removeDescription {filter}: full condition description
rangeFrom, rangeTo, clearPinned {filter}: field label

applyRange is retained for source compatibility but is unused by the built-in editor.

Result regions receive component, controller, state, status, and table (the TanStack table). The separate problemState region has no successful result or TanStack table.

Prop type and additions TableRegionProps
tableBodyWrapper Link to tableBodyWrapper
Prop type and additions TableBodyWrapperRegionProps, plus children
loadingState, errorState, emptyState Link to loadingState, errorState, emptyState
Prop type and additions TableStateRegionProps, plus colSpan
problemState Link to problemState
Prop type and additions TableProblemRegionProps: componentId, problem, controller, status, canReset

A custom toolbar region keeps the built-in filter control below it and the afterToolbar region.

These components render the table’s search, filter, sort, and pagination controls in custom children of <AppTable>, Table, InertiaXRenderer, or TableRenderer (example). TableSort is a sort select in place of the built-in table’s header buttons. Each renders nothing for a problem outcome, or when the table doesn’t offer its feature:

TableSearch Link to TableSearch
Renders The search box
Renders nothing when No column is searchable
TableFilters Link to TableFilters
Renders The filter trigger, pinned filters, and applied conditions, in the table’s filter mode
Renders nothing when The table has no filters
TableSort Link to TableSort
Renders A select of the sortable columns, and a direction button for the chosen one
Renders nothing when Sorting is off, or no column is sortable
TablePagination Link to TablePagination
Renders The page size, page summary, and page buttons
Renders nothing when Pagination is off

TableSort sets a single sort key, even when the table allows several, and a new column starts ascending; it has no option that removes the sort. texts.sorting.label names its select, and texts.sorting.none shows in it when the table has no sort. TableFilters accepts an optional children function, TableFilterLayout, which receives trigger, pinned (null when no filter is pinned), and applied to place them yourself. The pagination region applies only to the built-in table.

cellRenderers Link to cellRenderers
Renderer props TableCellRendererProps: cell, column, and rowId
iconRenderers Link to iconRenderers
Renderer props TableIconRendererProps: cell renderer props plus icon
filterInputs Link to filterInputs
Renderer props TableFilterInputRendererProps: ariaLabel, clause, definition, optional id, value, and onChange

Use register(key, value) for a new type or replace(key, value) for an existing type. They return { operation: 'register', key, value } and { operation: 'replace', key, value }, which you can also write directly. Duplicate registration and missing replacement targets fail. See Registration & diagnostics.

Filter inputs are registered by value type. The editor draws the input for clause.input.valueType: none for a clause without input, two for a range (its from and to values), and one otherwise. Built-in types are text, number, boolean, select, date, datetime, and time; replacing select also replaces the pinned select. A custom input reads clause.input.mode and, for select, clause.input.options.

Custom filter inputs preserve JSON primitive types and emit undefined to clear the draft value. The editor commits with Add/Save (Basic) or Apply (Advanced). Inputs mount while editing, and a select input also mounts for a pinned filter. A native select emits strings: converting numeric or Boolean options to strings changes their identity. Decode them before calling onChange, and emit an array for a multiple input.

customization.formatFilterValue(props: TableFilterValueFormatterProps): string | undefined formats the value portion of an applied Basic filter. Props contain readonly definition, clause, and value. The callback receives the entire value, including both endpoints for a range or every value of a list. It is not called for clauses without input.

Returning undefined delegates from component to provider to the built-in formatter. Only undefined delegates; an empty string hides the value portion of the chip. Built-in formatting uses the labels of clause.input.options, translated Boolean labels and unchanged date/time strings. It quotes text values, joins range endpoints with a dash and separates list values with commas. Object values use JSON text.

Formatting does not change the submitted value. Invalid configuration, invalid return types (including null) and callback failures report INERTIAX_REACT_TABLE_CUSTOMIZATION_INVALID. See Format applied values for an example.

Use these data-inertiax-part values to style the built-in filter controls:

Trigger and summaries Link to Trigger and summaries
Parts filters-trigger, filters-summary, filters-overflow
Applied Basic filters Link to Applied Basic filters
Parts filters, filter-chip, filter-chip-edit, filter-chip-remove, filter-chip-field, filter-chip-clause, filter-chip-value
Pinned controls Link to Pinned controls
Parts pinned-filters, pinned-filter, pinned-filter-controls, pinned-filter-clear, pinned-filter-edit
Field picker Link to Field picker
Parts filter-picker, filter-picker-search, filter-picker-list, filter-picker-item
Editor Link to Editor
Parts filter-editor, filter-editor-header, filter-editor-body, filter-actions, filters-apply, filters-clear, filters-close
Conditions Link to Conditions
Parts filter-group, filter-condition, filter-field, filter-clause, filter-input, filter-range

Editor portals carry data-inertiax-table-portal with the table’s ID and inherit its theme tokens. To target one table’s editor:

[data-inertiax-table-portal='users'][data-inertiax-part='filter-editor'] {
border-radius: 0.75rem;
}

Desktop editors use popovers; screens up to 640px wide use a modal panel, except inside a dialog (see Tables in dialogs). The filter-editor part applies to both. Advanced filters use filters inside the editor instead of an applied-chip strip.

Ranges and valueless conditions use the editor’s Add/Save or Apply action.

A table inside a dialog, such as a modal, drawer or sheet (shadcn’s Dialog or Sheet, a <dialog> element, or any element with role="dialog" or role="alertdialog"), renders its popups inside that dialog, so they work with its focus trap. They stay within the nearest transformed panel around the table, else within the dialog; when the dialog element has no size of its own (as in Headless UI) and no such panel exists, they stay within the screen. The filter editor is a popover there on every screen.

In Radix dialogs (including shadcn’s Radix components), Headless UI and <dialog>, Escape closes the open table popup first, and a second Escape closes the dialog. MUI, React Aria and Base UI dialogs (including shadcn’s Base UI components) close on every Escape. A Radix dialog’s onEscapeKeyDown still runs for the first Escape; check event.defaultPrevented there before acting on it.

The table looks for the dialog around the whole table: in a custom layout, a filter control placed in a different dialog keeps its popups outside that dialog. An app’s own Radix popup inside a custom filter input is a separate copy of Radix, so Escape there also closes the filter editor. The popups stack within the dialog’s content: if an element around the table starts its own stacking context, for example a positioned element with z-index or one with isolation: isolate, positioned content after it in the dialog can cover them. Give that element a z-index above that content, or remove its stacking context.

Inline customization objects are valid. Keep static presets at module scope; for state-dependent presets, useMemo and useCallback can avoid rebuilding customization on unrelated renders. This is an optional optimization, not a correctness requirement, except for pipeline: see Envelope pipelines. Closures must still capture the current values they use.

Event payloads and controller methods are listed in Controller & events.

addStage(name, transform, { priority }) Link to addStage
Operation Adds a stage; the name must be new
replaceStage(name, transform, { priority }) Link to replaceStage
Operation Replaces a stage by name; without priority, keeps the replaced stage’s priority
removeStage(name) Link to removeStage
Operation Removes a stage by name

Each helper returns the matching { operation: 'add' | 'replace' | 'remove', ... } object, which you can also write directly. Stages run in ascending priority, default 10; at equal priority, provider stages run before component stages, in declaration order. A replacement runs where it is declared, so a component’s replaceStage() of a provider stage runs after the provider’s other stages of equal priority.

A stage receives TableEnvelope, the union of successful Envelope and ProblemEnvelope, as a read-only value and returns it or a new envelope built from it. Use isProblemEnvelope() from @inertiax/table-protocol before accessing result fields; a pipeline that only changes columns should return problem envelopes unchanged.

Keep pipeline arrays at module scope or memoize them: a new array recreates the sessions under that provider or component and loses their client state. Type the array with satisfies TableProps['pipeline'] so addStage() infers the envelope type. See Envelope pipelines for an example.

Table APIs and CSS are exported by @inertiax/react-table. useTableProviderRuntime() reads the runtime belonging to the nearest TableProvider; useTableRuntime() reads the current Table renderer’s component scope.

Foundation @inertiax/react supplies a separate InertiaXProvider for app-owned services. It does not create a Table runtime or replace TableProvider. An application binding can read useInertiaXServices() and pass its optional floatingRoot to TableProvider explicitly.

For a caller-owned runtime, use createTableRuntime() from @inertiax/table-core. Pass it to TableProvider runtime={runtime} or createTableSession({ runtime, ... }). The provider creates one automatically when omitted. Generic Core runtimes cannot be supplied to Table; Table owns its catalogs, pipelines, diagnostics, and their TableRuntimeError class.

Replacing a supplied runtime preserves valid table state when the component ID and integration are unchanged. The caller owns both runtimes and must dispose them when no longer used. A different component ID or integration starts separate state.

A session passed to a children render function can be replaced after Activity or Suspense restores the table. Use the session passed to the current render; do not save it for later use. For custom controls, prefer controller hooks.

If you create sessions yourself, createTableSession() from @inertiax/table-core accepts an optional initialState containing a complete TableState:

import { createTableSession } from '@inertiax/table-core';
const session = createTableSession({
componentId: 'users',
integration,
initialState: savedState,
});

The state is validated against the current table definition and copied. If the integration supplies a location, that location takes precedence for search, filters, sorting, and pagination; missing parameters use defaults from the supplied table response.

Supplying initialState starts no request. Call session.controller.refresh() if the supplied rows do not match the restored state. Without initialState, a location that differs from the supplied response triggers a request automatically. Dispose a caller-owned session when finished; a disposed session cannot be reused.

useTableSnapshot() returns an outcome: 'success' | 'problem' union. A successful snapshot has a complete envelope and TableState. A problem snapshot has state: null, a problem envelope, and canReset. useTableState(), useTableEnvelope(), and TanStack projection require a successful outcome. Custom renderers must branch before mounting components that call those hooks.

The built-in renderer uses customization.regions.problemState for invalid external settings and customization.texts.problem for its label, reset, unavailable, page, pageSize, sorting, filters, and search wording. See Recover table links.

View Markdown