HTML Renderer
The HTML renderer produces an HTML string using CSS Grid for layout. It is the default renderer used by the <grigson-chart> custom element. Beats are proportionally spaced, barlines align vertically across all rows, and musical Unicode symbols are used throughout.
import { parseSong } from 'grigson';
import { HtmlRenderer } from 'grigson/renderers/html';
const song = parseSong(`
---
title: "Autumn Leaves"
key: G minor
---
[A]
| (4/4) Cm7 | F7 | BbM7 | EbM7 |
| Am7b5 | D7 | Gm | Gm |
`);
const html = new HtmlRenderer().render(song);
document.getElementById('chart').setHTMLUnsafe(html);
The config object is a plain JavaScript object. All fields are optional.
Grid layout
Beat columns and gap columns
The song-grid element uses a CSS Grid whose columns follow this template:
auto [beat] [gap] [beat] [gap] [beat] [gap] …
Specifically, the grid-template-columns is:
auto repeat(var(--beat-cols), minmax(var(--min-beat-width), 1fr) auto)
This creates two interleaved kinds of column:
- Beat columns (even-indexed: 2, 4, 6, …) — hold chord cells. Sized
minmax(--min-beat-width, 1fr), so they share available space equally while respecting a per-song minimum width computed from the widest chord. - Gap columns (odd-indexed: 1, 3, 5, …) — hold barlines and time signatures. Sized
auto, so they collapse to zero when empty and expand to fit their content when filled.
Each row uses grid-template-columns: subgrid so all rows share the same column grid. Barlines therefore align vertically across every section, regardless of how the bars are distributed between rows.
Column addressing
Given a beat offset k (0-indexed, measured in beat units), the CSS grid positions are:
| Item | CSS column |
|---|---|
Cell starting at beat k |
2k + 2 |
Barline after beat k |
2k + 1 |
The cell's grid-column span is 2b − 1, where b is the number of beat units the cell occupies. This makes the cell cover its beat columns plus the inner gap columns between them, stopping just before the trailing gap that holds the close barline.
Barlines and time signatures
Barlines are placed in gap columns as natural-width flex containers. A simple | barline is roughly 1.5 px wide; complex glyphs (:||:, etc.) expand the gap column to their natural width.
A time signature annotation, when present, is rendered as a flex sibling of the chord inside the first beat cell of the bar — not inside the barline gap column. This keeps the time signature immediately adjacent to the first chord with no dead space, and prevents it from overflowing the auto gap column when the bar is narrower than expected.
Denominator-aware beat units
The renderer normalises bar widths across mixed-denominator songs. It scans every time signature in the song and picks:
beatUnit = max(denominator) across all time signatures
Each bar then gets an effective beat count of numerator × (beatUnit ÷ denominator). This makes bar widths proportional to their actual duration:
| Song contains | beatUnit |
4/4 effective beats | 6/8 effective beats | 3/8 effective beats |
|---|---|---|---|---|
| only 4/4 | 4 | 4 | — | — |
| only 6/8 | 8 | — | 6 | — |
| 4/4 + 6/8 | 8 | 8 | 6 | — |
| 3/4 + 3/8 | 8 | — | 6 (3/4) | 3 |
In a purely 4/4 chart nothing changes. In a chart mixing 4/4 and 6/8, the 4/4 bars become eight eighth-note columns wide and the 6/8 bars become six eighth-note columns wide — so the grid accurately represents the relative durations.
Minimum beat width
--min-beat-width is computed from the widest chord in the song. The renderer estimates each chord's display width in em units (EM_PER_CHAR = 0.55), divides by the number of beat units that chord spans, and takes the maximum across all chords. The result is clamped to a minimum of 1em. Setting --min-beat-width on the part="song" element overrides this.
HTML structure
<div part="song" style="--beat-cols: 8; --min-beat-width: 1.80em">
<header part="song-header">
<h1 part="song-title">Autumn Leaves</h1>
<p part="song-key">G major</p>
</header>
<div part="song-grid">
<section part="section" style="display: contents">
<h2 part="section-label">A</h2>
<div part="row">
<!-- gap col 1: open barline -->
<span part="barline barline-single" style="grid-column: 1"></span>
<!-- beat col 2–8: Cm7 spanning 4 effective beats (col 2, span 7) -->
<!-- time-sig is a flex sibling of the chord in the first cell -->
<span part="cell" style="grid-column: 2 / span 7">
<span part="time-sig">
<span part="time-sig-num">𝟒</span>
<span part="time-sig-den">𝟒</span>
</span>
<span part="chord">
<span part="chord-root">C</span>
<span part="chord-quality"><small>m</small><sup>7</sup></span>
</span>
</span>
<!-- gap col 9: barline between bars -->
<span part="barline barline-single" style="grid-column: 9"></span>
<!-- beat col 10–16: F7 -->
<span part="cell" style="grid-column: 10 / span 7">…</span>
<!-- gap col 17: final barline -->
<span part="barline barline-final" style="grid-column: 17"></span>
</div>
</section>
</div>
</div>
A chart mixing 4/4 and 6/8 (beatUnit = 8) would show a 4/4 bar spanning 15 columns (span = 2×8−1) and a 6/8 bar spanning 11 columns (span = 2×6−1), with the beat columns the same width in both rows.
Slash chord
<span part="chord chord-slash">
<span part="chord-top">
<span part="chord-root">A<span part="chord-accidental" data-glyph="unicode">♭</span></span>
<span part="chord-quality">m</span>
</span>
<span part="chord-fraction-line"></span>
<span part="chord-bass">E<span part="chord-accidental" data-glyph="unicode">♭</span></span>
</span>
Dot cell (beat continuation)
<span part="dot" style="grid-column: 4 / span 1">/</span>
End-repeat with count
<span part="barline barline-endRepeat" style="grid-column: 9">
<span part="barline-glyph">
<span part="barline-glyph-inner">𝄇</span>
</span>
<span part="barline-repeat-count">×3</span>
</span>
Part names reference
| Part | Element | Description |
|---|---|---|
song |
<div> |
Outermost container; carries --beat-cols and --min-beat-width |
song-header |
<header> |
Title and key block |
song-title |
<h1> |
Song title |
song-key |
<p> |
Key in normalised form (e.g. "F major") |
song-grid |
<div> |
CSS Grid container for all rows |
section |
<section> |
One section; display: contents so children are direct grid items |
section-label |
<h2> |
Section heading (omitted when no label) |
row |
<div> |
One row; uses subgrid |
barline |
<span> |
Any barline; always combined with a kind part (see below) |
barline-single |
— | Plain barline | |
barline-double |
— | Double barline || |
barline-final |
— | Final barline ||. |
barline-startRepeat |
— | Start-repeat ||: |
barline-endRepeat |
— | End-repeat :|| |
barline-endRepeatStartRepeat |
— | Turn-around :||: |
barline-glyph |
<span> |
Clipping container for the barline SMuFL glyph; stretches to row height |
barline-glyph-inner |
<span> |
The SMuFL glyph character; shifted via --grigson-barline-glyph-offset |
barline-repeat-count |
<span> |
Repeat count label (e.g. "×3") inside an end-repeat |
cell |
<span> |
One chord cell; carries grid-column positioning |
dot |
<span> |
Beat-continuation mark rendered as / |
simile |
<span> |
Single-bar repeat mark; spans the full bar width |
time-sig |
<span> |
Stacked time signature fraction |
time-sig-num |
<span> |
Numerator (Math Bold digits U+1D7CE–U+1D7D7) |
time-sig-den |
<span> |
Denominator (Math Bold digits U+1D7CE–U+1D7D7) |
chord |
<span> |
Chord symbol; gains chord-slash when a bass note is present |
chord-root |
<span> |
Note name |
chord-accidental |
<span> |
Accidental in a root or bass note; data-glyph="unicode" or "ascii" |
chord-quality |
<span> |
Quality suffix |
quality-accidental |
<span> |
Accidental within a quality string |
chord-top |
<span> |
Upper half of a slash chord |
chord-fraction-line |
<span> |
Separator between chord and bass; carries data-slash-style |
chord-bass |
<span> |
Bass note of a slash chord |
For CSS theming via custom properties, see CSS custom properties.
Configuration
const config = {
notation: { … },
simile: { … },
accidentals: 'unicode', // 'unicode' | 'ascii'
};
notation
Controls chord symbol rendering. Pass an inline partial preset (merged on top of the defaults) or a named preset registered via definePreset().
notation: {
preset: { minor: '-', halfDiminished: 'm7b5' },
// or a named preset:
preset: 'realbook',
}
See Notation presets for the full reference and built-in presets.
simile
Controls whether consecutive identical bars are collapsed to a repeat mark.
simile: {
// 'longhand' — always write chords in full (default)
// 'shorthand' — use a simile mark for repeated bars (after the first bar of each row)
output: 'longhand',
}
The simile mark is an inline SVG. Enable it via config or the simile-output="shorthand" attribute on <grigson-chart>.
accidentals
Controls whether accidentals are rendered as Unicode glyphs or ASCII characters.
| Value | Accidentals | Note |
|---|---|---|
'unicode' (default) |
♭ ♯ | CSS kerning applied automatically |
'ascii' |
b # | Useful with display fonts lacking good ♭/♯ glyphs |
const html = new HtmlRenderer({ accidentals: 'ascii' }).render(song);
Or via the custom element attribute:
<grigson-chart>
<grigson-html-renderer accidentals="ascii"></grigson-html-renderer>
| Bb7 | Ebmaj7 |
</grigson-chart>
slashStyle / slash-style
Controls how slash chords (e.g. C/E) are rendered.
| Value | Appearance |
|---|---|
'diagonal' (default) |
Chord and bass inline, separated by a diagonal line (Real Book style) |
'horizontal' |
Chord stacked above a horizontal rule, bass below (fraction style) |
'ascii' |
Chord and bass inline, separated by a plain / character |
const html = new HtmlRenderer({ slashStyle: 'horizontal' }).render(song);
<grigson-chart>
<grigson-html-renderer slash-style="horizontal"></grigson-html-renderer>
| Cm7/Bb | G/B |
</grigson-chart>
The diagonal style can be tuned with CSS custom properties:
| Property | Default | Effect |
|---|---|---|
--grigson-slash-angle |
35deg |
Rotation of the diagonal line (positive = / lean) |
--grigson-slash-chord-offset |
-0.25em |
Vertical offset of the chord part (negative = up) |
--grigson-slash-bass-offset |
0.25em |
Vertical offset of the bass note (positive = down) |
Typeface
Set the typeface attribute on <grigson-html-renderer> to choose the embedded font.
| Value | Font | Style |
|---|---|---|
"sans" (default) |
Noto Sans | Clean sans-serif |
"serif" |
Noto Serif | Traditional serif |
"cursive" |
PetalumaScript | Handwritten Real Book style |
<grigson-chart>
<grigson-html-renderer typeface="cursive"></grigson-html-renderer>
| C△ | Am7 | Dm7 | G7 |
</grigson-chart>
Setting --grigson-font-family overrides the font entirely and ignores typeface. See Typefaces for more.
auto-size
A boolean attribute. When present on <grigson-html-renderer>, the element automatically finds the largest font size that fits all chord cells without horizontal clipping.
<grigson-chart>
<grigson-html-renderer auto-size></grigson-html-renderer>
<template>
---
title: "What's New?"
key: C major
---
| (4/4) C | AMI7(b5) | DMI7(b5) G7 | CMI |
</template>
</grigson-chart>
How it works. The algorithm sets the font to the ceiling (1.5 rem), reads scrollWidth > clientWidth on each chord cell, then binary-searches down to the largest no-overflow size. Precision is 0.02 rem (~6 iterations). The floor is 0.6 rem — the element clips at that size rather than shrinking further.
When it re-runs. After every re-render (source edit, transposition, attribute change) and whenever the container width changes (via ResizeObserver). This means it naturally adapts when the user resizes the window or when a chord becomes wider after transposition.
Print. JavaScript measurements don't reflect print layout. auto-size compensates by injecting a @media print rule that scales the computed font size proportionally to the estimated A4 print width (~680 CSS px for A4 with 1.5 cm margins). To override for a different paper size, add your own print rule after the element:
@media print {
grigson-chart { --grigson-font-size: 0.75rem; }
}
When not to use it. Leave auto-size off when you need all charts on a page to share a consistent font size — for example a printed book of charts where visual uniformity matters more than fitting individual charts to their container. For those cases, set --grigson-font-size directly instead (see CSS Custom Properties).
Embedded fonts
The custom element injects font subsets as base64 WOFF2 data URIs — no network request, no installation required.
| Family | Source | Codepoints |
|---|---|---|
GrigsonSans |
Noto Sans (© Google, SIL OFL 1.1) | Latin-1 (U+0000–00FF) |
GrigsonSerif |
Noto Serif (© Google, SIL OFL 1.1) | Latin-1 (U+0000–00FF) |
GrigsonSans / GrigsonSerif |
Noto Sans Symbols 2 (© Google, SIL OFL 1.1) | △ (U+25B3) |
GrigsonSans / GrigsonSerif |
Bravura (© Steinberg, SIL OFL 1.1) | ♭ ♯ (U+266D, U+266F) |
GrigsonTimeSig |
Bravura | Math Bold digits U+1D7CE–U+1D7D7 (used by time-sig) |
GrigsonCursive |
PetalumaScript (© Steinberg, SIL OFL 1.1) | Latin-1, ♭ ♯, Math Bold digits |
The families are composed using CSS unicode-range, so each codepoint is served from the most appropriate source. Users who supply their own font via --grigson-font-family bypass the embedded fonts entirely.