Custom HTML Templates

Theme CalcMark HTML output with CSS variables, or build a fully custom page using shared template partials.

On this page

CalcMark ships shared template partials and CSS custom properties that make it easy to customize HTML output — from a one-line color override to a fully branded page with custom scripts.

Quick Start: Override a CSS Variable #

The fastest way to restyle CalcMark output is to override one or more --cm-* CSS variables. Create a template file (e.g., my-theme.gohtml):

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My CalcMark Doc</title>
<style>
{{.Style}}
:root { --cm-accent: purple; --cm-font-sans: 'Georgia', serif; }
</style>
</head>
<body><main>{{template "cm-content" .}}</main></body>
</html>

Then render with it:

cm convert budget.cm --to=html -T my-theme.gohtml -o budget.html

The {{.Style}} block injects CalcMark’s built-in CSS (with all the structural layout rules), and your :root overrides change the accent color and font. All block rendering — calc results, diagnostics, errors, frontmatter — comes from the shared cm-content partial.

Available CSS Variables #

All theme values use the --cm-* namespace with sensible defaults. Override any of them in :root or a scoped selector.

VariableDefaultPurpose
--cm-accent#0066ccPrimary brand color — calc block borders, result values, blockquote accents
--cm-accent-light#0550aeLighter accent — frontmatter variable names
--cm-text#333Body text
--cm-text-secondary#57606aMuted text — frontmatter headings, blockquotes
--cm-text-code#24292eCode/monospace text
--cm-text-muted#6e7781Subtle text — frontmatter @ prefix, exchange labels
--cm-bg#f8f9faCalc block background
--cm-bg-subtle#f6f8faCode block background
--cm-bg-frontmatter#f0f4f8Frontmatter section background
--cm-border#d0d7deBorders — tables, frontmatter
--cm-error#b91c1cError text
--cm-error-bg#ffeef0Error background
--cm-warning#866100Warning text
--cm-warning-bg#fff8e1Warning background
--cm-warning-border#e6a817Warning left border
--cm-cascading#7c5c00Cascading error (hint severity)
--cm-font-sansSystem sans-serif stackBody font
--cm-font-monoSF Mono, Monaco, ...Code font

Dark Mode #

To add dark mode support, override the color variables inside a media query or class selector:

@media (prefers-color-scheme: dark) {
  :root {
    --cm-accent: #a78bfa;
    --cm-text: #e5e5e5;
    --cm-text-code: #e5e5e5;
    --cm-bg: #1e1e2e;
    --cm-bg-subtle: #313244;
    --cm-bg-frontmatter: #2a2a3e;
    --cm-border: #45475a;
    --cm-error: #f38ba8;
    --cm-error-bg: #302030;
  }
  body { background: #1e1e2e; }
}

For toggle-based dark mode (like a button in your UI), use a class or data attribute instead:

[data-theme="dark"] {
  --cm-accent: #a78bfa;
  --cm-text: #e5e5e5;
  /* ... */
}

Template Partials #

Custom templates have access to shared partials that handle all the complex rendering logic. You never need to duplicate block iteration, diagnostic display, or frontmatter layout.

Available Partials #

PartialPurpose
cm-contentRenders frontmatter + all blocks. Most templates use just this.
cm-frontmatterFrontmatter section only (globals, exchange rates, scale, etc.)
cm-blocksAll blocks — iterates and dispatches to cm-calc-block / cm-text-block
cm-calc-blockSingle calculation block with per-line results, errors, and diagnostics
cm-text-blockSingle text block (rendered Markdown) with warnings

Call them via {{template "cm-content" .}} or use individual partials for layout control:

<div class="sidebar">{{template "cm-frontmatter" .}}</div>
<div class="main">{{template "cm-blocks" .}}</div>

Template Data Model #

The data struct passed to every template:

FieldTypeDescription
.Styletemplate.CSSFull CalcMark CSS stylesheet
.Frontmatter*TemplateFrontmatterFrontmatter config (nil if none)
.Blocks[]TemplateBlockAll document blocks
.Contenttemplate.HTMLEmbedded-mode pre-rendered HTML (empty in CM mode)

Each TemplateBlock has .Type ("calculation" or "text"), .SourceLines, .Error, .HasLineDiagnostic, .Warnings, .HTML, and .DocLine. Each TemplateLine has .Source, .Result, .Error, .IsCascading, and .DocLine.

Use cm convert --show-template to see the default template as a starting point.

Real-World Examples #

CalcMark Lark (Playground) #

CalcMark Lark is the web-based playground at lark.calcmark.org. Its template uses partials for CM-mode rendering and handles embedded mode with a separate branch:

<style>
{{.Style}}
:root { --cm-accent: #7D56F4; --cm-font-sans: "Inter", system-ui, sans-serif; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --cm-accent: #a371f7; --cm-bg: #0d1117; --cm-text: #e5e5e5; /* ... */
  }
}
</style>
<!-- ... -->
{{if .Content}}
<div class="embedded-content">{{.Content}}</div>
{{else}}
{{template "cm-content" .}}
{{end}}

The full template is ~75 lines — most of which is the embedded-content CSS. The CM-mode rendering is a single {{template "cm-content" .}} call.

LSP Preview (Editor Webview) #

The built-in preview template (format.PreviewHTMLTemplate()) is the simplest possible consumer — a content-only fragment with no page wrapper:

{{template "cm-content" .}}

LSP clients (VS Code, etc.) provide their own HTML shell and inject the fragment. The --cm-* variables inherit from whatever theme the editor sets.

Go API #

For programmatic use, the format package exposes these accessors:

import "github.com/CalcMark/go-calcmark/format"

format.StyleCSS()            // Raw CSS string (for injection via strings.Replace, etc.)
format.PartialsTemplate()    // Shared partials source (cm-content, cm-blocks, etc.)
format.DefaultHTMLTemplate() // Default full-page template
format.PreviewHTMLTemplate() // Content-only fragment template

To render with a custom template via the Go API:

result, err := calcmark.Convert(source, calcmark.Options{
    Format:   "html",
    Template: myCustomTemplate, // Go html/template string
})

Partials are automatically available — your custom template can call {{template "cm-content" .}} without any extra setup.

Further Reading #