Code
hc-code is a code surface styled from the kit’s tokens. Its three read-only
modes share one decoration mechanism and need no script — all state lives
in HTML attributes, so they work under a strict default-src 'self'
Content-Security-Policy. A fourth, editable mode is a real <textarea> that
installCodeEditor() upgrades with a line-number gutter and an optional live
syntax-highlight overlay. Syntax highlighting (read-only or live) reuses one
token palette and stays CSP-safe.
Basic HTML
Section titled “Basic HTML”Apply hc-code to a <pre>. Lines overflow into a horizontal scroll by
default. Because a scrollable region must be keyboard-reachable, make a
scrollable block a focusable, labelled region (tabindex="0",
role="region", aria-label).
SELECT id, email
FROM members
WHERE active = true <pre class="hc-code" tabindex="0" role="region" aria-label="Example query"><code>SELECT id, emailFROM membersWHERE active = true</code></pre>Wrapping
Section titled “Wrapping”Long lines scroll horizontally by default. Set data-wrap="on" to soft-wrap
them instead.
<pre class="hc-code" data-wrap="on"><code>SELECT a_very_long_column_name, another_long_one FROM a_table_with_a_long_name</code></pre>Line numbers & per-line state
Section titled “Line numbers & per-line state”Apply hc-code to an <ol> with data-gutter="line-numbers", one
<li class="hc-code__line"> per line. Each line takes an optional
data-state that tints it from the semantic status tokens — covered
(success) and missed (error) are built in, for test-coverage gutters.
hc-code__swatch[data-state] is a matching legend chip.
- SELECT *
- FROM orders
- WHERE total > 0
Covered Missed
<ol class="hc-code" data-gutter="line-numbers" tabindex="0" aria-label="Coverage of members.sql"> <li class="hc-code__line">SELECT *</li> <li class="hc-code__line" data-state="covered"> FROM orders</li> <li class="hc-code__line" data-state="missed"> WHERE total > 0</li></ol>
<p class="hc-code__legend"> <span><span class="hc-code__swatch" data-state="covered"></span> Covered</span> <span><span class="hc-code__swatch" data-state="missed"></span> Missed</span></p>Unified diff
Section titled “Unified diff”Set data-mode="diff" on the <ol>. Each line carries a data-state of
added, removed, or context, plus data-old / data-new line numbers.
The gutter prints a + / - sign alongside the tint, so the change is not
conveyed by colour alone. The server computes the hunks — the kit only
styles them, so there is no client-side diffing and no script.
- SELECT id
- FROM users
- FROM app_users
<ol class="hc-code" data-mode="diff" tabindex="0" aria-label="Draft vs saved members.sql"> <li class="hc-code__line" data-state="context" data-old="12" data-new="12"> SELECT id</li> <li class="hc-code__line" data-state="removed" data-old="13"> FROM users</li> <li class="hc-code__line" data-state="added" data-new="13"> FROM app_users</li></ol>A side-by-side (split) layout is not part of this release; the unified view above is the supported diff presentation.
Editable field
Section titled “Editable field”Apply data-editable to a <div class="hc-code"> wrapping a real
<textarea class="hc-code__input" name="…">. The value is a native form
control, so it submits in forms and with htmx (data-hx-post,
data-hx-include) and degrades to a plain monospace textarea with no
JavaScript. Add data-gutter="line-numbers" and installCodeEditor()
(shipped in the auto-init behaviors bundle) overlays a line-number gutter
that re-numbers on input and scrolls in sync.
<div class="hc-code" data-editable data-gutter="line-numbers"> <textarea class="hc-code__input" name="content" spellcheck="false" aria-label="Route SQL">SELECT idFROM membersWHERE active = true</textarea></div>To keep the numbers aligned the behavior sets the textarea to not soft-wrap
(wrap="off"); long lines scroll horizontally. Label the textarea
(aria-label or a <label for>) — it is a form control.
Live highlight
Section titled “Live highlight”Add data-lang to opt the editable field into a live highlight overlay.
When the value resolves to a registered grammar, installCodeEditor() inserts
a decorative, aria-hidden hc-code__highlight layer behind the textarea,
re-tokenizes on input, and matches the textarea’s scroll. The textarea glyphs
are hidden (its caret stays visible) so the coloured overlay — the same
hc-code__tok spans and --hc-code-tok-* palette as the read-only
syntax highlighting below — shows through. It is
CSP-safe (self-hosted, no eval), and purely additive: with no JS, an
unknown data-lang, or no registered grammar, the field stays the plain
monospace textarea and its value still submits.
<div class="hc-code" data-editable data-gutter="line-numbers" data-lang="sql"> <textarea class="hc-code__input" name="content" spellcheck="false" aria-label="Route SQL">SELECT id, emailFROM membersWHERE active = true</textarea></div>Built-in grammars cover sql, json, yaml, and html (with yml / xml
aliases). Tokenization is throttled to one render per animation frame, so large
buffers stay responsive.
Custom grammars
Section titled “Custom grammars”A generic grammar can’t know dialect constructs — TesseraQL’s 2-way SQL
directives (/*%if … */, binds) should read as meta, which only its own
tokenizer knows. Register one with registerCodeLanguage(name, tokenizer). A
tokenizer maps source text to { tok, text } tokens whose text parts
reconstruct the input exactly; tok is an hc-code__tok
value (or falsy for plain text).
Register before the field is enhanced — like
setMessages(), the auto-init
behaviors bundle scans on load, so use the named installers from the main
entry for full control over ordering:
import { registerCodeLanguage, installCodeEditor } from '@hypermedia-components/core';
// A dialect tokenizer that classifies 2-way-SQL directives as `meta`.registerCodeLanguage('tql-sql', (text) => { const tokens = []; const re = /\/\*[%#@][\s\S]*?\*\/|\b(?:SELECT|FROM|WHERE|LIMIT)\b/gi; let last = 0; let m; while ((m = re.exec(text))) { if (m.index > last) tokens.push({ tok: '', text: text.slice(last, m.index) }); tokens.push({ tok: m[0].startsWith('/*') ? 'meta' : 'keyword', text: m[0] }); last = m.index + m[0].length; } if (last < text.length) tokens.push({ tok: '', text: text.slice(last) }); return tokens;});
installCodeEditor(); // now enhances <div class="hc-code" data-editable data-lang="tql-sql">If a tokenizer throws or its tokens don’t reconstruct the source, the overlay declines to highlight that buffer rather than desync from the textarea — the text stays visible, just uncoloured. A registration overrides a built-in of the same name and returns an uninstaller. Registering server-side? Port the same tokenizer to JS and the editor matches the server-rendered read-only view.
Syntax highlighting
Section titled “Syntax highlighting”Read-only highlighting is server-tokenized: the server wraps each token
in <span class="hc-code__tok" data-tok="…">, coloured from the
--hc-code-tok-* palette. There is no client tokenizer — like the diff
hunks, the kit only styles what the server emits, so it stays CSP-safe and
deterministic. Tokens nest inside <pre><code> (plain) or hc-code__line
(line-numbered / diff) and compose with data-state tints and the diff
gutter.
data-tok is a generic, language-agnostic vocabulary — keyword, string,
number, comment, operator, identifier, the structured-markup set
property (object / mapping keys), tag, and attribute, plus meta (a
catch-all for language-specific constructs, e.g. 2-way SQL directives and
binds). An unknown or absent data-tok renders as plain code. The editable
field’s live highlight overlay renders these very same
spans, so the editor matches the read-only / diff surfaces.
SELECT id, 1
FROM members — active only <pre class="hc-code"><code><span class="hc-code__tok" data-tok="keyword">SELECT</span> <span class="hc-code__tok" data-tok="identifier">id</span><span class="hc-code__tok" data-tok="keyword">FROM</span> <span class="hc-code__tok" data-tok="identifier">members</span></code></pre>Token colour wins over a context line’s muted text; line tints (data-state)
and the diff gutter are unaffected. The palette meets WCAG AA contrast on the
code surface in both themes (verified by code-syntax.spec.mjs).
Accessibility
Section titled “Accessibility”- A horizontally scrollable block must be reachable by keyboard. Make it a
focusable, labelled region:
tabindex="0"plusrole="region"and anaria-labelnaming the file or snippet. - In diff mode, colour is never the only cue — the gutter prints a
+(added) or-(removed) sign. The sign rides the list marker, so copying the block copies the code text without the signs or line numbers. - The line text is rendered verbatim; preserve meaningful leading whitespace
in the markup (
white-space: pre). Setdata-wrap="on"only when wrapping long lines is preferable to a scroll. - The editable field’s live-highlight overlay is decorative (
aria-hidden): the textarea stays the labelled control and the value submits unchanged. Tokenization is throttled to one render per frame, so large buffers stay responsive while typing.
Theming tokens
Section titled “Theming tokens”| Token path | Purpose |
|---|---|
code.bg | Surface background. |
code.fg | Code text colour. |
code.border | Border colour. |
code.radius | Corner radius. |
code.padding-block / code.padding-inline | Block padding. |
code.font-family | Monospace font stack. |
code.font-size | Code font size. |
code.line-height | Code line height. |
code.gutter-fg | Line-number / sign colour. |
code.gutter-width | Width of the line-number gutter. |
code.num-width | Width of each diff line-number column. |
code.gutter-gap | Gap between gutter and code. |
code.context-fg | Diff context-line text colour. |
code.added-bg / code.added-marker | added / covered tint & bar. |
code.removed-bg / code.removed-marker | removed / missed tint & bar. |
code.focus-border | Focus ring on the editable field. |
code.input-min-height | Minimum height of the editable textarea. |
code.tok-keyword / -string / -number / -comment / -operator / -identifier / -meta | Syntax-token foregrounds (data-tok). |
code.tok-property / -tag / -attribute | Structured-markup token foregrounds (keys, HTML tags & attributes). |
The tinted-state tokens resolve through the semantic status palette, so they
re-resolve automatically under data-theme="dark".
CSS variables
Section titled “CSS variables”Show the generated CSS variables
--hc-code-bg | -fg | -border | -radius--hc-code-padding-block | -padding-inline--hc-code-font-family | -font-size | -line-height--hc-code-gutter-fg | -gutter-width | -num-width | -gutter-gap--hc-code-context-fg--hc-code-added-bg | -added-marker--hc-code-removed-bg | -removed-marker--hc-code-focus-border | -input-min-height--hc-code-tok-keyword | -tok-string | -tok-number | -tok-comment--hc-code-tok-operator | -tok-identifier | -tok-meta--hc-code-tok-property | -tok-tag | -tok-attributeRelated
Section titled “Related”- Table — for tabular data; often hosts a code or status column.
- Status colors — the same
semantic palette the per-line
data-statetints resolve through. - kbd — inline monospace key hints, as opposed to a multi-line block.