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.
Component options
Section titled “Component options”propLink to prop- Named host prop; required by
Tableand your app’sAppTablebinding hostLink to host- Structural query service; required by
Table, supplied byAppTable envelopeLink to envelope- Supplied response; required by the direct
InertiaXRenderer debounceMsLink to debounceMs- Shared search/filter delay, default 300 ms; nonnegative;
0dispatches immediately controllerRefLink to controllerRef- Ref to the table controller; cleared on unmount
customizationLink to customization- Theme, text, regions, renderers, and event handlers
pipelineLink to pipeline- Advanced transformations of validated envelopes
childrenLink 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.
Pinned filters
Section titled “Pinned filters”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.
Theme tokens
Section titled “Theme tokens”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
Class names
Section titled “Class names”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.
Text overrides
Section titled “Text overrides”Each group is partial except clauses, which is a comparison-key-to-label map.
searchLink to search- Keys
label,placeholder,clear columnsLink to columns- Keys
label,searchPlaceholder,empty,toggle sortingLink to sorting- Keys
ascending,descending,label,none filtersLink 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 booleanLink to boolean- Keys
true,false; filter choices and fallback cell labels refresh,loading,emptyLink to refresh, loading, empty- Keys
label errorLink to error- Keys
label,retry problemLink to problem- Keys See problem wording
selectionLink to selection- Keys
summary,all,row resultLink to result- Keys
summary paginationLink to pagination- Keys
rowsPerPage,pageSummary,first,previous,next,last copyLink 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.
Regions
Section titled “Regions”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 tableBodyWrapperLink to tableBodyWrapper- Prop type and additions
TableBodyWrapperRegionProps, pluschildren loadingState,errorState,emptyStateLink to loadingState, errorState, emptyState- Prop type and additions
TableStateRegionProps, pluscolSpan problemStateLink 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.
Layout controls
Section titled “Layout controls”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:
TableFiltersLink 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
TableSortLink 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
TablePaginationLink 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.
Cell, icon, and input renderers
Section titled “Cell, icon, and input renderers”cellRenderersLink to cellRenderers- Renderer props
TableCellRendererProps:cell,column, androwId iconRenderersLink to iconRenderers- Renderer props
TableIconRendererProps: cell renderer props plusicon filterInputsLink to filterInputs- Renderer props
TableFilterInputRendererProps:ariaLabel,clause,definition, optionalid,value, andonChange
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.
Filter value summaries
Section titled “Filter value summaries”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.
Filter appearance
Section titled “Filter appearance”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.
Tables in dialogs
Section titled “Tables in dialogs”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.
React prop identity
Section titled “React prop identity”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.
Envelope pipelines
Section titled “Envelope pipelines”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.
Session lifecycle
Section titled “Session lifecycle”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.
Problem outcomes
Section titled “Problem outcomes”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.