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:
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.
| Name | Light | Dark | Aliases |
|---|---|---|---|
ok | #198754 | #198754 | success, succ |
warn | #ffc107 | #ffc107 | warning |
err | #dc3545 | #dc3545 | danger, alert, error, failure |
info | #0dcaf0 | #0dcaf0 | notice |
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.
| Name | Light | Dark | Aliases |
|---|---|---|---|
bad | #dc3545 | #dc3545 | low, negative, decrease |
neutral | #fcfcfc | #212121 | mid, zero |
good | #198754 | #198754 | high, 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.
| Name | Light | Dark | Aliases |
|---|---|---|---|
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.
| Name | Light | Dark | Aliases |
|---|---|---|---|
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.
| Family | Tokens |
|---|---|
| Tableau 10 | tab10_c1 … tab10_c10 (aliases c10_1 … c10_10) |
| Tableau 20 | tab20_c1 … tab20_c20 (aliases c20_1 … c20_20) |
| Tableau 20b | tab20b_c1 … tab20b_c20 |
| Tableau 20c | tab20c_c1 … tab20c_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.
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.
| Mode | red-100 | red-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.
| Name | Light | Dark | Aliases |
|---|---|---|---|
--novem-ok | #198754 | #198754 | ok, success, succ |
--novem-ok-bg | #d1e7dd | #82948c | — |
--novem-on-ok | #0a3622 | #ffffff | — |
--novem-warn | #ffc107 | #ffc107 | warn, warning |
--novem-warn-bg | #fff3cd | #6b5d20 | — |
--novem-on-warn | #664d03 | #ffffff | — |
--novem-err | #dc3545 | #dc3545 | err, danger, alert, error, failure |
--novem-err-bg | #fae3e5 | #b3a3a4 | — |
--novem-on-err | #58151c | #ffffff | — |
--novem-info | #0dcaf0 | #0dcaf0 | info, 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.
| Name | Light | Dark | Aliases |
|---|---|---|---|
--novem-bad | #dc3545 | #dc3545 | bad, low, negative, decrease |
--novem-neutral | #fcfcfc | #212121 | neutral, mid, zero |
--novem-good | #198754 | #198754 | good, 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.
| Name | Light | Dark | Aliases |
|---|---|---|---|
--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.
| Name | Light | Dark | Aliases |
|---|---|---|---|
--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.
| Name | Light | Dark | Aliases |
|---|---|---|---|
--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.
| Name | Light | Dark | Aliases |
|---|---|---|---|
--novem-bg | #ffffff | #1a1a2e | — |
--novem-surface | #f8f9fa | #1e1e30 | — |
--novem-surface-strong | #e9ecef | #2a2a3e | — |
--novem-surface-muted | #fcfcfc | #1a1a2e | — |
| Name | Light | Dark | Aliases |
|---|---|---|---|
--novem-border | #dee2e6 | #2a3a4a | — |
--novem-border-strong | #adb5bd | #495057 | — |
--novem-border-subtle | #e9ecef | #2a2a3e | — |
--novem-divider | #dee2e6 | #2a3a4a | — |
| Name | Light | Dark | Aliases |
|---|---|---|---|
--novem-text | #212529 | #dee2e6 | — |
--novem-text-secondary | #6c757d | #9aa3b3 | — |
--novem-text-muted | #adb5bd | #6a7383 | — |
--novem-text-inverse | #1f1f1f | #c0c0c0 | inverse |
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.
| Name | Light | Dark | Aliases |
|---|---|---|---|
--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.
| Name | Light | Dark | Aliases |
|---|---|---|---|
--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.
| Name | Light | Dark | Aliases |
|---|---|---|---|
--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.