Document
Doc-only chrome lives in --novem-* variables that don't travel to charts or grids: cover, running header, page numbers, table styling, callouts and content elements. General colours come from the Colors page.
AI-assisted, human-approvednovem uses AI to review and keep our documentation up to date.
Novem documents draw their general colours, fonts and surfaces from the Colors primitives that every visualisation consumes. The variables on this page cover document elements: the cover page, running header and footer, page numbers, tables, callouts and inline body content.
The Aliases column lists named-color tokens that resolve to the variable when used in API instructions.
The built-in novem theme layers compact, print-oriented defaults over
these shared primitives: white paper, near-black text, light neutral table
headers, fine rules and underlined links. Dark mode uses the same layout
with a charcoal page and light text. See Typography
for the document type sizes. Custom themes keep the shared primitives
listed below unless they supply their own overrides.
Existing documents using theme: novem receive these defaults when rendered
again, including PDF exports. The new fonts, type sizes, margins and running
header can change line wrapping, page breaks and page counts. Authored sizes,
page margins, headers and footers retain their settings; custom and
organisation themes keep their own styling.
Tables
Tables in novem documents follow a single set of theme tokens that covers the header row, the alternating stripe, the cell border and the totals or section rows that often appear at the bottom or between groups of rows.
| Name | Light | Dark | Aliases |
|---|---|---|---|
--novem-table-header-bg | --novem-primary-surface | --novem-primary-surface | — |
--novem-table-header-text | --novem-on-primary | --novem-on-primary | — |
--novem-table-stripe-bg | #f8f9fa | #1f2940 | — |
--novem-table-border | #e5e7eb | #2a3a4a | — |
--novem-table-total-bg | #e9ecef | #2a2a3e | — |
--novem-table-total-text | --novem-text | --novem-text | — |
--novem-table-section-bg | --novem-surface | --novem-surface | — |
Callouts
Novem callouts draw their semantic colour from the
Status indicators on the Colors
page (--novem-ok, --novem-warn, --novem-err and --novem-info
plus their -bg and -on-* companions). The visual treatment of the
callout box itself is themed here — the border style and width, the
corner radius and the inner padding. These primitives sit alongside the
colour tokens because they're inseparable from callout theming in
practice.
| Name | Light | Dark | Aliases |
|---|---|---|---|
--novem-callout-border-style | dashed | dashed | — |
--novem-callout-border-width | 2px | 2px | — |
--novem-callout-radius | --novem-radius | --novem-radius | — |
--novem-callout-pad | 12px 16px | 12px 16px | — |
Inline emphasis
Inline marks inside body text — highlights, marked spans, definition emphasis — are controlled through a small pair of tokens.
| Name | Light | Dark | Aliases |
|---|---|---|---|
--novem-mark-bg | #fff3cd | #664d03 | — |
--novem-mark-text | --novem-text | --novem-text | — |
Author byline
The author byline is the small block that displays a sender or author alongside the avatar, name and handle. It shows up in mails as the "from" header and in docs that surface authorship metadata.
| Name | Light | Dark | Aliases |
|---|---|---|---|
--novem-author-name-text | --novem-text | --novem-text | — |
--novem-author-handle-text | --novem-text-muted | --novem-text-muted | — |
--novem-author-avatar-bg | --novem-surface-muted | --novem-surface-muted | — |
--novem-author-avatar-border | --novem-border | --novem-border | — |
Call to action
A call to action is a styled content link that points the reader at another resource — a "Read more", "Open dashboard" or "View report" block. It's especially common in mails, where it acts as the visual focal point of the message, and shows up in docs and grids whenever the content needs to direct the reader somewhere else. A CTA is a piece of content rather than a UI button, so the size primitives sit alongside the colour ones.
| Name | Light | Dark | Aliases |
|---|---|---|---|
--novem-cta-bg | --novem-primary | --novem-primary | — |
--novem-cta-text | --novem-on-primary | --novem-on-primary | — |
--novem-cta-border | --novem-primary | --novem-primary | — |
--novem-cta-radius | --novem-radius | --novem-radius | — |
--novem-cta-pad | 12px 24px | 12px 24px | — |
Semantic hooks and spacing
Every rendered block has data-kind and its original data-line. The
id config key becomes data-id; classlist adds theme classes. Use
these selectors to style authored structure:
[data-kind="heading"] { color: var(--novem-primary); }
[data-id="summary"] { border-left: 3px solid var(--novem-accent); }
Block rhythm is expressed as --nd-mt scale units. --nd-unit defaults to
--novem-rhythm-unit (2px). Adjust that variable to change the rhythm of the
whole document, or set margins on selected kinds. An explicit authored
m value takes precedence.
Rules on scaled pages
On screen, a page renders at its real size and is scaled down to fit its frame, so a rule set in points can end up thinner than one screen pixel. Some browsers then drop it. Each page carries two variables for keeping thin rules visible:
| Variable | Value |
|---|---|
--nd-page-scale | the page's effective scale, including the surrounding viewer, 1 when it is shown at full size |
--nd-device-px | one screen pixel, measured in the page's own CSS pixels |
The variables stay current when the viewer resizes, browser zoom changes, or the window moves between displays with different pixel densities.
Give a rule its authored width, but never less than about one and a half screen pixels (exactly one pixel can still be lost when the rule falls between two):
.novem--doc--page { --hairline: max(0.5pt, calc(1.5 * var(--nd-device-px, 0px))); }
[data-kind="table"] td { box-shadow: inset 0 calc(-1 * var(--hairline)) 0 0 var(--novem-border); }
In print --nd-page-scale is 1 and --nd-device-px is 0px, so the PDF
keeps the authored width.
{{ box }} uses novem--doc--box. {{ button }} uses
novem--doc--button plus a variant class (primary, secondary, or link).
Button colours, radius, and padding use --novem-button-* variables.
Page types
A page can declare a free-form type: value in its meta to receive a
matching CSS class on the page itself. Themes use this hook to apply
page-specific styling without inventing per-page tokens.
{{ page type: centered }}
# A centered page
Vislib emits novem--doc--page--type-centered on .novem--doc--page,
alongside the existing data-page-type attribute and the
--doc-title / --doc-author / --doc-date custom properties — so
all type-specific theming sits on a single element:
.novem--doc--page--type-centered .novem--doc--page-content {
align-items: center;
justify-content: center;
}
In the continuous blog/web flow the same class is applied to the
.novem--doc--blog-header (cover-style entries) or
.novem--doc--blog-content section that stands in for a page, so a
single selector keeps working across both layouts. The value is
sanitised before it reaches the DOM:
- Only the first whitespace-delimited word is used —
type: cover sheetbecomescover - Lowercased
- Reduced to
[a-z0-9]— hyphens, underscores and other punctuation are stripped - Leading digits are removed —
type: 2colbecomescol - An empty result is dropped, so no class is added
Page layouts
This section describes the CSS for page layouts and table charts. For the corresponding markdown options and examples, see Layout and Tables.
Use a page's type to select a CSS grid, then assign its blocks to named
areas with area. The renderer sets grid-area on each block and adds a
data-area attribute for CSS selectors.
The following grid places a title across the page, commentary and KPI tiles on the left, and an allocation table on the right. Add it to a custom or organisation theme:
.novem--doc--page--type-summary .novem--doc--page-content {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: auto auto 1fr;
grid-template-areas:
"title title"
"commentary allocation"
"figures allocation";
gap: 24px;
}
The corresponding document content is:
{{ page type: summary, section: "Portfolio summary", autopaginate: false }}
# Portfolio summary {area: title}
{{ para area: commentary }}
The example portfolio holds 40% in equities and 40% in fixed income.
Alternatives and cash account for the remaining 20%.
{{ /para }}
{{ kpis area: figures, cols: 2 }}
{{ kpi label: "Portfolio value", value: "EUR 8M" }}
{{ kpi label: "Quarter", value: "+3%", sentiment: auto }}
{{ /kpis }}
{{ table area: allocation, bars: 2, bars_max: 100, widths: 3 2 }}
| Asset class | Share |
|---|--:|
| Equity | 40% |
| Fixed income | 40% |
| Alternatives | 15% |
| Cash | 5% |
{{ /table }}
A block without area in a grid page is placed by the grid's
auto-placement, so name an area for every block on a layout page. A name
the theme's grid does not declare makes the browser add an implicit track.
Filling an area. A block in an area stretches to the area's height.
An image with height: fill fills it; fit: cover crops the picture to
the box, fit: contain (the default) shows all of it.
Bleed. bleed: runs an image, vis or box out to the page edges it
names, past the page margins and the header and footer bands:
right, top right bottom, x, y or all. The distance is measured
after layout, so it holds whatever furniture the page carries.
Running section. section: on a {{ page }} or {{ tocpage }} sets
the running section label from that page until a later page sets another.
It prints with $section in any block, typically one cascading header,
and reaches CSS as the page-scoped --doc-section string.
Heat tables. {{ table heat: 2-11 }} shades the named columns by value
on one diverging scale per table; heat_max: 6 sets the magnitude that
takes the full shade (larger values clamp; the default is the largest
absolute value in the selected columns). Recolour it with --novem-heat-pos and --novem-heat-neg, and the
zero shades --novem-heat-zero or, per sign, --novem-heat-zero-pos and
--novem-heat-zero-neg. --novem-heat-floor (0-1) is the share of the hue
every tinted cell starts from. A cell whose painted shade is dark takes
--novem-heat-strong-text. An empty body
row renders as a short spacer between row groups (--novem-table-gap).
Bar lists. {{ table bars: 2, bars_max: 100, bar_color: 3 }} draws the
numbers in column 2 as bars: a track, a fill for the value's share of
bars_max (default: the largest value), and the value after it. Column 3
holds each row's colour (a colour token such as blue or gray-400, or a
hex value) and is not displayed. Style them with --novem-table-bar-track,
--novem-table-bar-fill, --novem-table-bar-height and
--novem-table-bar-gap. Add waterfall: true for a bridge: each row moves a
running total by its value, rows marked |= or |== are levels drawn from
zero, and a link joins each bar to the next row
(--novem-table-bar-fill-pos, --novem-table-bar-fill-neg,
--novem-table-bar-link, --novem-table-bar-link-length).
More table charts.
stack: truedraws a row's bar columns as one stacked bar in the first of them, segments coloured--novem-table-bar-seg-1to-4; withlabels: trueeach segment carries its column header and its value.vertical: truegrows bars upward; each bar exposes its share of the scale as--nd-bar, so a theme laying rows out as columns can size it.steps: 10turns an "a of b" cell (Year 3 of 10) into b pips, the first a−1 passed and the a-th current (--novem-table-step,-done,-now).bar_coloralso exposes each row's colour as--nd-row-color, for a swatch or a rule. A colour token follows the light or dark mode.widthscounts displayed columns only: thebar_colorcolumn and a stack's columns after the first are not displayed.- Figures may carry a magnitude suffix:
384kand1.1Mshare a scale. - A body row with a single filled cell is a group label
(
novem--doc--table-row--group); a theme can let it run across the empty cells. A lone=or==in an otherwise empty first cell, under a first column with no header, marks a subtotal or total row.
{{ table bars: 2, bars_max: 100, bar_color: 3 }}
| Asset class | Share | Colour |
|---|--:|---|
| Fixed Income | 41.1% | #1f5f8f |
| Equity | 38.3% | #8cc0e2 |
| Other | 2.3% | gray |
{{ /table }}
Document meta and generated text
Frontmatter identifier keys reach CSS as --doc-meta-key custom properties, so a
running header or footer can print it on every page. The engine
quote-wraps and escapes each value, so it drops straight into a
content: property.
| Variable | Frontmatter key |
|---|---|
--doc-meta-title | title |
--doc-meta-subtitle | subtitle |
--doc-meta-author | author |
--doc-meta-date | date |
These are not the --doc-* properties above: those carry cover
semantics and are stripped on auto-pagination continuations, while
--doc-meta-* are document-wide and repeat on continuations.
A literal string in a content: rule is the same text in every
document that uses the theme — including documents not written in the
theme author's language. So keep words out of generated text and
take the whole line from the document; punctuation and glyphs are fine,
they carry no language.
/* Wrong — "UPDATED" is fixed by the theme, so a German document
renders "UPDATED 3. MÄRZ 2026" */
.novem--doc--page-header::after {
content: "UPDATED " var(--doc-meta-date);
}
/* Right — the frontmatter carries the words as well as the date */
.novem--doc--page-header::after {
content: var(--doc-meta-date);
}
The words now live where a translator can reach them:
---
date: AKTUALISIERT 3. MÄRZ 2026
---
The same rule covers any label a theme prefixes onto document text — a
content: "BY " ahead of the cover author, say. If the wording has to
follow the document's language, it belongs in the document.
For a running line that is real text rather than pseudo-element
content, the header: and footer: page options interpolate any
frontmatter key into the header and footer bands, and the theme only
styles the band:
---
date: 3 MARCH 2026
header: UPDATED {meta.date}
---
In frontmatter it applies document-wide; inside a {{ page }} block it
overrides that one page. false suppresses the authored content. Structural
bands remain available to theme CSS on every non-cover page.
Cover
A cover is created by {{ frontpage }} or {{ page type: cover }}.
Frontmatter alone does not create one. The cover typically uses the
brand surface colours from the Colors page. The variables below let
you override those defaults specifically for the cover without
affecting other surfaces.
| Name | Light | Dark | Aliases |
|---|---|---|---|
--novem-cover-bg | --novem-primary-surface | --novem-primary-surface | — |
--novem-cover-text | --novem-on-primary | --novem-on-primary | — |
--novem-cover-text-muted | --novem-on-primary-muted | --novem-on-primary-muted | — |
--novem-cover-accent | --novem-accent | --novem-accent | — |
Page header
The running header that appears at the top of every page has its own small set of tokens for the background, the bottom rule and the text.
In the built-in novem theme, content pages repeat the frontmatter title
at the left and date at the right, inside the text margins. The row uses
8pt type, a fine rule, and 18px of space before the body. It is omitted
when neither title nor date is present. A header: string or an authored
header block replaces the row; header: false removes it while retaining
the page's top margin.
| Name | Light | Dark | Aliases |
|---|---|---|---|
--novem-page-header-bg | transparent | transparent | — |
--novem-page-header-border | --novem-border | --novem-border | — |
--novem-page-header-text | --novem-text-muted | --novem-text-muted | — |
Page footer and numbering
The built-in novem theme's default footer shows a small, muted page number
aligned right and a 6mm Novem symbol at the lower left. The symbol appears
only with the automatic footer; an authored footer owns that space.
footer: false in frontmatter suppresses both. Use an explicit footer block
for other content, and theme the band and page number independently.
The footer band remains available for theme styling even when it has no
content. It has no separator line by default, including in custom themes
and older documents. A theme can opt in with --novem-footer-rule or an
explicit border on .novem--doc--footer. Footnote separators are styled
independently.
.novem--doc--page {
--novem-footer-rule: 1px solid var(--novem-border);
}
| Name | Light | Dark | Aliases |
|---|---|---|---|
--novem-page-footer-text | --novem-text-muted | --novem-text-muted | — |
--novem-page-number-color | --novem-primary | --novem-primary | — |
--novem-footer-rule | none | none | — |
Spacing
Page padding is controlled per direction so you can tune portrait and landscape layouts independently.
The built-in novem theme uses 15mm top and side margins and a 12.5mm
bottom margin on portrait pages. Landscape keeps the 32px top/bottom and
50px side margins below. Explicit document margins override these defaults
in either orientation. The table lists the shared baseline for custom themes.
| Variable | Default (portrait) | Default (landscape) |
|---|---|---|
--novem-spacing | 8px | 8px |
--novem-page-pad-top | 40px | 32px |
--novem-page-pad-right | 60px | 50px |
--novem-page-pad-bottom | 40px | 32px |
--novem-page-pad-left | 60px | 50px |
Shape and decoration
A small set of non-colour primitives controls the corner radius, the default shadow and the table cell padding.
| Variable | Default |
|---|---|
--novem-radius | 4px |
--novem-shadow | 0 2px 8px rgba(0,0,0,0.1) |
--novem-table-cell-pad | 10px 12px |
Presenter mode
When a document is opened in presenter mode (the /present route) the
deck takes over the viewport. The variables below control the backdrop
behind the slides and the highlight ring drawn around the currently-
selected thumbnail in the overview grid. They have no effect outside
the presenter — the regular doc view ignores them.
| Name | Light | Dark | Aliases |
|---|---|---|---|
--novem-presenter-bg | #1a1a1a | #050507 | — |
--novem-presenter-progress | linear-gradient(90deg, #1f1f1f, #0184a7 50%, #ca5eed) | linear-gradient(90deg, #2a2a2a, #4ab5d8 50%, #d68aee) | — |
--novem-presenter-outline | --novem-accent | #818cf8 | — |
--novem-presenter-outline-hover | --novem-primary | #4f46e5 | — |
--novem-presenter-outline-width | 4px | 4px | — |
--novem-presenter-progress accepts any value the CSS background-image
property accepts, so themes can use a single-colour gradient (linear-gradient(#0184a7, #0184a7)), a
gradient (the default), or even a layered set of backgrounds. The
strip is sized to 100vw 100% so the colour band runs end-to-end of
the deck regardless of how full the bar is.