Cell renderers
Why this exists
Section titled “Why this exists”The Aurora data-table is built on TanStack Table. For each column, the codegen decides what to render in each cell. The default is plain string coercion: whatever value sits in the row at that key gets stringified and printed. That works for a varchar or a text property — it falls flat for everything else. A boolean shows as the literal true / false. A date shows as an ISO timestamp. An enum shows as its raw key. None of those are usable on a list page.
The fix is per-type cell renderers. Each one takes the row’s value, knows what shape it has, and emits the visual treatment that fits — an icon for booleans, a localized date for timestamps, a colored badge for enum values. The codegen dispatches by property type so the right renderer is wired automatically; the developer overrides per column when the default does not fit.
How it works
Section titled “How it works”Two kinds of components under the data-table
Section titled “Two kinds of components under the data-table”The frontend lib at @aurora/components/data-table/ distinguishes two roles:
cells/— components inyected viaflexRenderComponentwhose only job is to format a value for display. They consume the row’s value viainjectFlexRenderContext<CellContext<T, V>>()(orHeaderContext<T, V>for header cells) and have no side effects on the table state.components/— components that compose the toolbar or mutate the table’s state. Selection toggles, sort buttons, pagination controls, column visibility, filter UI. They depend on the TanStack mutation API (Table<T>,setColumnVisibility,toggleAllRowsSelected, …).
Anything that calls a Table<T> setter belongs in components/, even if it happens to be inyected via flexRenderComponent. Anything that just reads a value and renders it belongs in cells/.
Dispatch by property type
Section titled “Dispatch by property type”The codegen of <mod>.columns.ts walks each property of the aggregate and picks a cell based on the property type. Today the dispatch covers:
| Property shape | Cell |
|---|---|
boolean | flexRenderComponent(BooleanCell, { inputs: {} }) |
id + relationship: many-to-one | accessorKey: '<rel>.name' with auto-include (see Auto-include FK columns) |
| anything else | (info) => \${info.getValue |
The cells/ family is designed to grow: future renderers (DateCell, BadgeCell, CurrencyCell, EnumCell, …) will take over from the string fallback for their respective types as each lands.
The BooleanCell reference implementation
Section titled “The BooleanCell reference implementation”BooleanCell is the canonical example of the contract:
- Lives at
@aurora/components/data-table/cells/boolean-cell.component.ts. Selectorau-boolean-cell.ChangeDetectionStrategy.OnPush. Generic overT. - Defaults:
lucideCheck+text-emerald-600fortrue,lucideMinus+text-muted-foreground/60forfalse. - Override inputs:
trueIcon,falseIcon,trueClass,falseClass. All have defaults. - Accessibility: the
<ng-icon>carriesaria-label="true"oraria-label="false".
Customizing per column
Section titled “Customizing per column”To change the rendering of one column, edit the cell: factory in the generated <mod>.columns.ts and pass overrides through inputs::
{ accessorKey: 'isLocked', // ... cell: () => flexRenderComponent(BooleanCell, { inputs: { trueIcon: 'lucideLock', trueClass: 'text-amber-600' }, }),}That edit will produce a .origin file on the next regeneration — accept the merge to keep your customization. There is no YAML field for overrides today; the override path is TypeScript on the generated columns file.
Authoring a new cell renderer
Section titled “Authoring a new cell renderer”When you add DateCell, BadgeCell, or anything similar, two rules:
-
Place it under
cells/and re-export from the barrel chain (cells/index.ts→data-table/index.ts→@aurora). Anything that mutates table state stays incomponents/. -
Read the value with a getter, not
computed(). TheinjectFlexRenderContext().getValue()API is a method on a Proxy, not a signal. Acomputed()would memoize against an identity that never changes, so the cell would never re-render when the row’s value changes.get value(): boolean {return this.context.getValue() ?? false;}This is non-obvious; every new cell renderer must use the getter pattern.
When it applies
Section titled “When it applies”- A list shows literal
true/falsefor a boolean column — that is the legacy string fallback. Regenerate the module and the column switches toBooleanCell. - You want a non-default icon or color on one boolean column (a “locked” account, a “deprecated” feature flag) — edit the generated
cell:factory and accept the.originreview on regeneration. - A future SPEC ships a new cell renderer (
DateCell,EnumCell, …). Regenerate and the matching property type picks it up automatically. - You are contributing a new cell renderer to the framework. It belongs in
cells/; if it has anyTable<T>mutation, it belongs incomponents/instead.
Trade-offs and limits
Section titled “Trade-offs and limits”- No YAML hook for overrides. There is no
widget.cellIconorwidget.cellColorfield. Overrides are applied in TypeScript on the generated<mod>.columns.tsand reviewed via.originon regen. A YAML hook is a future SPEC. - One renderer per type. Booleans always render as
BooleanCellunless you override per column. There is no per-property “render-as” YAML hint — the dispatch is by property type alone. - Booleans drop
searchable. Text search over the literaltrue/falseis not useful, so the codegen omits the flag on boolean columns. flexRenderComponentrequires explicitinputs: {}. Even when you do not override any input, the second argument has to be present ({ inputs: {} }). TanStack’s typing helper treatsinput<T>(default)declarations as required under TypeScript strict mode, so leaving it off is a compile error.- The split is enforced socially, not technically. Nothing prevents a contributor from putting a state-mutating component under
cells/. The convention is documented; reviewers police it.
Related
Section titled “Related”- Icon cell for boolean columns — the change that introduced
BooleanCelland thecells/convention. - Auto-include FK columns in lists — sibling pipeline that emits relational columns (different mechanism, related surface).
- Configure a frontend module — the broader workflow this concept fits into.