JavaScript Data GridCell renderer
Create a custom cell renderer function, to have full control over how a cell looks.
Overview
A renderer is a function that determines how a cell looks. It's responsible for the complete cell rendering process, including DOM structure creation, content insertion, applying CSS classes, setting accessibility attributes, and managing all visual aspects of the cell.
TIP
If you only need to format the displayed value (e.g., add units, format dates, or apply text transformations), consider using the valueFormatter option instead. It's more performant and simpler for value-only transformations. Use a renderer when you need to modify the DOM structure, add custom HTML elements, or handle complex visual layouts. See the renderer vs valueFormatter section for a detailed comparison.
Built-in renderers
Handsontable provides 10 built-in renderers that you can use by their alias names. Each renderer is designed for a specific use case:
| Alias | What the renderer does |
|---|---|
autocomplete | Renders autocomplete cells with suggestions |
checkbox | Renders checkbox cells for boolean values or values defined by checkedTemplate and uncheckedTemplate options |
date | Renders date values with date formatting |
dropdown | Renders dropdown cells with select options |
html | Renders HTML content in cells (allows raw HTML) |
numeric | Renders numeric values with number formatting |
password | Renders password fields (masks the displayed value) |
text | Renders plain text (default renderer) |
time | Renders time values with time formatting |
Using aliases provides a convenient way to specify which renderer should be used without needing to reference the full renderer function. You can change the renderer function associated with an alias without modifying the code that uses it.
renderer vs valueFormatter
As mentioned in the Overview, Handsontable provides two distinct options for controlling how cell values are displayed: valueFormatter and renderer. This section provides a detailed comparison to help you choose the right tool for your use case.
What is renderer?
The renderer option is a function responsible for the complete cell rendering process. It handles DOM structure creation, content insertion (via innerText or innerHTML), applying CSS classes, setting accessibility attributes, and managing all visual aspects of the cell.
Function signature:
renderer(hotInstance, td, row, col, prop, value, cellProperties)
When to use renderer:
- Modify DOM structure (add icons, custom HTML elements, complex layouts)
- Apply custom styling or CSS classes dynamically
- Handle accessibility attributes
- Create interactive elements within cells
- When you need full control over the cell's HTML structure
Example:
function customRenderer(hotInstance, td, row, col, prop, value, cellProperties) {
// Create custom DOM structure
td.innerHTML = `
<div class="custom-wrapper">
<span class="icon">📊</span>
<span class="value">${value}</span>
</div>
`;
}
What is valueFormatter?
The valueFormatter option (available since v17.0.0) is a function that transforms cell values before they are displayed. It focuses solely on value transformation and is called by the rendering engine right before the renderer function executes.
Function signature:
valueFormatter(value, cellProperties) => formattedValue
When to use valueFormatter:
- Transform displayed values (add prefix, suffix, units)
- Format dates, numbers, or text in a custom way
- Apply simple text transformations
- When you only need to change what is displayed, not how it's rendered
Example:
columns: [
{
data: 'price',
valueFormatter(value) {
return value ? `$${value.toFixed(2)}` : '';
}
},
{
data: 'weight',
valueFormatter(value) {
return value ? `${value} kg` : '';
}
}
]
Key differences
| Aspect | valueFormatter | renderer |
|---|---|---|
| Purpose | Transform the value | Complete cell rendering |
| Scope | Value transformation only | DOM structure, styling, accessibility |
| Performance | Faster (called before renderer) | More overhead (full DOM manipulation) |
| Use case | Simple formatting (units, prefixes) | Complex layouts, custom HTML |
| Returns | Formatted value | Nothing (modifies DOM directly) |
Using renderer and valueFormatter together
Using both renderer and valueFormatter together is recommended when you need both value formatting and custom DOM structure. This approach separates concerns: valueFormatter handles value transformation, while renderer focuses on DOM manipulation. This separation improves maintainability and code clarity. The valueFormatter executes first, transforming the value, and then the renderer receives the formatted value:
columns: [
{
data: 'amount',
// First, format the value
valueFormatter(value) {
return `$${value.toFixed(2)}`;
},
// Then, render it with custom DOM structure
renderer(hotInstance, td, row, col, prop, value, cellProperties) {
TD.innerHTML = `<div class="amount-cell"><span class="currency">${value}</span></div>`;
}
}
]
In this example, valueFormatter adds the currency symbol and formatting, while renderer wraps it in a custom DOM structure with additional styling.
Use a cell renderer
You can use any of the built-in renderers by specifying their alias name in your column configuration. The example below shows how to use the numeric renderer, which formats numeric values according to the cell's formatting options:
const container = document.querySelector("#container");
const hot = new Handsontable(container, {
data: someData,
columns: [
{
renderer: "numeric",
},
],
});
Register custom cell renderer
To register your own alias use registerRenderer() function from the @handsontable/renderers package. It takes two arguments:
rendererName- a string representing a renderer functionrenderer- a renderer function that will be represented byrendererName
If you'd like to register asteriskDecoratorRenderer under alias asterisk you have to call:
import { registerRenderer } from "@handsontable/renderers";
registerRenderer("asterisk", asteriskDecoratorRenderer);
Choose aliases wisely. If you register your renderer under name that is already registered, the target function will be overwritten:
import { registerRenderer } from "@handsontable/renderers";
registerRenderer("text", asteriskDecoratorRenderer);
Now "text" alias points to asteriskDecoratorRenderer function, not the built-in textRenderer.
So, unless you intentionally want to overwrite an existing alias, try to choose a unique name. A good practice is prefixing your aliases with some custom name (for example your GitHub username) to minimize the possibility of name collisions. This is especially important if you want to publish your renderer, because you never know aliases has been registered by the user who uses your renderer.
import { registerRenderer } from "@handsontable/renderers";
registerRenderer("asterisk", asteriskDecoratorRenderer);
Someone might already registered such alias
import { registerRenderer } from "@handsontable/renderers";
registerRenderer("my.asterisk", asteriskDecoratorRenderer);
That's better.
Use an alias
The final touch is to use registered aliases. That way users can easily refer to an alias without the need to know the name of the function.
To sum up, a well prepared renderer function should look like this:
import { registerRenderer } from "@handsontable/renderers";
function customRenderer(
hotInstance,
td,
row,
column,
prop,
value,
cellProperties
) {
// ...your custom logic of the renderer
}
// Register an alias
registerRenderer("my.custom", customRenderer);
From now on, you can use customRenderer like so:
const container = document.querySelector("#container");
const hot = new Handsontable(container, {
data: someData,
columns: [
{
renderer: "my.custom",
},
],
});
Render custom HTML in cells
This example shows how to use custom cell renderers to display HTML content in a cell. This is a very powerful feature. Just remember to escape any HTML code that could be used for XSS attacks. In the below configuration:
Deprecated
DOMPurify will be removed in the next version. After that, any string containing HTML will be stripped before rendering. To keep sanitized HTML (e.g. with DOMPurify), set the sanitizer option to your own sanitizer function.
- Title column uses built-in HTML renderer that allows any HTML. This is unsafe if your code comes from untrusted source. Take notice that a Handsontable user can use it to enter
<script>or other potentially malicious tags using the cell editor! - Description column also uses HTML renderer (same as above)
- Comments column uses a custom renderer (
safeHtmlRenderer). This should be safe for user input, because only certain tags are allowed - Cover column accepts image URL as a string and converts it to a
<img>in the renderer
Render custom HTML in header
You can also put HTML into row and column headers. If you need to attach events to DOM elements like the checkbox below, just remember to identify the element by class name, not by id. This is because row and column headers are duplicated in the DOM tree and id attribute must be unique.
Deprecated
DOMPurify will be removed in the next version. After that, any string containing HTML will be stripped before rendering. To keep sanitized HTML (e.g. with DOMPurify), set the sanitizer option to your own sanitizer function.
Add event listeners in cell renderer function
If you are writing an advanced cell renderer, and you want to add some custom behavior after a certain user action (i.e. after user hover a mouse pointer over a cell) you might be tempted to add an event listener directly to table cell node passed as an argument to the renderer function. Unfortunately, this will almost always cause you trouble and you will end up with either performance issues or having the listeners attached to the wrong cell.
This is because Handsontable:
- Calls
rendererfunctions multiple times per cell - this can lead to having multiple copies of the same event listener attached to a cell - Reuses table cell nodes during table scrolling and adding/removing new rows/columns - this can lead to having event listeners attached to the wrong cell
Before deciding to attach an event listener in cell renderer make sure, that there is no Handsontable event that suits your needs. Using Handsontable events system is the safest way to respond to user actions.
If you did't find a suitable Handsontable event put the cell content into a wrapping <div>, attach the event listener to the wrapper and then put it into the table cell.
Performance considerations
Cell renderers are called separately for every displayed cell, during every table render. Table can be rendered multiple times during its lifetime (after table scroll, after table sorting, after cell edit etc.), therefore you should keep your renderer functions as simple and fast as possible or you might experience a performance drop, especially when dealing with large sets of data.
If you only need to format the displayed value (e.g., add units, format dates, or apply text transformations), consider using the valueFormatter option instead of a custom renderer. The valueFormatter is called before the renderer and focuses solely on value transformation, making it more performant for simple formatting tasks. Use a renderer when you need to modify the DOM structure, add custom HTML elements, or handle complex visual layouts.
Related articles
Related guides
Related API reference
- APIs:
- Configuration options:
- Core methods:
- Hooks: