This is the documentation of version 0.62.1. Go to the latest version, 1.0.0-rc.5.

Map styling

The Styling Module restyles the base map, its POIs and the traffic overlays through semantic knobs: bigger labels, thicker roads, no exit numbers, orange for slow traffic. It sits between hiding whole layer groups with the Base Map Module and editing individual MapLibre layers by hand — and unlike either, it is designed to be read by machines: the whole catalogue of knobs is available as data. The playground below is built from that catalogue: one control per knob.

Initialization

import { TomTomMap, StylingModule } from '@tomtom-org/maps-sdk/map';

const map = new TomTomMap({ mapLibre: { container: 'map' } });
const styling = await StylingModule.get(map);

The module is shared per map: a second get() returns the same instance. Pass settings to apply them right away, for instance restored from storage:

const styling = await StylingModule.get(map, { 'labels.sizeFactor': 1.2, 'buildings.3d': true });

Knobs

Every adjustable thing is a knob with a stable id. Set one with set, read it with get, restore the style's own value with reset:

styling.set('labels.sizeFactor', 1.3);        // every base-map label 30% bigger
styling.set('roads.widthFactor', 0.8);        // thinner roads, hierarchy kept
styling.set('roads.exitNumbers', false);      // no exit number badges
styling.set('buildings.3d', true);            // extruded buildings
styling.set('pois.minZoom', 10);              // POIs appear from zoom 10
styling.set('traffic.flow.slowColor', '#f59e0b');

styling.get('roads.widthFactor');             // 0.8
styling.reset('roads.widthFactor');           // back to the style's width
styling.reset();                              // everything back to the style as shipped

How knobs work

A knob's kind decides what it does to the loaded style:

Kind Takes What it does
factor a multiplier, 1 = as the style ships multiplies the style's own expression — text-size: E becomes ["*", 1.3, E] — so zoom ramps and the road hierarchy are kept
toggle true / false shows or hides the layers it names, view.sky the sky; the basemap.*, buildings.* and roads.shields toggles go through the Base Map Module
number a value in a range replaces one value outright: the zoom POIs appear from, the light direction of the hillshade
colour any CSS colour replaces the colour, and re-derives the shades the style built from it: recolouring slow traffic recolours its darker outline by the same offset. colors.accent paints no layer: the modules read it
enum one of the knob's options picks a mode: the hillshade method, the projection

Every knob also follows the same rules:

Knobs write only the style's layers and the map's view. What the SDK draws itself — routes, places, geometries — is configured on its own module, and a few of its parts follow the knobs (The accent, Borrowed looks).

Every knob

Group Knobs Kind
Labels & symbols labels.sizeFactor, symbols.sizeFactor factor 0.5–1.5
Roads roads.widthFactor factor 0.5–1.5
roads.exitNumbers, roads.shields, roads.arrows, roads.restricted, roads.underConstruction toggle
Buildings buildings.footprints, buildings.3d toggle
POIs pois.sizeFactor factor 0.5–1.5
pois.minZoom (3–18), pois.zoomShift (−3–3) number
pois.labelColor, pois.labelOutlineColor colour
pois.microMarkers toggle
Traffic flow traffic.flow.freeColor, traffic.flow.slowColor, traffic.flow.queueingColor, traffic.flow.stationaryColor, traffic.flow.closedColor colour
traffic.flow.widthFactor factor 0.5–2
Traffic incidents traffic.incidents.minorColor, traffic.incidents.moderateColor, traffic.incidents.majorColor, traffic.incidents.closedColor colour
traffic.incidents.widthFactor factor 0.5–2
Map colours colors.land, colors.water, colors.vegetation, colors.park, colors.artificial, colors.roadMajor, colors.road, colors.roadOutline, colors.label, colors.labelOutline, and colors.accent colour
Base-map groups basemap.land, basemap.water, basemap.roads, basemap.railways, basemap.ferries, basemap.borders, basemap.natureLabels, basemap.roadLabels, basemap.houseNumbers, basemap.smallerTownLabels, basemap.stateLabels, basemap.cityLabels, basemap.allPlaceLabels, basemap.capitalLabels, basemap.countryLabels: one per base-map layer group except the two building ones and roadShields, which are the buildings.* and roads.shields knobs above toggle
Hillshade hillshade.method (standard | basic | igor | combined | multidirectional) enum
hillshade.lightDirection (0–359), hillshade.exaggeration (0–1), hillshade.maxZoom (10–22) number
hillshade.shadowColor, hillshade.highlightColor, hillshade.accentColor colour
View view.projection (mercator | globe) enum
view.sky toggle
view.skyColor, view.horizonColor, view.spaceColor colour

Two groups restyle layers another module shows. The traffic knobs show nothing until the Traffic flow or Traffic incidents module shows its layers. The hillshade knobs shape the shading once the Terrain module shows it; that guide explains why they show nothing at city zoom until two of them are set, and how hillshade.exaggeration differs from the exaggeration of the 3D surface.

The knob catalogue

stylingKnobCatalogue lists every knob as static data — id, kind, description, range or options, and which styles it works on — so it reads the same with no map loaded as with one. styling.get(id) gives the value in force on a live map.

import { stylingKnobCatalogue } from '@tomtom-org/maps-sdk/map';

for (const knob of stylingKnobCatalogue) {
    // knob.id, knob.kind ('factor' | 'number' | 'toggle' | 'color' | 'enum'), knob.description,
    // knob.range (factor and number), knob.options (enum), knob.appliesTo
    const current = styling.get(knob.id);
}

The playground at the top of this guide is exactly that: its panel is generated from the catalogue, each control showing the value get reads. Its entries carry no default, since the loaded style decides; the other catalogues of the SDK share their shape (Every setting as data).

The ids are also exported on their own: stylingKnobIds lists every knob, typed StylingKnobId, and stylingColorKnobIds (typed StylingColorKnobId) the colour ones. Other code names a map colour through them rather than hard-coding it, as the map effects plugin does to scope its bloom.

Two things are worth copying from the playground when you wire controls up. A slider or a colour picker fires input far faster than a restyle runs, so coalesce the calls into one set per animation frame (last value wins) and apply the final value on change; the readout can follow every event, since updating a label costs nothing. And if you rebuild the panel from the config-change event, skip the rebuild for changes your own controls made — set emits the event, so rebuilding mid-drag replaces the input under the pointer. Rebuild on external changes only: a reset, a setStyle, or an agent.

Globe and sky

The view knobs own the map-level MapLibre state that a style switch would otherwise reset — projection and sky — so a globe stays a globe across setStyle:

styling.set('view.projection', 'globe');
styling.set('view.sky', true);        // MapLibre's default sky is fully transparent: a globe with no atmosphere

3D terrain is not a knob: it needs the elevation style part loaded first, which TerrainModule.get does for you.

The sky follows the style's light/dark theme: a daylit blue with a white horizon over a light style, a night blue with a dim horizon over a dark one. view.skyColor and view.horizonColor override either, and a style that declares its own sky wins over both.

view.spaceColor is the colour behind the map — what a globe leaves visible around the planet. The map canvas is transparent there, so something has to fill it. Where your page already gives the map container a background, that colour is the knob's default and what a reset returns to, so the SDK never paints over your own styling. Where it does not, the knob follows the style theme as the sky does, and is re-applied after a setStyle, so switching light to dark carries the surround with it:

styling.set('view.spaceColor', '#05070d');   // or leave it to follow the style theme

Three things worth knowing, measured rather than assumed: the globe flattens to Mercator between zoom 11 and 12 by design; without view.sky a globe has no atmosphere; and MapLibre's fog over a tilted view starts at pitch 60, which is also its default maxPitch, so raise maxPitch on the map if you want to see it. The demo above zooms a globe into 3D terrain; the 3D terrain playground keeps a tilted camera, its sky and its relief across a style switch. The map effects plugin adds a depth of field over a tilted view.

Presets

A preset is a named bundle of knob settings for a common intent — TomTom's cartographic opinions, in code. stylingPresetCatalogue lists them with the settings each one applies:

Preset Intent
data-viz A quiet base for your own data on top: fewer labels and markers, thinner roads, no badges
night-driving Bigger labels and shields, wider roads and traffic tubes, POIs out of the way until close up
minimal The bare map: land, water, roads and the main place names
globe Globe projection with an atmosphere
styling.applyPreset('data-viz');                   // replaces the current settings
styling.set('labels.sizeFactor', 1.1);             // then adjust
styling.applyPreset('globe', { merge: true });     // lay a preset over what is there

A preset is exactly as durable as the knobs it sets, and re-applying one on a later SDK version keeps working for as long as those knob ids do. For the visual half of the data-viz intent — dimming and desaturating what remains — see the Map effects plugin.

Map colours: setMapColors()

The most common styling ask — "our map in our brand colours" — is one call over Map Maker's ten Foundations colours. Partial objects are fine.

styling.setMapColors({ land: '#f3f5f7', water: '#accbe2', roadMajor: '#a1b8ce', label: '#364659' });

The ten are Map Maker's own, under its names and in its order, so a colour you set here is the one you set on the same control in Map Maker's Foundations. What each reaches:

Name Map Maker Reaches
land Land (base) Map background and other bare-land features, influences admin borders/labels. Also used to derive all map colours if undefined.
water Water Waterbodies, waterways and other water features. Also influences glaciers, water labels and ferry lines.
vegetation Vegetation Forests and other land cover such as grass and shrubland. Also influences earth cover vegetation at smaller scales.
park Park & Recreation City parks and sport/recreation grounds. Also influences National Parks and other protected areas at smaller scales.
artificial Artificial Built-up-areas and man-made features such as landuse grounds and buildings. Also influences railways.
roadMajor Major Road Most important road classes, from motorways & trunks to primary roads. Also influences outline and tunnel colours.
road Road Less important road classes, from secondary/tertiary to streets and service roads. Also influences tunnel colours.
roadOutline Road Outline Road outlines for secondary/tertiary roads and minor classes. Also influences road labels and all other outline colours.
label Label Labels for capitals and other populated places/admin areas. Also influences border styling and all other label colours.
labelOutline Label Outline Outlines for populated places and admin area labels. Also influences border outlines and all other label halos.

One name fans out to every layer and shade the style built from it. roadMajor recolours the motorway line, the trunk line one shade lighter, their outlines darker, the tunnel and bridge variants and the small-scale version — each re-derived from your colour by the same HSL offset the style used, so the result is a coherent recolour with the shade relationships preserved rather than a flat fill. The style is its own manifest: the offsets are measured off the loaded style, not shipped as a table, so the mechanism follows the style to its next version.

Land-use and building tints in a hue of their own — a hospital's pink, a campus's cream, a shopping area's yellow — are not shades of any of the ten. They keep their hue and follow artificial's lightness, and their saturation scales with its luminance the way the style's theme derives them, so a dark built-up colour darkens and mutes them too — whatever its own hue or saturation, a grey included.

The mono styles set only land and derive the other nine from it, the way their theme compiles them. On those, land alone recolours the whole map with the theme's own formulas, a colour you set outright still wins, and a road you set carries its outline and major class with it. The other styles set all ten and derive nothing.

await map.setStyle('monoLight');
styling.setMapColors({ land: 'hsl(38, 36%, 93%)' }); // a sepia map: water, roads and land cover follow

The ten colours are also knobs (colors.water, …), so get('colors.water') reads the style's own base colour — or the one derived from your land — until one is set, the catalogue carries the summary above as each description, reset('colors.roadMajor') restores one, and getConfig() carries the colours along with the rest of the settings.

A palette is a recolour, not an inversion. Because every literal is re-derived by its offset from the style's own colour, a palette that stays near the loaded style's lightness lands cleanly, while one that inverts it — dark land over standardLight — pushes the derived shades to the ends of their range and washes the road hierarchy out. Pair a dark palette with standardDark, whose shades already run dark, and recolour from there:

await map.setStyle('standardDark');
styling.setMapColors({ land: '#161b27', water: '#2c486d', roadMajor: '#b68e54', label: '#dce0e5' });

Fidelity bar: a coherent recolour with preserved shade relationships — visually faithful for reasonable palettes, approximate for extreme ones. The ten colours a style derives from land are the theme's exactly; the shades within each are re-derived from the loaded style, so pixel parity with a compiled theme is not the goal.

From a few colours, a stylesheet or an image

When the look comes as a brand's swatches, a website's CSS, a photo or a mood rather than ten named colours, the Map Theme plugin assigns the roles for you:

styling.setMapColors(deriveMapColors(['#1d3f8a', '#e63312', '#f5d000', '#111111', '#f4f1ea']));
styling.setMapColors(deriveMapColorsFromCss(await cssFile.text()));
styling.setMapColors(await deriveMapColorsFromImage(fileInput.files[0]));

The accent: what a theme reaches beyond the base map

colors.accent is the colour for what is drawn on top of the map. It is a knob like the other colours — get, set, reset and getConfig() carry it — but it paints no style layer.

It is published on the map as map.mapColors, and the modules that draw their own layers read it as their default, so one theme recolours the base map and the routes, areas and pins drawn on it:

import { RoutingModule, StylingModule } from '@tomtom-org/maps-sdk/map';
import { deriveMapColors } from '@tomtom-org/maps-sdk-plugin-map-theme';

const styling = await StylingModule.get(map);
const routes = await RoutingModule.create(map); // no colour chosen
styling.setMapColors(deriveMapColors(['#1d3f8a', '#e63312', '#f5d000', '#111111', '#f4f1ea']));
// the base map is Miró, and so is the route drawn across it
map.mapColors.accent; // the colour the route took
Module What takes the accent Your own colour instead
RoutingModule the route line, its outline and the waypoint icons color, or a layers paint override for one layer
GeometriesModule the fill and the outline color, fill.color or fill.palette for both, line.color for the outline, a feature's color property
PlacesModule the pin, the selected label, connection lines and entry points color for all four; over it, icon.default.style.fillColor for the pin, label.color or layers.selected for the label, connections.layers and entryPoints.layers for the rest
ReachableRangesModule its bands, as a ramp from the accent (innermost) toward the map's land colour (outermost), reversed for fillStyle: 'inverted' palette

A colour the caller chose always wins — routes.applyConfig({ color: '#c8102e' }) stays #c8102e through every theme change. The modules given none follow the accent live, and without an accent each keeps its own default.

To theme the base map alone, leave the accent out, or reset it; the modules go back to their own defaults:

const { accent, ...baseMapColors } = deriveMapColors(['#1d3f8a', '#e63312', '#f5d000', '#111111', '#f4f1ea']);
styling.setMapColors(baseMapColors);

// or, with an accent already set:
styling.reset('colors.accent');

Only those four modules read it. CustomGeoJSONModule, TrafficAreaAnalyticsModule, the flyover plugin's marker and your own MapLibre layers draw the colours you give them, TrafficIncidentDetailsModule draws the style's incident colours (Borrowed looks), and base-map POI icons and the places cluster badges keep theirs (cluster.badge.color sets a badge's). To match the theme, read map.mapColors.accent whenever the settings change:

styling.events.on('config-change', () =>
    map.mapLibreMap.setPaintProperty('my-trail', 'line-color', map.mapColors.accent ?? '#0a3653'),
);

The Map Theme plugin's deriveMapColors returns the accent, picked to read on the land. No knob writes a module's layers: the accent is one more input the module reads (Who owns what). The demo below themes the base map, an area, pins and a route in one call:

Borrowed looks

A few parts the modules draw are made to look like part of the style, so they read their look off it. They follow the knobs that restyle what they borrow, live, whether the knob was set before the module was created or after, and across a setStyle:

Module What it borrows The knobs it follows Your own look instead
PlacesModule, base-map marker type the POI labels and icons pois.labelColor, pois.labelOutlineColor, labels.sizeFactor, symbols.sizeFactor, pois.sizeFactor label, or a layers override
PlacesModule, circle-icon marker type the icon size symbols.sizeFactor, pois.sizeFactor a layers override
PlacesModule, pin-clustered marker type the micro markers symbols.sizeFactor, pois.sizeFactor a layers override
RoutingModule the delay colour of a traffic section traffic.incidents.minorColor, moderateColor, majorColor a line-color under layers.sections.traffic.routeIncidentBackgroundLine
TrafficIncidentDetailsModule the line colour of each incident magnitude traffic.incidents.minorColor, moderateColor, majorColor, closedColor none: the module always draws the style's incident colours
const places = await PlacesModule.create(map, { markerType: 'base-map' });
styling.set('pois.labelColor', '#be185d'); // the places' labels turn with the map's POIs

A look of your own wins over the knob. With none of its knobs set, a route keeps the SDK's own delay colours, which match the incidents of the light styles. The pin marker type borrows nothing, and no knob reaches it. The knob still writes only the style's layers: the module reads the new look and redraws its own (Who owns what).

Exporting the style

exportStyle() returns the map's style as currently rendered — the loaded style with every knob applied — as a MapLibre style specification. Save it, diff it, serve it yourself, or load it back as a custom style. Tile URLs carry the API key the map was created with; strip or rotate it before publishing the file.

Settings: persist, share, re-apply

getConfig() returns the overridden knobs as a plain object; applyConfig() takes one back. It serializes cleanly, so styling can live in local storage, a URL or a pull request, and re-applying it on a later SDK version keeps working for as long as the knob ids do:

localStorage.setItem('map-styling', JSON.stringify(styling.getConfig()));
// …later
styling.applyConfig(JSON.parse(localStorage.getItem('map-styling')));

styling.events.on('config-change', (settings) => save(settings));

Style changes

The style (style on the map, or setStyle) is which map document loads; knobs adjust whichever one is loaded. Settings survive map.setStyle(...): the module re-applies them once the new style has loaded, after the other modules have restored themselves, so knobs always land on top. A clean switch (resetState: true) drops them along with the rest of the SDK state. See Map styles.

How durable is this?

Two kinds of knobs, told apart by appliesTo in the catalogue:

That is the contract: one call the SDK re-verifies on every style release, instead of a setPaintProperty block against layer ids that nobody tests again. Which style version the SDK verified against is our business, not yours — appliesTo tells you which knobs depend on it.

Interplay with other modules

The map modules split along one line: what is shown versus how the style draws it — the rule is stated once, in Who owns what. In short:

The knobs live in one module because many reach across style parts — labels.sizeFactor, or a map colour that reaches land, water, roads and labels at once — and because a control panel or an agent wants one catalogue and one settings object for the whole look.

The two sides meet in three places, each described in Who owns what: the base-map group toggles, which write through BaseMapModule so that the later call wins; the toggles inside a module's layers (pois.microMarkers, roads.arrows and the like), which can only hide what that module shows; and pois.zoomShift, which composes with the POIsModule filters.categories setting.

The advanced tier: styling.layers

For what the knobs do not cover, styling.layers.query(...) selects style layers by the base-map group taxonomy, layer type, source layer or id fragment — every constraint given must hold, so the others narrow a group — and edits their paint, layout, filter or visibility directly:

styling.layers.query({ group: 'roadLabels' }).setPaint({ 'text-color': '#93c5fd' });
styling.layers.query({ metadataGroups: ['water'], layerTypes: ['fill'] }).setPaint({ 'fill-opacity': 0.6 });
styling.layers.query({ idIncludes: ['railway'] }).setVisible(false);
styling.layers.query({ group: 'roadLabels' }).reset();

Edits are re-applied after a style switch for as long as the layers still exist, and land on top of the knobs. setFilter replaces the filter the style gave a layer, and the filters other modules add, such as POI categories, still narrow it. This is the version-coupled tier, framed the way Map Maker frames raw layer editing: layer ids, property expressions and even which layers exist belong to the style version the map loads. A query that matches nothing warns once and does nothing. Edits are not part of getConfig() — they are edits to this style, not portable settings. Prefer a knob or setMapColors wherever one covers the intent, and map.mapLibreMap remains available below all of this (which to use when), though setPaintProperty on a style layer there is not re-applied after a style switch.

API reference

For complete documentation see the StylingModule API Reference.

Related guides and examples

Related guides

Related examples