Theme

Colors

Novem has a unified color system organised by intent: status indicators, value sentiment, categorical series, brand chrome, and so on. Themes layer on top: change a primitive once, and every doc, grid, plot, and embed follows.

AI assisted, human approved — novem uses AI to review and keep our documentation up to date.

When building novem we wanted to empower our users to to create the perfect visualisation. Whether for their personal blog, video, a professional newspaper, or a startup stats dashboard.

We also wanted it to be: easy with good looking defaults, modern in adapting to trends and changing preferences, and contextual by picking the right colors at the right time.

This page explains the novem color system, then shows how a theme overrides it.

The novem platform uses a unified color format in all places where colors can be provided. Novem colors can be specified explicitly with a hex color code (#ff3d5d), or by using a color name.

In addition to having a large pool of named colors, we've also created a gradient scale for each of the color names. If you add -100 to -900 after the color name you'll get a lighter or darker version of that color.

As en example, this is the color scale for the novem theme color blue:

blue-100
blue-200
blue-300
blue-400
blue-500
blue-600
blue-700
blue-800
blue-900

To further expand on color options, novem offers a dark-mode option for all available colors. Dark mode works a bit different between theme colors and the extended colors -- extended colors will have their gradient inverted, while theme colors will be adjust to a dark mode palette.

Below is shown both the light and dark colors for the novem theme color bad. The middle color differs slightly between the light and dark versions.

In contract, the extended color x-red, is unchanged.

The default behaviour of novem visuals is to detect the user's preferred theme, but this can be overridden per visualisation. You can also specify both light and dark version of the color if precision is required.

Novem colors are hierarchical in nature. Since novem supports embedding multiple visuals within grids, documents, dashboards, and emails, the embedded visualisations will inherit the default color scheme of their context. For instance, a map with predominantly green tones may adapt to a blue color scheme if it is embedded in a document with a primarily blue theme.

The catalog below lists the default mappings. Every entry can be remapped by a theme — red-500 can point at your brand red, primary can point at your corporate navy — without changing the names that visualisation code uses. See Theme variables below for how that works.

Novem groups its named colors into five buckets that serve different visual purposes. Each bucket has its own role in a theme, and knowing which one a name belongs to tells you how it should be overridden.

Status names communicate state. They show up in callouts, badges, alerts, validation messages and log lines, where the renderer always picks a fixed colour rather than interpolating between two.

NameLightDarkAliases
ok#198754#198754success, succ
warn#ffc107#ffc107warning
err#dc3545#dc3545danger, alert, error, failure
info#0dcaf0#0dcaf0notice

Sentiment names mark the endpoints of a value-driven gradient. They show up in heatmaps, diverging cell colours and sparkline conditional fills, and the renderer interpolates from one extreme through a midpoint to the other.

NameLightDarkAliases
bad#dc3545#dc3545low, negative, decrease
neutral#fcfcfc#212121mid, zero
good#198754#198754high, positive, increase

It's worth pointing out that the status and sentiment buckets share default colours but represent different ideas. The status ok and the sentiment good both default to the same green, and the status err and the sentiment bad both default to the same red, but a status pill says "this thing is operational" while a heatmap cell says "this number is high". A theme might paint both greens the same colour, but it might also paint good lime and ok teal — keeping the buckets separate so that green-on-red can be reserved for failures and successes while heatmaps use a different palette altogether.

Brand names carry the house identity for an organisation or a product line. They're the tokens you reach for first when adapting novem to match a corporate visual identity.

NameLightDarkAliases
primary#1a1a2e#c0c8dc
secondary#6c757d#adb5bd
accent#4e79a7#6e90c8

Neutral names are the grayscale anchors that the rest of the chrome hangs off — page backgrounds, body text, the high-contrast inverse ink and the absolute black and white reference values.

NameLightDarkAliases
light#f8f9fa#f8f9fa
dark#212529#212529
inverse#1f1f1f#c0c0c0
black#000000#000000
white#ffffff#ffffff

Categorical palettes are used for chart series, pie slices and grouped bars where the colours need to be distinguishable but carry no ordinal meaning. Novem ships several Tableau-derived palettes of varying length so that a chart with many series still has unique colours to work with.

FamilyTokens
Tableau 10tab10_c1tab10_c10 (aliases c10_1c10_10)
Tableau 20tab20_c1tab20_c20 (aliases c20_1c20_20)
Tableau 20btab20b_c1tab20b_c20
Tableau 20ctab20c_c1tab20c_c20

In a custom plot the active categorical palette is also exposed as render.theme.colors as a JavaScript array indexed from zero.

Each named hue below has a -100 to -900 gradient. Use them anywhere a colour is accepted (red-500, blue-200, and so on), and a theme can remap any step of any scale to your brand equivalents.

gray-100
gray-200
gray-300
gray-400
gray-500
gray-600
gray-700
gray-800
gray-900
blue-100
blue-200
blue-300
blue-400
blue-500
blue-600
blue-700
blue-800
blue-900
indigo-100
indigo-200
indigo-300
indigo-400
indigo-500
indigo-600
indigo-700
indigo-800
indigo-900
purple-100
purple-200
purple-300
purple-400
purple-500
purple-600
purple-700
purple-800
purple-900
pink-100
pink-200
pink-300
pink-400
pink-500
pink-600
pink-700
pink-800
pink-900
red-100
red-200
red-300
red-400
red-500
red-600
red-700
red-800
red-900
orange-100
orange-200
orange-300
orange-400
orange-500
orange-600
orange-700
orange-800
orange-900
yellow-100
yellow-200
yellow-300
yellow-400
yellow-500
yellow-600
yellow-700
yellow-800
yellow-900
green-100
green-200
green-300
green-400
green-500
green-600
green-700
green-800
green-900
teal-100
teal-200
teal-300
teal-400
teal-500
teal-600
teal-700
teal-800
teal-900
cyan-100
cyan-200
cyan-300
cyan-400
cyan-500
cyan-600
cyan-700
cyan-800
cyan-900

The scale flips between light and dark mode. In light mode red-100 is the palest red, but in dark mode it's the darkest red — the value that red-900 would have in light mode. This is a deliberate contrast trick so that the semantic meaning of each step stays constant. A -100 is always a subtle background-tone that's barely visible against the page, and a -900 is always a strong foreground-tone, no matter which mode the visualisation is being viewed in.

You don't usually have to think about it. Code that paints a callout with bg=red-100, text=red-700 works in both modes without any conditional logic.

Modered-100red-700
Light#f8d7da (pale, suitable as bg over white)#842029 (deep, suitable as text)
Dark#842029 (deep, suitable as bg over near-black)#f8d7da (pale, suitable as text)

The midpoint -500 is the only step that's invariant across modes.

Every step of every scale is exposed as two CSS variables, one for each mode, using a suffix convention.

--novem-scale-{hue}-{step}-light
--novem-scale-{hue}-{step}-dark

Override both variants in a single :root block. The engine routes the unsuffixed name like --novem-scale-red-500 to the right variant based on the active mode, so consumer code never has to care.

:root {
  /* corporate red — endpoints flipped between modes */
  --novem-scale-red-100-light: #fce4e4;
  --novem-scale-red-100-dark:  #4a0511;

  --novem-scale-red-500-light: #c8102e;
  --novem-scale-red-500-dark:  #c8102e;

  --novem-scale-red-900-light: #4a0511;
  --novem-scale-red-900-dark:  #fce4e4;
}

Use :root rather than .novem--doc--page for any primitive that should travel across rendering contexts. Charts, grids and mails each have their own config/theme endpoint, but they all read scale variables from the document root, so a single :root block reskins the scale everywhere it's used.

If you only override the midpoint, the two variants are identical, but the platform still expects both to be set.

:root {
  --novem-scale-red-500-light: #c8102e;
  --novem-scale-red-500-dark:  #c8102e;
}

You can override as many or as few steps as you need. Steps you don't touch keep their defaults along with the inversion. The same pattern applies to every named scale, so --novem-scale-blue-200-{light,dark}, --novem-scale-gray-700-{light,dark} and so on all behave the same way.

The same -light and -dark suffix convention applies to every mode-aware variable on this page. The Light and Dark columns in the tables below show the values you'd assign to each suffix variant.

A theme overrides the color system through a set of stable --novem-* CSS variables. The names are stable, so changing a value reskins everything that consumes it, and the full set of defaults lives in the registry at vislib/lib/theme/variables.ts.

The variables documented on this page are the general-purpose colour primitives that are used everywhere and not tied to a single rendering surface. Surface-specific colour tokens live with their surface. The plot area, axes, gridlines, legend, tooltip, annotations, bar positive/negative, map, calendar and mtable primitives are documented on Charts, and tables, callouts, inline emphasis, author byline, CTA, cover, page header, footer and page numbering primitives are on Document.

The Aliases column lists named-color tokens that resolve to this variable when used in API instructions. Writing success in an instruction lands on --novem-ok, and so on for every alias listed.

Status indicators are the discrete semantic states that paint callouts, badges, alerts, log lines and validation messages. Each role uses three companion variables: a foreground colour for icons, borders and text; a background tint for callout fills and badge backgrounds; and a text colour to sit on top of that background.

NameLightDarkAliases
--novem-ok#198754#198754ok, success, succ
--novem-ok-bg#d1e7dd#82948c
--novem-on-ok#0a3622#ffffff
--novem-warn#ffc107#ffc107warn, warning
--novem-warn-bg#fff3cd#6b5d20
--novem-on-warn#664d03#ffffff
--novem-err#dc3545#dc3545err, danger, alert, error, failure
--novem-err-bg#fae3e5#b3a3a4
--novem-on-err#58151c#ffffff
--novem-info#0dcaf0#0dcaf0info, notice
--novem-info-bg#cff4fc#2a5660
--novem-on-info#055160#ffffff

Value sentiment uses a three-stop continuous scale for heatmaps, diverging cell colours, sparkline conditional fills and any other visualisation where a value crosses through a neutral midpoint.

NameLightDarkAliases
--novem-bad#dc3545#dc3545bad, low, negative, decrease
--novem-neutral#fcfcfc#212121neutral, mid, zero
--novem-good#198754#198754good, high, positive, increase

When mtable evaluates an instruction like bad,neutral,good(_,0,_)^lin it pulls these three primitives, so re-pointing them re-skins every diverging cell scale, heatmap and sparkline at once.

Sequential scales paint magnitude without a zero crossing — population density, hit count, hour-of-day intensity. The two tokens here are the gradient endpoints and everything in between is interpolated.

NameLightDarkAliases
--novem-seq-low--novem-surface--novem-surface
--novem-seq-high--novem-primary--novem-primary

Novem ships ten ordered colours for chart series, pie slices and grouped bars. The renderer cycles through them in order, and a custom plot can pick them up via render.theme.colors.

NameLightDarkAliases
--novem-color-1#4e79a7#4e79a7
--novem-color-2#f28e2c#f28e2c
--novem-color-3#e15759#e15759
--novem-color-4#76b7b2#76b7b2
--novem-color-5#59a14f#59a14f
--novem-color-6#edc949#edc949
--novem-color-7#af7aa1#af7aa1
--novem-color-8#ff9da7#ff9da7
--novem-color-9#9c755f#9c755f
--novem-color-10#bab0ab#bab0ab

For longer series the platform also accepts tab20_c* named tokens, which extend the palette beyond the ten defaults if your visualisation needs more than ten distinguishable colours.

Brand variables give a doc, grid or report its house style. The platform splits them into two kinds of role. Brand text roles like --novem-primary are used for headings, page numbers and accent text that sit on the page background. Brand surface roles like --novem-primary-surface, --novem-secondary and --novem-accent are used as a backdrop for other content. Each surface role has an on- companion that gives the text or icon colour for content placed on top of it.

NameLightDarkAliases
--novem-primary#1a1a2e#c0c8dc
--novem-primary-surface#1a1a2e#0e0e1e
--novem-on-primary#ffffff#ffffff
--novem-on-primary-muted#8a9cc5#8a9cc5
--novem-secondary#6c757d#adb5bd
--novem-on-secondary#ffffff#1a1a2e
--novem-accent#4e79a7#6e90c8
--novem-on-accent#ffffff#ffffff

A common point of confusion: --novem-on-primary is the text colour for content sitting on --novem-primary-surface, not on --novem-primary directly. --novem-primary is a text colour itself and doesn't have its own pair.

The surface and ink primitives cover page backgrounds, borders and body text. Most of the work in adapting a theme to a new brand happens here.

NameLightDarkAliases
--novem-bg#ffffff#1a1a2e
--novem-surface#f8f9fa#1e1e30
--novem-surface-strong#e9ecef#2a2a3e
--novem-surface-muted#fcfcfc#1a1a2e

NameLightDarkAliases
--novem-border#dee2e6#2a3a4a
--novem-border-strong#adb5bd#495057
--novem-border-subtle#e9ecef#2a2a3e
--novem-divider#dee2e6#2a3a4a

NameLightDarkAliases
--novem-text#212529#dee2e6
--novem-text-secondary#6c757d#9aa3b3
--novem-text-muted#adb5bd#6a7383
--novem-text-inverse#1f1f1f#c0c0c0inverse

Some surfaces are coloured by data rather than by a theme variable — mtable cells, sparklines, conditional fills and heatmaps all set their backgrounds based on a value. The renderer doesn't know in advance whether the resolved surface will be light or dark, so it picks one of two ink tokens based on a luminance test of the surface and uses that for the bar fill, the label or the divider.

NameLightDarkAliases
--novem-on-light#1d2d35#1d2d35
--novem-on-dark#dee2e6#dee2e6

Links cover the appearance of clickable text inside rendered content. The hover and visited variants only matter when the content is viewed in a browser — static or printed output ignores them.

NameLightDarkAliases
--novem-link--novem-accent--novem-accent
--novem-link-hover--novem-accent--novem-accent
--novem-link-visited#6f42c1#a370f7

Inline code and fenced code blocks each get their own background and text tokens so a theme can match the look of code samples to the surrounding chrome.

NameLightDarkAliases
--novem-code-bg--novem-surface--novem-surface
--novem-code-text--novem-text--novem-text
--novem-code-inline-bg--novem-surface--novem-surface
--novem-code-inline-text--novem-link--novem-link

If you're just starting out with data visualisation and colors, a lot of the information in this document might seem overwhelming. If that is the case, hopefully this little Q&A section can offer some help.

A hex color is a web and design convention of describing a color with 6 hexadecimal digits prefixed by a hash (#) symbol. The number can be though of as three groupings of 2 letters representing all values between 0 and 255 (00 to FF). These values correspond to the Red, Green and Blue primary colors used to create the color you want.

There is also an 8-digit version (#ff3dba70), where the two last digits represent opacity.

Dark mode is a modern convention where the users device can tell a web page or application that the user prefers a color scheme with a dark background and light text.

More and more applications and web pages are supporting dark mode, and we want to make sure that novem visuals fit every context.