Customising the look
The look has four layers, from broadest to narrowest:
- Brand colours. Give
brandTheme()the colours from your style guide and it returns a theme and the colours to go with it. - Theme. One of
daylight,midnight,paperormono. - Colour tokens. Override any of the 36 tokens on top of the theme.
- CSS. Restyle the controls, attribution and cards like any other part of your page.
The server reads layers 1 to 3 too, so the first image is painted in your colours.
Brand colours from your style guide
Section titled “Brand colours from your style guide”brandTheme() turns a handful of colours into a whole map palette: land, water, parks, roads, labels, markers and clusters. Only primary is required.
import { brandTheme } from "instantmap-js"; // or "instantmap-js/server", or "instantmap-js/brand"
const brand = brandTheme({ primary: "#e11d48", background: "#fff7ed", foreground: "#1c1917" });// { theme: "daylight", colors: { marker: "#…", cluster: "#…", land: "#…", … } }| Input | Used for | Default |
|---|---|---|
primary (required) |
Markers, clusters and highways | |
background |
The page colour. Land is a tint of it. | Near-white, or near-black when mode is "dark" |
foreground |
Text colour. Labels and outlines are mixed from it. | Dark ink, or light ink on a dark background |
accent |
The selected marker | The foreground colour |
water |
Water | A quiet blue leaning toward primary |
park |
Parks and forests | A quiet green |
mode |
"light" or "dark" |
Decided from background |
tint |
How strongly primary tints land, water and roads, from 0 (neutral) to 1 |
0.5 |
Colours can be #rgb, #rrggbb, rgb(), hsl(), or the bare h s% l% triplets that shadcn-style tokens use, such as 222.2 47.4% 11.2%.
The result is { theme, colors }. It builds on daylight (or midnight for dark brands) and lists only the colours that differ. Label and marker text colours are chosen so they stay readable against the colour behind them.
Spread it wherever you give a theme and colours:
import { staticUrl } from "instantmap-js/server";
const src = staticUrl({ center: [-122.42, 37.77], zoom: 12, width: 960, height: 480, ratio: 2, markers, ...brand });<InstantMap center={[-122.42, 37.77]} zoom={12} markers={markers} {...brand} />import { posterHtml } from "instantmap-js/server";
const html = posterHtml({ center: [-122.42, 37.77], zoom: 12, width: 960, height: 480, ...brand });map.theme = brand.theme;map.colors = brand.colors;Plain tokens
Section titled “Plain tokens”If your style guide is a list of hex colours, pass them straight in:
const brand = brandTheme({ primary: "#0f766e", background: "#f0fdfa", foreground: "#134e4a", accent: "#f59e0b", tint: 0.7,});Tailwind and shadcn CSS variables
Section titled “Tailwind and shadcn CSS variables”shadcn/ui and many Tailwind setups keep colours as CSS variables such as --primary, --background and --foreground.
On the server, pass the same values you put in your stylesheet. Bare triplets work as they are. For oklch() values, convert them to hex, rgb() or hsl() first, because the server reads only those.
const brand = brandTheme({ primary: "222.2 47.4% 11.2%", background: "0 0% 100%", foreground: "222.2 84% 4.9%",});In the browser, brandThemeFromCss() reads the variables for you. It accepts any CSS colour syntax, including oklch().
import { brandThemeFromCss } from "instantmap-js";
const brand = brandThemeFromCss({ primary: "--primary", background: "--background", foreground: "--foreground" });map.theme = brand.theme;map.colors = brand.colors;Its second argument is optional: { element, mode, tint }. element is where the variables are read (default <html>), which is useful for a section that overrides them. It throws if primary is not set.
A design-token file
Section titled “A design-token file”If your tokens live in a JSON file, read them with your own code and pass the values in:
import tokens from "./design-tokens.json";import { brandTheme } from "instantmap-js/server";
const brand = brandTheme({ primary: tokens.color.brand.primary, background: tokens.color.surface.page, foreground: tokens.color.text.default,});The shape of your file will differ. Only the values matter: any colour in one of the syntaxes above.
Pick a theme
Section titled “Pick a theme”If you do not need brand colours, use a built-in theme (daylight, midnight, paper or mono) and override a few tokens. Most brands need four: the marker pill, its text, the selected state and clusters.
<instant-map theme="daylight" colors="marker:ffffff,markerText:111827,selected:ff385c,cluster:ff385c,accent:ff385c"></instant-map>The colors attribute is a comma-separated list of token:hex pairs, without the #. Tokens are written in alphabetical order when the library builds the value for you, so the same colours always give the same address.
Dark mode that follows the page
Section titled “Dark mode that follows the page”const media = matchMedia("(prefers-color-scheme: dark)");const apply = () => (map.theme = media.matches ? "midnight" : "daylight");media.addEventListener("change", apply);apply();On the server, pick the theme for the first image from the visitor’s Sec-CH-Prefers-Color-Scheme header, or from a cookie you set. For a brand with both a light and a dark set of colours, build two brandTheme() results and switch between them the same way.
Quieter maps for data
Section titled “Quieter maps for data”Use mono when your markers carry the meaning and the map is only context:
<instant-map theme="mono" colors="accent:2563eb,cluster:2563eb,selected:2563eb"></instant-map>Read the resolved colours
Section titled “Read the resolved colours”To see every colour a theme and your overrides produce, call resolvePalette(). The map element has no palette property.
import { resolvePalette } from "instantmap-js"; // or "instantmap-js/server"
const palette = resolvePalette(map.theme, map.colors);palette.water; // "#…"Restyle the controls
Section titled “Restyle the controls”instant-map::part(controls) { top: auto; bottom: 28px; right: 16px; border-radius: 999px;}instant-map::part(attribution) { font-size: 10px;}Restyle the card
Section titled “Restyle the card”The built-in card lives in your page (light DOM). Set a few --im-card-* variables on instant-map to change its radius, colours, font and photo shape, or use the .instant-map-card class names for anything else:
instant-map { --im-card-radius: 4px; --im-card-accent: #c2410c; --im-card-font: "Inter", system-ui, sans-serif;}The full list is in Cards and popups. To replace the card entirely, use a template or your own popup.
Keep the first image in sync
Section titled “Keep the first image in sync”Pass the same theme and colors to staticUrl() (or the React component’s props) that you give the element. Otherwise the visitor sees the colours change at the hand-off. Spreading one brandTheme() result into both is the simplest way.
Contrast checklist
Section titled “Contrast checklist”brandTheme()keeps label and marker text readable for you. If you set tokens by hand, keepmarkerTextonmarkerat 4.5:1 or better. Prices are small text.- Keep
labelagainstlandreadable at 12 px. - Don’t use colour alone to carry meaning. A selected marker also grows and gets a stronger shadow.