Map
The built-in map tag — an embedded OpenFreeMap / MapLibre map with one pin per location. Pins take a street address (geocoded at build time, no API key) or explicit coordinates. Usage, options, and a live example.
A built-in tag that embeds an interactive map with a marker for each location. It renders OpenFreeMap vector tiles with MapLibre GL JS — both free and keyless — so a map just works with nothing to sign up for.
Aardvark uses the audited mantine-map package as an internal implementation dependency;
it does not expose a second community tag. {% map %} is the only map
authoring surface. You install nothing for it — Aardvark ships that runtime and stages it
into your build cache — except on the released 0.3.3, which reads mantine-map from your
own node_modules; the On 0.3.3 you do install it note further down covers the install — and
Aardvark publishes its content-hashed worker locally.
The executable map engine therefore loads from your own site rather than a JavaScript CDN.
The selected remote style, tiles, glyphs, and sprites are provider data, not executable CDN
runtime.
Drop a pin by street address and the address is turned into coordinates once, at
build time, by the free Nominatim (OpenStreetMap) geocoder —
cached so a rebuild never re-looks-up an address it already knows. Prefer to be exact
(or build offline)? Give a pin explicit lat / lng and no geocoding happens at all.
Usage
Wrap the map in {% map %} … {% endMap %} and give each location
its own self-closing {% pin %}. The simplest form is an address
and a label:
{% map %}
{% pin address="British Museum, London" label="British Museum" %}
{% pin address="Tower of London, London" label="Tower of London" %}
{% pin address="Buckingham Palace, London" label="Buckingham Palace" %}
{% endMap %}
At build time each address is geocoded and baked into the page as coordinates, so the
reader’s browser only loads the basemap and drops the markers — it never geocodes.
Live example
The same three London landmarks, pinned by explicit coordinates so the page needs no
network when it builds — the source below and the map it renders match exactly. With no
center or zoom set, the map frames all the pins automatically:
{% map height=420 %}
{% pin lat=51.5194 lng=-0.1270 label="British Museum" description="Great Russell St" %}
{% pin lat=51.5081 lng=-0.0759 label="Tower of London" description="A royal fortress since 1066" %}
{% pin lat=51.5014 lng=-0.1419 label="Buckingham Palace" color="#c2255c" %}
{% endMap %}
renders, live:
- British Museum — Great Russell St
- Tower of London — A royal fortress since 1066
- Buckingham Palace
Click a marker for its popup. The map keeps MapLibre | OpenFreeMap © OpenMapTiles | © OpenStreetMap contributors attribution in the corner — that crediting is required, so don’t remove it.
Pinning a location
A {% pin %} is placed one of two ways:
- By address —
{% pin address="350 Fifth Ave, New York, NY" %}. Geocoded at build time (cached). Easiest to author; needs a network connection the first time it’s built. - By coordinates —
{% pin lat=40.7484 lng=-73.9857 %}. Exact, reproducible, and never touches the network. Use this for precision, for places a geocoder won’t find, or to keep a build fully offline.
An address that can’t be resolved (typo, or an offline build with a cold cache) is skipped with a build warning rather than failing the build — so one bad address never breaks your docs.
Options
Every {% map %} attribute is optional:
{% map %} attribute |
Effect |
|---|---|
style="liberty" |
Basemap style: liberty (default), bright, positron, or a full MapLibre style URL. |
zoom=12 |
Initial zoom level. Omit to auto-fit the pins. |
center="51.50,-0.12" |
Center as "lat,lng" — or an address. Omit to auto-fit the pins. |
height=420 |
Map height in pixels (default 400). |
interactive=false |
Lock the map (no pan/zoom, no zoom buttons) for a static locator. |
Each {% pin %} takes:
{% pin %} attribute |
Effect |
|---|---|
address="…" |
Street address, geocoded at build time. Use this or lat/lng. |
lat=… lng=… |
Explicit coordinates (skips geocoding). lon is accepted as an alias for lng. |
label="…" |
Bold heading in the marker’s popup (and the location’s name in the fallback list). |
description="…" |
A line of detail below the label in the popup. |
color="#c2255c" |
Marker color (any CSS color). |
Geocoding, privacy & offline builds
Geocoding runs only at build time and only for address pins — the published page
contains coordinates, never an address lookup. Results are cached under
.aardvark-cache/ (git-ignored), so addresses are resolved once and reused on every
later build.
Forcing a re-lookup
Each geocoded address is cached as one small JSON file under .aardvark-cache/geo/,
named by a hash of the address (its contents are the resolved lat / lng and place
name). The cache is purely derived and git-ignored, so clearing it is always safe — it
just costs a fresh lookup on the next build. To drop every cached coordinate and
re-resolve on the next build:
rm -rf .aardvark-cache/geo
To drop a single entry — say the geocoder placed a pin in the wrong spot and you want it looked up again — the filenames are hashed, so match the file by the (wrong) coordinate you saw on the map, then delete it and rebuild:
grep -rl 43.6532 .aardvark-cache/geo | xargs rm
(To skip geocoding for a location altogether, give its pin explicit lat / lng.)
The default geocoder is Nominatim’s public server, used within its
usage policy: one request
per second, a descriptive User-Agent, and aggressive caching. For heavier use, switch to
Google — all via the optional map block in aardvark.config.yaml:
map:
geocoder: nominatim # default; or "google"
# googleApiKey: "…" # required when geocoder: google
# rateLimit: 1.0 # requests per second
# timeout: 10 # seconds per geocoder request
style: liberty # default basemap
mantine-map and MapLibre GL JS are shipped by Aardvark, not by your site. The first build
that renders a map extracts them into .aardvark-cache/ (nothing is downloaded, and the files
are checked against digests recorded when they were vendored), then bundles that runtime and
CSS and emits a self-contained, content-hashed worker under /_aardvark/maplibre/. There is
nothing to install and no runtime JavaScript or CSS CDN dependency to configure — except on
the released 0.3.3, which the next paragraph covers.
On 0.3.3 you do install it. Shipping the runtime lands in the release after 0.3.3; 0.3.3
itself bundles the copy in your own node_modules. A site scaffolded by 0.3.3 already lists
"mantine-map": "0.4.0" in package.json, so its ordinary npm install covers it; a site
upgrading from an earlier release adds that entry itself and then actually installs it — the
same installed-tree rule as below, where a listed but uninstalled package does not count. Without that copy the build warns, drops
the map and renders the location list below — and still exits 0, so a map can disappear from a
published site unnoticed.
A project with its own installed mantine-map keeps using that copy — Aardvark stages nothing
in that case, and the version resolved from your node_modules is the one that gets bundled.
It is the installed tree that decides, not the manifest: a package listed but not installed is
not a copy Aardvark can build against.
That copy still has to be exactly 0.4.0, the version the map surface is built against. If yours is a different version the map is dropped with a warning, as it was before — Aardvark does not quietly substitute its own runtime for a dependency you installed deliberately. Remove your pin to hand the runtime back to Aardvark.
Content-Security-Policy: allow the local worker with worker-src 'self', and allow
MapLibre’s image forms with img-src 'self' data: blob:. The basemap style still names its
own remote style, tile, glyph, sprite, and image providers, so add those origins under
connect-src and img-src as appropriate. These responses are map data, not executable
runtime fetched from a CDN. The default OpenFreeMap styles use OpenFreeMap-hosted resources.
(Cloud hosts and WAFs sometimes set a CSP for you — check there if a map renders locally but
loses its basemap after deployment.)
No JavaScript? A reader (or a search crawler) without the map still gets an accessible list of the pinned locations, each linking to its spot on OpenStreetMap — the same list screen readers use.
CSS Selectors
The map mounts inside an island wrapper carrying data-aardvark-island="Map" and renders its own class names — target the container, the MapLibre canvas, and the no-JavaScript fallback list.
[data-aardvark-island="Map"] /* the island wrapper */
.aardvark-map /* the map container */
.aardvark-map-canvas /* the MapLibre canvas */
.aardvark-map-fallback /* the no-JS location list */
Injecting Attributes
attr={…} forwards raw HTML attributes — id, data-*, ARIA, analytics hooks — onto the
rendered map root. (Style it through the CSS parts above, and configure the map with the
documented attributes — style, zoom, center, height, interactive — plus each
{% pin %}.)
{% map height=420 attr={'data-analytics': 'office-map', 'aria-label': 'London landmarks'} %}
{% pin lat=51.5194 lng=-0.1270 label="British Museum" %}
{% pin lat=51.5081 lng=-0.0759 label="Tower of London" %}
{% pin lat=51.5014 lng=-0.1419 label="Buckingham Palace" %}
{% endMap %}
component('aardvark', 'map', height=420,
attr={'data-analytics': 'office-map', 'aria-label': 'London landmarks'}, children='''
{% pin lat=51.5194 lng=-0.1270 label="British Museum" %}
{% pin lat=51.5081 lng=-0.0759 label="Tower of London" %}
{% pin lat=51.5014 lng=-0.1419 label="Buckingham Palace" %}
''')