Build-time rendering

Grigson charts can be pre-rendered at build time using Declarative Shadow DOM (DSD). The chart is fully visible in the browser before any JavaScript runs — useful for static sites, SSR frameworks, and environments where JS may be disabled or slow to load.

Live demo

The chart below was rendered at Eleventy build time. Try disabling JavaScript in DevTools and reloading — it will still render correctly.

How it works

An Eleventy transform post-processes every .html output file. It finds each <grigson-chart> element and calls parseSong and HtmlRenderer.render() from the grigson package at build time (Node.js). The resulting HTML string is placed inside a <template shadowrootmode="open"> injected as the first child of the element:

<grigson-chart normalise>
  <template shadowrootmode="open">
    <style>:host{display:block;container-type:inline-size}</style>
    <style>/* component CSS */</style>
    <!-- pre-rendered chart HTML -->
  </template>
  <template><!-- original .chart source --></template>
</grigson-chart>

When the browser parses this HTML it immediately attaches the shadow root — no script required. When JavaScript loads, <grigson-chart> upgrades and detects the existing shadow root, adopting it rather than re-rendering. The element only re-renders if an attribute changes or the source template is mutated.

The :host{display:block;container-type:inline-size} rule is included in the DSD template even though <grigson-chart> also sets it via JavaScript. Without it, @container queries inside the shadow root would not fire until after JS upgrades the element — breaking responsive layouts when JS is disabled or slow to load.

Transform registration

Install eleventy-plugin-grigson and add it to your .eleventy.js. The transform runs automatically on every page — no per-element markup changes are needed.

import grigsonPlugin from 'eleventy-plugin-grigson';

eleventyConfig.addPlugin(grigsonPlugin);

Font note

getRendererFontFaceCSS() returns @font-face rules with the notation fonts embedded as data URIs. These are emitted into the main document (not the shadow root) so that browsers reliably load the unicode-range sub-faces — browsers have known limitations with multiple @font-face rules sharing a family name inside shadow roots. Because the declarations use data URIs, no network requests are made. The component styles go inside the shadow root where they are properly scoped.