Lattice Grid Buy a licence

demo D138

HTML in cells, safely

Templates escape by default; the opt-out and what it opens up

allowUnsafeTemplates: true

Building…
Loading a live grid…

The configuration

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.13.0/lattice-grid.min.css">
<script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.13.0/lattice-grid.min.js"></script>

<div id="grid"></div>

<script>
  const grid = LatticeGrid.createGrid(document.getElementById('grid'), {
    rowKey: 'id',
    autoHeight: true,
    // The explicit opt-out. An unsafe segment is assigned to innerHTML with no
    // filtering of the value, so this means "the data is trusted": only ever
    // true when the data cannot be authored by anyone you would not trust with
    // your users' sessions.
    allowUnsafeTemplates: true,
    columns: [
      { field: 'service', title: 'Service', layout: { width: 150, pin: 'start' } },
      {
        id: 'raw',
        field: 'label',
        title: 'The stored value',
        layout: { width: 260 },
        // No template at all: the plain text path, which never parses.
        cell: { wrap: true },
      },
      {
        id: 'escaped',
        field: 'label',
        title: '{{value}}',
        layout: { width: 260 },
        // Double braces. Escaped even with the flag set: the opt-out is per
        // interpolation, not per grid. Note {{value}}, not {{label}}: a field
        // name on its own is not a binding and renders an empty cell.
        cell: { template: '{{value}}', wrap: true },
      },
      {
        id: 'unescaped',
        field: 'label',
        title: '{{{value}}}',
        layout: { width: 260 },
        // Triple braces. Elements, because the flag above is set.
        cell: { template: '{{{value}}}', wrap: true },
      },
      {
        id: 'fromString',
        field: 'label',
        title: 'cell.render → string',
        layout: { width: 260 },
        // A render function returning a string goes through the same gate as
        // {{{ }}}: without the flag it is written as text however much it looks
        // like markup, and the console says so once per column.
        cell: { render: (p) => String(p.value ?? ''), wrap: true },
      },
      {
        id: 'fromElement',
        title: 'cell.render → element',
        layout: { flex: 1, min: 220 },
        // The route that never needs the opt-out. Built with createElement and
        // textContent, so a value containing markup is text by construction
        // rather than by a flag someone can turn off two years from now.
        cell: {
          render: (p) => {
            const span = document.createElement('span');
            const tier = document.createElement('b');
            tier.textContent = String(p.data?.tier ?? '');
            span.append(tier, document.createTextNode(` · ${String(p.data?.label ?? '')}`));
            return span;
          },
          wrap: true,
        },
      },
    ],
    rows,  // objects whose label field contains inert markup
  });
</script>

Rendering trusted markup with allowUnsafeTemplates

By default, Lattice Grid escapes every value it puts into a cell template, so a string containing <span> or & lands on screen as literal text rather than markup. That default is what keeps a JavaScript data grid safe against XSS when a template pulls in user-submitted or third-party data: a name field, a comment, an imported spreadsheet cell. Setting allowUnsafeTemplates: true on the grid config lifts that escaping for every template column at once, so a developer reaches for it when the data is already trusted, or already sanitised upstream, and the cell genuinely needs to render markup rather than describe it: inline badges built from a template string, a highlighted substring, a small icon plus label composed in one interpolation rather than a full custom renderer. The option is grid-wide rather than per-column, so mixing untrusted and markup-bearing columns in the same grid means sanitising the untrusted values yourself before they reach the template. Turning the option on does not change how the grid virtualises rows: templates still compile once at initialisation and run at the same per-cell cost during scroll, whether their output is escaped or not. Screen readers read whatever text content the rendered markup exposes, so a badge or icon inserted this way needs its own accessible label if the visual marker is not decorative.

How do I render HTML inside a Lattice Grid cell?

Set allowUnsafeTemplates: true in the grid configuration, then include markup directly in a column’s cell.template string. The grid stops escaping template output for every column once this is set, so only enable it when the underlying data is trusted or has already been sanitised, since it removes the default protection against injected markup.