Map theme plugin
The Map Theme plugin themes a TomTomMap from any source of a look, such as a brand’s swatches, your page’s CSS, a logo, a photo, a mood or an artist. It turns the source into the ten map colours for land, water, roads and labels, plus the accent that routes, geometries and pins take. setMapColors applies them to the whole map.
What it is for
Every source comes down to a handful of colours, and one call assigns them their roles:
| The look comes from | Call |
|---|---|
| A brand guide, a palette, colours a user picked | deriveMapColors(colors) |
| Your own page’s CSS | deriveMapColors over its custom properties |
| A stylesheet: a website’s, a design system’s tokens | deriveMapColorsFromCss(cssText) |
| A logo, a photo, a poster | deriveMapColorsFromImage(image) |
| A mood, an artist | a coding agent or the Agent Toolkit writes the colours, then deriveMapColors |
Installation
npm install @tomtom-org/maps-sdk maplibre-gl culori @tomtom-org/maps-sdk-plugin-map-theme@tomtom-org/maps-sdk and culori are peer dependencies. Every function is pure. Deriving needs no backend and no map.
From colours
import { StylingFoundationsModule } from '@tomtom-org/maps-sdk/map';import { deriveMapColors } from '@tomtom-org/maps-sdk-plugin-map-theme';
const styling = await StylingFoundationsModule.get(map);const colors = deriveMapColors(['#1d3f8a', '#e63312', '#f5d000', '#111111', '#f4f1ea']);styling.setMapColors(colors);- Every role comes back, including the accent, so a UI can show what became what.
setKnob(styling, stylingFoundationsKnobCatalogue, 'colors.water', …)corrects one. - An applied theme lives in the
colors.*knobs of the Styling Foundations module. It survivessetStyleand serialises withgetConfig(). - To theme the base map alone, leave
accentout of the object beforesetMapColors.
How colours get their roles
The assignment is deterministic. Order doesn’t matter, and near-duplicates count once.
| Role | Gets |
|---|---|
land | The lightest low-saturation colour (the darkest on a dark theme), toned down to a neutral |
roadMajor | The most colourful (highest OKLCH chroma) colour, preferring one that isn’t blue or green |
water, vegetation | A blue or cyan, and a green |
label | The colour contrasting most with land, if it reaches 4.5:1 |
accent | The most colourful colour left that reads 3:1 on land and sits 30° of hue or more from roadMajor. Otherwise, roadMajor’s complement |
- Roles no colour fits are derived from
land. Roads and outlines become shades of it, and water and vegetation become muted hues. One to eight colours always give a complete palette, and picked colours come back as given. modeis'auto'(the default),'light'or'dark', listed inmapThemeModes.'auto'makes a dark theme when the colours are mostly dark.'light'and'dark'skip that check, butlandstill comes from your colours.- Low-saturation means an HSL saturation of 40% or less. A saturated colour becomes
landonly when none of your colours is low-saturation. So if white is your only low-saturation colour,mode: 'dark'keeps a whitelandandmapColorsLightDarkreturns'light'. To keep a dark map, add a dark grey such as#141414.
Light and dark styles
setMapColors re-derives each style’s own shades at that style’s offsets, so a dark theme over streetLight pushes them to black. mapColorsLightDark(colors) tells a dark theme (labels lighter than land) from a light one, whether 'auto' or the source decided it. Use it to switch the map first:
import { mapColorsLightDark } from '@tomtom-org/maps-sdk-plugin-map-theme';
const lightDark = mapColorsLightDark(colors);if (lightDark !== map.styleLightDarkTheme) { await map.setStyle(lightDark === 'dark' ? 'streetDark' : 'streetLight');}styling.setMapColors(colors);From your page’s CSS
To give a map your app’s brand, read the page’s custom properties and its theme-color, and theme from those.
const css = getComputedStyle(document.documentElement);const brand = ['--color-primary', '--color-accent', '--color-surface', '--color-text'] .map((name) => css.getPropertyValue(name).trim()) .concat(document.querySelector('meta[name="theme-color"]')?.content ?? []) .filter(Boolean);
styling.setMapColors(deriveMapColors(brand));Use your design system’s own property names. The plugin reads colours in hex, rgb(), hsl() or a CSS name, and skips anything else.
From a stylesheet
A whole stylesheet works too, such as a website’s CSS, a design system’s tokens or a file a user uploads. deriveMapColorsFromCss(cssText, { mode, colorCount }) reads the colours it paints with and assigns them roles. It takes the CSS as text, so it needs no DOM.
import { deriveMapColorsFromCss } from '@tomtom-org/maps-sdk-plugin-map-theme';
// A File from an <input type="file" accept=".css">, or CSS a server fetchedstyling.setMapColors(deriveMapColorsFromCss(await fileInput.files[0].text()));- The most used colour wins. A colour counts every time a rule fills with it. Borders and shadows count for less. Translucent colours, a colour’s own shades (a button’s hover) and words in
url()paths don’t count. var()resolves as the page does, against the rule’s own custom properties or the page root’s, not one set on another selector (.inverse { --bg: … }).- Greys don’t crowd out the brand. While colourful ones remain, the six colours (
colorCountchanges that) hold at most two greys, a background and a text colour. - The theme is light or dark to match the background on
:root,htmlorbody. Otherwise, it matches the grey most rules fill with. It is light when neither resolves. The plugin reads rules for one colour scheme (prefers-color-scheme, a.darkor.lightclass,data-theme="dark") only with thatmode, and they then override the defaults.'auto'reads the page without them. - The plugin doesn’t follow
@imports, so concatenate the files first.
cssColors(cssText) returns the colours, most used first, without assigning roles, so the user can edit them first.
From an image
import { deriveMapColorsFromImage } from '@tomtom-org/maps-sdk-plugin-map-theme';
// A File from an <input type="file">, a Blob, an <img>, an ImageBitmap or a CORS-readable URLstyling.setMapColors(await deriveMapColorsFromImage(fileInput.files[0], { mode: 'dark' }));- The plugin draws the image into a 64×64 canvas in the browser, which is cheap, and its six dominant colours (
colorCountchanges that) get the roles. A small but striking colour can go missing. - A URL must be served with
Access-Control-Allow-Origin. Otherwise, pass theFileorBlob. - Without a DOM (a worker, a test, a server), decode the image yourself and pass
{ data, width, height }(ImagePixels, shaped likeImageData). Or handdominantColors(pixels)toderiveMapColors. - Busy photos average into muted colours. Raise
colorCount, or pick fromdominantColorsby hand.
From a mood, an artist or a website you cannot read
A mood has no colours to read. A page can’t fetch another origin’s CSS either, though a server can fetch it and hand it to deriveMapColorsFromCss. So these start with a model, not a function:
- A coding agent reads the site’s
theme-color,:rootcustom properties and logo, or turns “warm earthy tones” or “like a Miró” into three to six hex colours. It then writes thederiveMapColorscall. The/tomtom-maps-sdk-jsskill has the recipes. - In a conversation, the Agent Toolkit’s
setMapStyling({ theme: { colors } })does the same at run time, andtheme: { imageUrl }themes from an image. It switches to the light or dark variant of the standard style the theme needs.
Limits
- The assignment is a heuristic. It makes calls a designer wouldn’t, which is why every role comes back for correction.
- Only the label contrast is guaranteed. A palette of near-equal lightness gives roads barely separable from land. A label derived for a mid-lightness land takes whichever side reads, even against the
mode. - If they find no colour that parses, or none that is opaque,
deriveMapColorsandderiveMapColorsFromCssthrow aRangeError, andderiveMapColorsFromImagerejects with one. An image that can’t be read (not CORS-readable, no DOM to decode it) rejects too. Catch the error to keep the current colours. - A theme recolours a TomTom style. It sets the ten map colours and the accent, and leaves single layers as the style draws them. Editing style layers covers those.
API reference
deriveMapColors · mapColorsLightDark · deriveMapColorsFromCss · cssColors · deriveMapColorsFromImage · dominantColors · mapThemeModes
Related guides and examples
- Map styling —
setMapColors, the ten colours and the accent - Map theme from colours, an image or CSS