Vector map
A vector map draws countries as SVG paths, shaded by a value, with markers, lines, tooltips and zoom. Tabler renders it from data attributes, with no map library and no map provider.
Overview
A vector map draws countries as SVG paths, so it stays sharp at any size. Use it to show a number per country, a few places, or routes between them.
The component is Tabler's own. It needs no map library, no API key and no network request. The country shapes come from Natural Earth, which is public domain.
Installation
The vector map is not part of tabler.js, so pages without a map do not load it. Add two files after tabler.js: the component and the map data.
<script src="https://cdn.jsdelivr.net/npm/@tabler/[email protected]/dist/js/tabler.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@tabler/[email protected]/dist/js/tabler-vector-map.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@tabler/[email protected]/dist/js/maps/world.js"></script>
The styles are part of tabler.css.
With a bundler, import the component and the map, then register the map under a name:
import { VectorMap } from '@tabler/core/dist/js/tabler-vector-map.esm.js';
import { world } from '@tabler/core/dist/js/maps/world.esm.js';
VectorMap.addMap('world', world);
Default markup
Add class="vector-map" and data-bs-toggle="vector-map" to an empty element. Tabler draws the map on page load. The map is as wide as its parent and keeps its own proportions, so it needs no height.
Set data-bs-label to a short text that says what the map shows. Without it the map is hidden from screen readers.
<div class="vector-map" data-bs-toggle="vector-map" data-bs-label="World map"></div>Values
Pass a number per country in data-bs-values, as a JSON object. The keys are ISO 3166-1 alpha-2 codes, such as PL or US. The country with the highest value gets the full color and the others get a lighter shade.
<div class="vector-map" data-bs-toggle="vector-map" data-bs-label="Visits by country" data-bs-values="{"US": 100, "CA": 62, "BR": 48, "GB": 71, "DE": 80, "FR": 66, "PL": 54, "IN": 90, "CN": 85, "JP": 59, "AU": 40, "ZA": 22, "EG": 18, "MX": 35, "AR": 27}"></div>The shades are spread between the lowest and the highest value. Set data-bs-min and data-bs-max to use a fixed range instead, for example 0 and 100 for a percentage.
Color
The map uses the primary color. Set --tblr-vector-map-color to change it.
<div class="vector-map" data-bs-toggle="vector-map" data-bs-label="Growth by country" data-bs-min="0" data-bs-values="{"US": 4, "CA": 7, "BR": 9, "DE": 3, "PL": 8, "IN": 10, "CN": 6, "AU": 5}" style="--tblr-vector-map-color: var(--tblr-green)"></div>Color scale
One color shows how much. A scale of several colors also shows good and bad, or cold and hot. Pass two to five colors in data-bs-colors, as a JSON array. The first color is for the lowest value and the last one for the highest. The values between them get a mix of the two nearest colors.
<div class="vector-map" data-bs-toggle="vector-map" data-bs-label="Customer satisfaction by country" data-bs-min="0" data-bs-max="100" data-bs-colors="["var(--tblr-red)", "var(--tblr-yellow)", "var(--tblr-green)"]" data-bs-values="{"US": 82, "CA": 91, "MX": 48, "BR": 35, "AR": 22, "GB": 74, "DE": 96, "FR": 63, "ES": 55, "PL": 88, "RU": 12, "IN": 41, "CN": 58, "JP": 79, "AU": 100, "ZA": 28, "EG": 6, "NG": 17, "SA": 67, "ID": 45}"></div>Any CSS color works, also a Tabler color such as var(--tblr-red). The browser mixes the colors with color-mix(), so they follow the theme and you can change them later with CSS.
The plugin writes the colors to --tblr-vector-map-color-1, --tblr-vector-map-color-2 and so on, and their number to --tblr-vector-map-colors. To set a scale without JavaScript options, add the vector-map-colors class and these properties yourself:
<div class="vector-map vector-map-colors" data-bs-toggle="vector-map" style="--tblr-vector-map-colors: 2; --tblr-vector-map-color-1: var(--tblr-blue); --tblr-vector-map-color-2: var(--tblr-red)"></div>
With a scale, the lowest value gets the first color in full, so --tblr-vector-map-value-min is not used. Markers keep the color from --tblr-vector-map-color.
Legend
Colors alone do not say how much. Add data-bs-legend="true" to show the scale under the map, as a bar between the lowest and the highest value. The bar uses the same colors as the map, also with a scale of several colors.
<div class="vector-map" data-bs-toggle="vector-map" data-bs-legend="true" data-bs-label="Customer satisfaction by country" data-bs-min="0" data-bs-max="100" data-bs-colors="["var(--tblr-red)", "var(--tblr-yellow)", "var(--tblr-green)"]" data-bs-values="{"US": 82, "CA": 91, "MX": 48, "BR": 35, "AR": 22, "GB": 74, "DE": 96, "FR": 63, "ES": 55, "PL": 88, "RU": 12, "IN": 41, "CN": 58, "JP": 79, "AU": 100, "ZA": 28, "EG": 6, "NG": 17, "SA": 67, "ID": 45}"></div>The two numbers are the range of the scale: min and max when you set them, the lowest and the highest value otherwise. Pass a function from JavaScript to add a unit or to format the numbers:
new tabler.VectorMap('#satisfaction', {
values: { PL: 88, DE: 96 },
legend: (value) => `${value}%`,
});
Set the size of the bar with --tblr-vector-map-legend-width and --tblr-vector-map-legend-height.
Markers
Markers point at single places, such as offices or customers. Pass them in data-bs-markers as a JSON array. Each marker has a lat, a lng and an optional name. A marker keeps its size when the map is resized or zoomed.
<div class="vector-map" data-bs-toggle="vector-map" data-bs-label="Our offices" data-bs-markers="[{"name": "Warsaw", "lat": 52.23, "lng": 21.01}, {"name": "New York", "lat": 40.71, "lng": -74.01}, {"name": "Tokyo", "lat": 35.68, "lng": 139.69}, {"name": "Sydney", "lat": -33.87, "lng": 151.21}]" style="--tblr-vector-map-color: var(--tblr-red)"></div>Lines
Lines join two markers, for a route or a connection. Pass them in data-bs-lines as a JSON array. from and to are the names of two markers. The dashes move from the start of the line to its end, and stand still for users who ask for reduced motion.
<div class="vector-map" data-bs-toggle="vector-map" data-bs-label="Flights from Warsaw" data-bs-markers="[{"name": "Warsaw", "lat": 52.23, "lng": 21.01}, {"name": "New York", "lat": 40.71, "lng": -74.01}, {"name": "Tokyo", "lat": 35.68, "lng": 139.69}, {"name": "Cape Town", "lat": -33.92, "lng": 18.42}]" data-bs-lines="[{"from": "Warsaw", "to": "New York"}, {"from": "Warsaw", "to": "Tokyo"}, {"from": "Warsaw", "to": "Cape Town"}]"></div>Zoom
Add data-bs-zoom="true" to let users look closer. The map gets two buttons, and reacts to the mouse wheel, a double click, a pinch and a drag.
<div class="vector-map" data-bs-toggle="vector-map" data-bs-zoom="true" data-bs-label="Visits in Europe" data-bs-values="{"PL": 8, "DE": 6, "FR": 5, "ES": 3, "IT": 4, "GB": 7, "SE": 2, "UA": 3, "RO": 2, "CZ": 5}"></div>While the pointer is over a zoomable map, the wheel zooms the map and does not scroll the page. Set data-bs-zoom-on-scroll="false" to keep the wheel for the page. On a touch screen a finger on the map moves the map, not the page, so leave some room around a large map.
Selecting countries
Set data-bs-select to let users pick countries with a click. With single a click on another country moves the selection. With multiple every click adds a country. A click on a selected country takes it out again. Pass the countries that start selected in data-bs-selected.
<div class="vector-map" data-bs-toggle="vector-map" data-bs-select="multiple" data-bs-selected="["PL", "FR", "BR"]" data-bs-label="Countries to compare"></div>A selected country gets an outline and keeps the color of its value. Change the outline with --tblr-vector-map-selected-stroke and --tblr-vector-map-selected-stroke-width.
The map fires selected.bs.vector-map after every change. Read the selection from the event, or with getSelected():
const element = document.getElementById('compare');
element.addEventListener('selected.bs.vector-map', (event) => {
console.log(event.selected); // ['PL', 'FR', 'BR', 'DE']
});
tabler.VectorMap.getInstance(element).setSelected(['US', 'CA']);
Clicks
The map fires regionclick.bs.vector-map when a country is clicked and markerclick.bs.vector-map when a marker is clicked, also when nothing can be selected. Use them to open a page or to filter a table. A drag on a zoomable map is not a click.
element.addEventListener('regionclick.bs.vector-map', (event) => {
console.log(event.code, event.name, event.value); // 'PL', 'Poland', 54
});
element.addEventListener('markerclick.bs.vector-map', (event) => {
console.log(event.name, event.lat, event.lng);
});
Call event.preventDefault() in regionclick.bs.vector-map to keep that click from changing the selection.
Tooltip
A tooltip shows the name of the country or marker under the pointer, with the value when the country has one. Set data-bs-tooltip="false" to turn it off.
Pass a function from JavaScript to write your own text. It gets the item under the pointer and returns a string. The string is shown as plain text. Return an empty string to show nothing.
new tabler.VectorMap('#visits', {
values: { PL: 54, DE: 80 },
tooltip: (item) => (item.type === 'region' && item.value !== undefined ? `${item.name}: ${item.value}%` : item.name),
});
Options
Set options with data attributes or pass them to the constructor. For a data attribute, add data-bs- to the name and write it in kebab case, for example data-bs-zoom-max.
| Name | Type | Default | Description |
|---|---|---|---|
map |
string, object | 'world' |
Name of a loaded map, or the map data itself. |
values |
object | {} |
A number per region code. |
min |
number, null | null |
Value that gets the lightest shade. The lowest value when null. |
max |
number, null | null |
Value that gets the full color. The highest value when null. |
colors |
array | [] |
Two to five colors of a scale, from the lowest value to the highest. |
markers |
array | [] |
Places as { name, lat, lng }. |
lines |
array | [] |
Connections as { from, to }, by marker name. |
tooltip |
boolean, function | true |
Shows a tooltip, or builds its text. |
legend |
boolean, function | false |
Shows the scale under the map, or formats its two numbers. |
select |
string | 'none' |
Lets users select countries: 'none', 'single' or 'multiple'. |
selected |
array | [] |
Codes of the countries that start selected. |
zoom |
boolean | false |
Lets users zoom and drag the map. |
zoomMax |
number | 8 |
The largest magnification. |
zoomOnScroll |
boolean | true |
Zooms with the mouse wheel. Needs zoom. |
zoomInLabel |
string | 'Zoom in' |
Accessible name of the zoom in button. |
zoomOutLabel |
string | 'Zoom out' |
Accessible name of the zoom out button. |
label |
string, null | null |
Accessible name of the map. |
JavaScript
Tabler wraps the element in a VectorMap component that works like the Bootstrap components: it is created once per element and stored on it. When the page is loaded, Tabler creates one for every element with data-bs-toggle="vector-map". This is the code that runs:
const init = (): void => {
initAll(SELECTOR_DATA_TOGGLE, VectorMap)
}
if (document.readyState === 'complete') {
init()
} else {
document.addEventListener('DOMContentLoaded', init, { once: true })
window.addEventListener('load', init, { once: true })
}Create the component yourself for an element added later, or to pass the data from code:
const visits = tabler.VectorMap.getOrCreateInstance(document.getElementById('visits'), {
values: { PL: 54, DE: 80, FR: 66 },
});
Call update() with new values to shade the map again. The map is not drawn again, so this is cheap enough for live data:
visits.update({ PL: 61, DE: 77, FR: 70 });
| Method | Description |
|---|---|
update(values) |
Shades the regions with new values. Fires updated.bs.vector-map. |
render() |
Reads the attributes again and draws the map from the start. Fires rendered.bs.vector-map. |
getSelected() |
Returns the codes of the selected countries. |
setSelected(codes) |
Selects exactly these countries. Fires selected.bs.vector-map. |
zoomIn() |
Zooms in one step, around the middle of the view. |
zoomOut() |
Zooms out one step. |
reset() |
Goes back to the whole map. |
dispose() |
Removes the map, the buttons, the legend, the tooltip and the component from the element. |
VectorMap.addMap(name, data) |
Static. Registers a map under a name. |
getInstance(element) |
Static. Returns the component for the element, or null. |
getOrCreateInstance(element, config) |
Static. Returns the component for the element and creates it when needed. |
| Event | Description |
|---|---|
rendered.bs.vector-map |
Fired on the element after every render. |
updated.bs.vector-map |
Fired on the element after update(). |
regionclick.bs.vector-map |
Fired when a country is clicked. Has code, name, value when the country has one, and the element in relatedTarget. Can be prevented. |
markerclick.bs.vector-map |
Fired when a marker is clicked. Has name, lat, lng and the element in relatedTarget. |
selected.bs.vector-map |
Fired after the selection changes. Has the codes in selected, and the clicked code when a click made the change. |
Styling a single country
Every country has its code in data-region, so one rule can color it. Set --tblr-vector-map-region-color, which works for a shape and for the dot of a small country:
.vector-map [data-region='PL'] {
--tblr-vector-map-region-color: var(--tblr-red);
}
Custom properties
The map reads its colors and sizes from custom properties on the element. Change them for one map with a style attribute, or for a section with a class.
| Property | Default | Description |
|---|---|---|
--tblr-vector-map-color |
var(--tblr-primary) |
Color of the highest value, and of the markers. |
--tblr-vector-map-region-fill |
var(--tblr-border-color) |
Fill of a country with no value. |
--tblr-vector-map-region-stroke |
var(--tblr-bg-surface) |
Border between countries. |
--tblr-vector-map-region-stroke-width |
1px |
Width of the border. |
--tblr-vector-map-value-min |
0.1 |
Share of the color the lowest value gets, from 0 to 1. |
--tblr-vector-map-color-1 to -5 |
none | Colors of a scale, from the lowest value to the highest. |
--tblr-vector-map-colors |
none | Number of colors in the scale. |
--tblr-vector-map-point-size |
0.375rem |
Diameter of the dot of a small country. |
--tblr-vector-map-selected-stroke |
var(--tblr-body-color) |
Outline of a selected country. |
--tblr-vector-map-selected-stroke-width |
2px |
Width of that outline. |
--tblr-vector-map-marker-size |
0.5rem |
Diameter of a marker. |
--tblr-vector-map-marker-color |
var(--tblr-vector-map-color) |
Color of a marker. |
--tblr-vector-map-marker-halo-color |
var(--tblr-bg-surface) |
Color of the ring around a marker and around the dot of a small country. |
--tblr-vector-map-marker-halo-width |
2px |
Width of the ring. |
--tblr-vector-map-line-color |
var(--tblr-secondary) |
Color of a line. |
--tblr-vector-map-line-width |
1px |
Width of a line. |
--tblr-vector-map-line-dash |
4px |
Length of a dash and of the gap after it. |
--tblr-vector-map-line-duration |
1s |
Time one dash takes to move to the place of the next. |
--tblr-vector-map-legend-width |
8rem |
Width of the bar of the legend. |
--tblr-vector-map-legend-height |
0.5rem |
Height of the bar. |
--tblr-vector-map-controls-offset |
var(--tblr-spacer-2) |
Distance of the zoom buttons from the corner. |
The world map
The world map has 207 countries in the Miller projection, without Antarctica. Values for a code that is not on the map are skipped.
Small countries
31 countries are too small to see as a shape on a map of the world, for example Andorra, San Marino, Malta or Singapore. The map shows such a country as a dot when it has a value. A dot works like any other country: it gets the same shade and has a tooltip. It keeps its size when the map is resized or zoomed.
<div class="vector-map" data-bs-toggle="vector-map" data-bs-zoom="true" data-bs-label="Visits from small countries" data-bs-values="{"AD": 4, "SM": 2, "MC": 5, "LI": 3, "VA": 1, "MT": 6, "SG": 9, "HK": 8, "BH": 5, "MU": 3}"></div>The dots are the sovereign states that have no shape on the map, plus Hong Kong and Macao. Dependent territories, such as Gibraltar or Guam, are not on the map. Set the size of a dot with --tblr-vector-map-point-size.
A small country with no value is hidden, so a map with a few markers does not fill up with grey dots. To show all of them, add this rule:
.vector-map-region-point:not([data-value]) {
display: inline;
}
Three territories have no ISO code. The map uses XK for Kosovo, XC for Northern Cyprus and XS for Somaliland. Borders follow the default view of Natural Earth.
Accessibility
- Set
data-bs-label. The map is then an image with that name. The countries themselves are not read out one by one. - The data behind the map has to be reachable another way. Put a table of the same values next to or below the map. It serves screen reader users and anyone who prints the page.
- The tooltip is for the pointer only. Do not put anything there that is not in the text version too.
- Shades of one color are hard to tell apart for many users. Turn on the legend, and for exact numbers add a table.
- Countries and markers can be clicked with a pointer only. They are not in the tab order, because a world map would be two hundred tab stops. When a click or a selection does something, offer the same choice in another control, such as a list of checkboxes or a select, and keep the two in step with
setSelected(). - The zoom buttons work with the keyboard and have accessible names. Change the names with
data-bs-zoom-in-labelanddata-bs-zoom-out-labelwhen the page is not in English.
Variables
$vector-map-color: var(--primary);
$vector-map-region-fill: var(--border-color);
$vector-map-region-stroke: var(--bg-surface);
$vector-map-region-stroke-width: 1px;
$vector-map-value-min: 0.1;
$vector-map-colors-max: 5;
$vector-map-point-size: 0.375rem;
$vector-map-selected-stroke: var(--body-color);
$vector-map-selected-stroke-width: 2px;
$vector-map-marker-size: 0.5rem;
$vector-map-marker-color: var(--vector-map-color);
$vector-map-marker-halo-color: var(--bg-surface);
$vector-map-marker-halo-width: 2px;
$vector-map-line-color: var(--secondary);
$vector-map-line-width: 1px;
$vector-map-line-dash: 4px;
$vector-map-line-duration: 1s;
$vector-map-controls-offset: var(--spacer-2);
$vector-map-legend-width: 8rem;
$vector-map-legend-height: 0.5rem;Migration notes
Before 1.7 this page used jsVectorMap. Its files are still in dist/libs/jsvectormap and its styles are still in tabler-vendors.css, so a page that uses it keeps working. The demo and the docs now use the component above.
Class reference
Every class this component ships, grouped by what it changes. A name in braces stands for a family — {color} is any base color, {breakpoint} any responsive step.
vector-map | component | Box that holds the map, as wide as its parent |
vector-map-svg | part | The rendered SVG, added by JavaScript |
vector-map-region | part | One country or region, with its code in data-region |
vector-map-region-point | part | A country too small for a shape, drawn as a dot when it has a value |
vector-map-region-point-dot | part | The dot, in the color of the region |
vector-map-region-point-halo | part | Ring around the dot, in the surface color |
vector-map-marker | part | Group that holds the two parts of a marker |
vector-map-marker-dot | part | The colored dot of a marker |
vector-map-marker-halo | part | Ring around the dot, in the surface color |
vector-map-line | part | Dashed curve between two markers |
vector-map-tooltip | part | Label that follows the pointer |
vector-map-controls | part | Zoom buttons in the corner of the map |
vector-map-legend | part | The scale under the map, two numbers around a bar |
vector-map-legend-label | part | A number at one end of the scale |
vector-map-legend-scale | part | The bar of the legend |
vector-map-legend-step | part | One slice of the bar, colored like a region with the same share |
vector-map-colors | style | Shades the values with a scale of several colors, set by JavaScript for data-bs-colors |
CSS
Variables
The component uses local CSS variables
on .vector-map for real-time customization. Values for the CSS variables are set via Sass, so Sass customization is still supported, too. Source: core/scss/ui/_vector-map.scss.
--vector-map-color: #{$vector-map-color};
--vector-map-region-fill: #{$vector-map-region-fill};
--vector-map-region-stroke: #{$vector-map-region-stroke};
--vector-map-region-stroke-width: #{$vector-map-region-stroke-width};
--vector-map-value-min: #{$vector-map-value-min};
--vector-map-point-size: #{$vector-map-point-size};
--vector-map-selected-stroke: #{$vector-map-selected-stroke};
--vector-map-selected-stroke-width: #{$vector-map-selected-stroke-width};
--vector-map-marker-size: #{$vector-map-marker-size};
--vector-map-marker-color: #{$vector-map-marker-color};
--vector-map-marker-halo-color: #{$vector-map-marker-halo-color};
--vector-map-marker-halo-width: #{$vector-map-marker-halo-width};
--vector-map-line-color: #{$vector-map-line-color};
--vector-map-line-width: #{$vector-map-line-width};
--vector-map-line-dash: #{$vector-map-line-dash};
--vector-map-line-duration: #{$vector-map-line-duration};
--vector-map-controls-offset: #{$vector-map-controls-offset};
--vector-map-legend-width: #{$vector-map-legend-width};
--vector-map-legend-height: #{$vector-map-legend-height};Sass variables
Override these variables before importing Tabler to change the defaults. Source: core/scss/_variables.scss.
$vector-map-color: var(--primary);
$vector-map-region-fill: var(--border-color);
$vector-map-region-stroke: var(--bg-surface);
$vector-map-region-stroke-width: 1px;
$vector-map-value-min: 0.1;
$vector-map-colors-max: 5;
$vector-map-point-size: 0.375rem;
$vector-map-selected-stroke: var(--body-color);
$vector-map-selected-stroke-width: 2px;
$vector-map-marker-size: 0.5rem;
$vector-map-marker-color: var(--vector-map-color);
$vector-map-marker-halo-color: var(--bg-surface);
$vector-map-marker-halo-width: 2px;
$vector-map-line-color: var(--secondary);
$vector-map-line-width: 1px;
$vector-map-line-dash: 4px;
$vector-map-line-duration: 1s;
$vector-map-controls-offset: var(--spacer-2);
$vector-map-legend-width: 8rem;
$vector-map-legend-height: 0.5rem;
