Column Definitions
Columns are defined using createColumn() and passed to <DfGrid> via the columns prop. The grid supports both flat (single-layout) and responsive (multi-layout) column definitions.
createColumn()
function createColumn<R extends keyof RendererOptionsMap>(
fieldName: string,
label: string,
renderer?: R,
otherOptions?: Omit<ColumnDefinition<R>, 'fieldName' | 'label' | 'renderer'>,
): ColumnDefinition<R>| Parameter | Type | Description |
|---|---|---|
fieldName | string | Property name on the row data object, read as a direct property access — dotted paths are not resolved. Also used as a CSS class on cells. |
label | string | Column header label. |
renderer | keyof RendererOptionsMap | Cell renderer to use. Defaults to 'plain'. See Cell Renderers. |
otherOptions | object | Additional column options (see ColumnDefinition below). |
ColumnDefinition
interface ColumnDefinition<R extends keyof RendererOptionsMap = 'plain'> {
fieldName: string;
label: string;
renderer?: R;
rendererOptions?: RendererOptionsMap[R]; // renderer-specific options
sortable?: Sortable; // see Sorting
filterable?: Filterable; // see Filtering
cssClass?: string; // additional CSS class(es) on cells
}createColumn() sets sortable: true on the column it builds; a sortable key in otherOptions overrides it. A ColumnDefinition written as a plain object literal has no such default, and a column without sortable cannot be sorted.
cssClass is set on the column's header cell as well as its body cells. A header cell lays out its label and sorting indicator as a flex row, so text-align does not move them; the classes text-right, text-end and text-center align them to the end or the centre of the header cell.
Example
import { createColumn } from '@dynamicforms/vue-grid';
const columns = [
createColumn('id', 'ID', 'int', { cssClass: 'text-right' }),
createColumn('title', 'Title', 'plain', { sortable: true }),
createColumn('rating', 'Rating', 'float', {
sortable: { direction: 'desc', nulls: 'last' },
filterable: { fieldType: 'number' },
rendererOptions: { locale: { locale: 'en-US', localeOptions: { minimumFractionDigits: 1, maximumFractionDigits: 1 } } },
}),
];Custom renderer functions
renderer accepts a function instead of a registry name, for a column whose content isn't a formatted field value at all:
type CellRendererTransformer = (
value: any,
rowValue: RowValue,
options: CellOptionsInternal,
) => RenderableValue;The function is called directly instead of looking up a renderer in the registry, and takes full ownership of the cell's main content — no transform or type-specific formatting runs, since there is no registry renderer in the loop at all. It still composes with preRender/postRender: whatever the function returns is wrapped exactly the way a built-in renderer wraps its own output. An element it returns is the cell itself, stretched to its grid area; see Custom renderers.
This is the same CellRendererTransformer type setCellRenderer() uses to replace a named renderer application-wide (see Cell Renderers); passing one directly as renderer instead scopes it to a single column. transform produces the value the column's renderer formats, never a component with its own event handlers, so it cannot express a cell whose whole content is interactive — this is why the option exists. See A column that isn't tied to a single field in the Cookbook for a worked example.
Responsive layouts
To define multiple layouts that activate at different container widths, pass an array of ResponsiveColumnDefinition objects:
interface ResponsiveColumnDefinition {
name?: string; // optional name; defaults to cssClass value
cssClass: string; // CSS class applied to the shared body grid in this layout
columns: ColumnDefinitionsList;
rows?: number; // grid rows this layout's cards occupy per record; default 1
}
type ResponsiveColumnDefinitions = ColumnDefinitionsList | ResponsiveColumnDefinition[];rows matters only for a layout that stacks more than one row of fields per record (a card layout, not a single-line one) — see Card layout CSS for how it plugs into --row-base in your own CSS.
The grid decides whether the array is responsive by looking at its first element: an element carrying name or cssClass together with columns marks the whole array as a list of layouts. Every entry must end up with a non-empty name — name, or cssClass when name is omitted — otherwise the grid throws column definition <idx> must have a name or cssClasses assigned and non-empty.
The grid renders a hidden shadow grid for each layout from the first secondaryShadowCount records in display order and records the width that layout needs. A layout is measured again when more records are available to sample than its width was recorded from, so a grid that starts empty and receives its first page later is measured from that page rather than from its header alone. Records that change without growing the sample keep the recorded width; reMeasureLayouts() measures every layout again. When the container is resized or a recorded width changes, the grid emits update:activeColumns with the name of the widest layout whose recorded width still fits the container. If no layout fits, the active layout stays as it is.
The value of activeColumns is matched against the layout name — name when given, otherwise cssClass. When activeColumns is unset, or names no existing layout, the first layout in the array is used. A flat column list is treated as a single layout named default with an empty cssClass.
import { createColumn, filterColumns } from '@dynamicforms/vue-grid';
import type { ResponsiveColumnDefinitions } from '@dynamicforms/vue-grid';
const allColumns = [
createColumn('id', 'ID', 'int'),
createColumn('name', 'Name', 'plain'),
createColumn('country', 'Country', 'plain'),
createColumn('email', 'Email', 'email'),
];
const columnsResponsive: ResponsiveColumnDefinitions = [
{ cssClass: 'wide', columns: allColumns },
{ cssClass: 'medium', columns: filterColumns(allColumns, [0, 1, 3]) },
{ cssClass: 'narrow', columns: filterColumns(allColumns, [1]) },
];<df-grid
v-model:active-columns="activeLayout"
:columns="columnsResponsive"
:records="records"
key-field="id"
/>Only the active column list changes between layouts — the grid placement of each cell within the card is your own CSS, keyed off the layout's cssClass. See the Cookbook for a worked example, including collapsing a wide layout into a stacked, single-column one.
filterColumns()
A helper to select a subset of columns by index or field name:
function filterColumns(
columns: ColumnDefinitionsList,
selectors: (number | string | { [fieldName: string]: number })[],
): ColumnDefinitionsListSelectors can be:
number— picks column at that indexstring— picks the first column with thatfieldName{ fieldName: occurrence }— picks the column with thatfieldNameat indexoccurrence(0-based) among all columns sharing the name; only the first entry of the object is read
The returned list follows the order of the selectors, not the order of the columns. A column selected twice appears twice, and a selector that matches nothing is dropped.
useColumns()
function useColumns(props: GridProps, gridId: symbol)The composable <DfGrid> uses to resolve the columns prop down to a single active layout. gridId is a symbol identifying the grid instance; it is what the numeric renderers register their per-column formatting state under, and what releases it when the grid unmounts. The returned members are all computed refs:
| Member | Description |
|---|---|
active | Name of the active layout: the activeColumns prop, or the first layout's name when the prop is unset. |
builtColumns | All layouts, each carrying name, cssClass and columns. |
activeColumnsDefinition | The entry of builtColumns that is currently active. |
name | name of the active layout. |
cssClass | cssClass of the active layout; '' for a flat column list. |
columns | The active layout's column list. |
