Skip to content

Cell Renderers ​

Every column has a renderer that determines how its value is displayed. Renderers are specified by name in createColumn() and can be customised via rendererOptions.

Built-in renderers ​

RendererInput typeDescription
plainanyRenders the value inside a div as text.
intnumberInteger. Supports locale-aware formatting.
floatnumberFloating-point number, formatted with Intl.NumberFormat. Fraction digits are controlled through locale.localeOptions.
decimalnumberIdentical to float: same formatting, same options.
datestring | DateFormats a date value.
timestring | DateFormats a time value.
datetimestring | DateFormats a date+time value.
checkboxbooleanRenders a checked/unchecked indicator.
mdstringRenders Markdown content.
colorstringRenders a 1.25em square colour swatch, centred vertically in the cell: the value is the swatch's background-color, passed as the --df-cell-color custom property of the cell.
linkstringRenders a hyperlink with the value as its text. A URL whose scheme is not allowed renders as text: see URL schemes.
emailstringRenders a mailto: link. Where mailto is not an allowed scheme, renders the name as text.
filestringRenders a link to the file, opened in a new tab, with the file name as its text. A click on the link does not reach the row; a click beside it does. A URL whose scheme is not allowed renders as text.
ip4stringIPv4 address.
ip6stringIPv6 address.
ipstringAuto-detects IPv4 or IPv6.
null-emptyanyRenders an empty cell. It does not look at the value; it exists to be named as a nullHandler.
null-nullanyRenders the text null in a span with the CSS class df-cell-null, which frames it. It does not look at the value; it exists to be named as a nullHandler.
link-fast, email-fast, file-fast, null-null-fastas without -fastOne element less per cell: see Cell element.
header—Internal renderer for header cells. Not for data columns.

Cell element ​

Every cell fills its grid area: it is stretched to the width of its column and the height of its row. Text and HTML are rendered in a div that is the cell. link, email, file and null-null put their element (the <a>, or the span with df-cell-null) in a div that is the cell, so the element keeps its own size inside it. color draws its swatch as the cell's ::before.

The -fast variants render that element as the cell itself, without the div. The element is then stretched to the grid area:

  • link-fast, email-fast, file-fast: the cell is the <a>, so the whole cell is the link. A click anywhere in a file-fast cell does not reach the row.
  • null-null-fast: the cell is a div with the text null and the CSS class df-cell-null, so the frame is drawn around the whole cell.

The Built-in Renderers example shows both.

Common options (CellOptions) ​

Renderers accept these base options via rendererOptions. null-empty, null-null, null-null-fast and header ignore transform; every other renderer applies it.

typescript
interface CellOptions {
  nullHandler?: string;                                                    // renderer key to use when value is null/undefined
  transform?: (value: any, row: RowValue) => any;                         // transform value before rendering
  preRender?: (value: any, row: RowValue) => RenderableValue | string | null;  // content rendered before (left of) the cell value
  postRender?: (value: any, row: RowValue) => RenderableValue | string | null; // content rendered after (right of) the cell value
}

The transform function receives the raw cell value and the full row object, and returns the value that will actually be rendered.

A value from the record is rendered as text: the built-in renderers escape it where they build HTML (int, float, decimal, ip4, ip6, ip), and set it as a prop where they render an element (link, email, file, color and the -fast variants). A string returned by transform, preRender or postRender is text as well. HTML is returned as a RenderableValue of an HtmlString from @dynamicforms/vuetify-inputs, which is rendered as it is: the application escapes or sanitizes whatever it puts into it from a record.

A column that specifies no rendererOptions at all is given { nullHandler: 'null-null' }, so its null and undefined cells are rendered by null-null. As soon as you supply a rendererOptions object, that default is gone: without a nullHandler key the column's own renderer receives the null. The choice is made on the raw record value, before transform runs.

preRender and postRender allow injecting additional content to the left or right of the main cell value. When either is set, the cell switches to a flex layout with three zones: pre, content, and post. Such cells carry the extra CSS class has-pre-post, which is what the flex layout is attached to. has-pre-post is applied whenever the column has preRender/postRender configured, regardless of what an individual row's callback returns.

Interactive content via preRender/postRender ​

Unlike transform — whose return value is rendered as text by the column's renderer — preRender and postRender can return a RenderableValue wrapping a real Vue component, which is what makes them the only way to put an interactive element (an icon with its own click handler, say) inside a cell. SimpleComponentDef (from @dynamicforms/vuetify-inputs) is what both preRender/postRender and setCellRenderer() build:

typescript
interface SimpleComponentDef {
  componentName: string; // name of a globally registered component, or a native tag like 'div'
  componentProps?: Record<string, unknown> & { innerHTML?: never }; // props, attrs and event handlers
}

HTML content is an HtmlString from @dynamicforms/vuetify-inputs, rendered as the inner HTML of a div and not sanitized.

componentName is resolved as a globally registered component name — a component only imported locally in your <script setup> will not resolve.

Always call stopPropagation()

preRender/postRender content sits inside the same row card the grid's own click handler listens on. Without e.stopPropagation() in the component's own handler, the click also bubbles up and is interpreted as a click on the row — which, while selection mode is active, toggles the row's selection instead of (or in addition to) running your handler.

Returning null leaves that zone genuinely absent from the DOM for that row; returning '' (a plain renderer's default when transform yields an empty string) still renders as an empty component. See the Cookbook for the clickable-icon, conditionally-empty-cell, action-only-column, and selection-checkbox-column recipes built on top of this.

Numeric options (int, float, decimal) ​

typescript
interface LocaleWithOptions {
  locale?: string;                        // BCP 47 locale string (e.g. 'sl-SI')
  localeOptions?: Intl.NumberFormatOptions; // passed directly to Intl.NumberFormat
}

interface IntOptions extends CellOptions {
  locale?: string | LocaleWithOptions;  // formatting locale; defaults to browser locale
  padToLength?: number | 'auto';        // pad the integer part to N digits, or 'auto'
}

A numeric padToLength keeps values vertically aligned: the integer part is padded to that many digits, and the padding zeros are rendered at half opacity, as are the trailing zeros of the fraction.

padToLength: 'auto' picks the width instead of taking it from you. The column keeps track of the longest integer part and the longest fraction it has formatted so far; whenever a value exceeds either, the column's formatter is rebuilt with the new minimum digit counts and the column is redrawn, so all its cells pad to the same width. The padding is rendered at half opacity exactly as with a numeric value. Because the measurement only ever grows, the width tracks the widest value the column has actually rendered.

localeOptions defaults to { useGrouping: false }, so grouping separators are off unless you ask for them, and maximumFractionDigits is set to 20 whenever you do not supply it, so fractions are not rounded by default.

The object form of locale is recognised only when the object contains a locale key; an object carrying only localeOptions is ignored and the defaults apply.

int formats the value like float and then cuts the fractional part off, so it truncates rather than rounds.

A link in a cell takes a URL whose scheme is allowed, or a URL without a scheme, which is relative. A URL with another scheme renders as text, without a link, so that a value such as javascript:... from a record is never a link.

typescript
const defaultUrlSchemes: readonly string[];                     // ['http', 'https', 'mailto', 'tel']
function setAllowedUrlSchemes(schemes: readonly string[]): void;
function getAllowedUrlSchemes(): readonly string[];

interface LinkOptions extends CellOptions {
  urlSchemes?: readonly string[];  // the schemes of this column, in place of getAllowedUrlSchemes()
}

setAllowedUrlSchemes() sets the schemes of every grid in the application, for each column whose rendererOptions name no urlSchemes; the grids render their links again. getAllowedUrlSchemes() returns them: defaultUrlSchemes until they are set. Schemes are compared without regard to case. The options apply to link, email, file and their -fast variants.

typescript
import { createColumn, defaultUrlSchemes, setAllowedUrlSchemes } from '@dynamicforms/vue-grid';

setAllowedUrlSchemes([...defaultUrlSchemes, 'sms']);

createColumn('download', 'Download', 'file', { rendererOptions: { urlSchemes: ['https'] } });

Date/time options (date, time, datetime) ​

typescript
interface DateTimeOptions extends CellOptions {
  format?: string;          // date-fns format string; defaults to 'P' (date), 'p' (time),
                            // or 'P p' (datetime), resolved against date-fns' default
                            // locale (change it globally with date-fns' setDefaultOptions)
  parseISOPrefix?: string;  // string prepended to the raw value before ISO parsing
                            // (e.g. '2000-01-01T' to make a time-only value parseable)
}

A Date instance is formatted as-is and parseISOPrefix is not applied to it. Any other value is parsed with date-fns' parseISO first, after parseISOPrefix has been prepended; a value parseISO cannot read makes formatting throw a RangeError.

Custom renderers ​

setCellRenderer() replaces one of the built-in renderers application-wide. For a renderer scoped to a single column instead, pass the same kind of function directly as that column's renderer — see Custom renderer functions.

setCellRenderer() replaces one of the built-in renderers:

typescript
function setCellRenderer(
  dataType: keyof RendererOptionsMap,
  transform: CellRendererTransformer,
): void

dataType must be one of the renderer names listed above; new names cannot be added. The replacement is global — every grid in the application renders with it from that point on. Capture the current implementation with getCellRenderers() first if you need to keep it.

typescript
import { RenderableValue } from '@dynamicforms/vuetify-inputs';
import { setCellRenderer } from '@dynamicforms/vue-grid';

setCellRenderer('plain', (value, rowValue, options) => new RenderableValue(value ?? '—'));

The transformer receives:

  • value — the (possibly transformed) cell value
  • rowValue — the full row object
  • options — the column's rendererOptions object, with internal per-grid/per-column state stamped onto it under symbol keys

The return value must be a RenderableValue (from @dynamicforms/vuetify-inputs). Its classes are combined with the column's fieldName and cssClass when the cell is built. Text, markdown and HTML are rendered in a div that is the cell. The element of a SimpleComponentDef is the cell itself and is stretched to the grid area; to keep its own size, it is given as the child of a div: { componentName: 'div', children: element }.

getCellRenderers() ​

typescript
function getCellRenderers(): RenderersMap

getCellRenderers() returns a copy of the registry as it stands at the moment of the call. Use it to wrap an existing renderer:

typescript
import { getCellRenderers, setCellRenderer } from '@dynamicforms/vue-grid';

const original = getCellRenderers().int;
setCellRenderer('int', (value, rowValue, options) => original(value, rowValue, options));

RowValue ​

typescript
type RowValue = Record<string | symbol, any>;

Each item in the records prop must satisfy this type. The keyField prop names the property used as a unique row identifier.

Released under the MIT License.