Get started
Show a map

Customize labels and icons

Customize labels and icons

Change three things: the glyphs the elements draw, the words they say, and the tokens they paint with. Set each on the provider, in CSS, or per element.

Swap the icon set

Every icon is a role, not a picture. A surface asks for action-copy and the provider answers with artwork, so swapping the provider swaps every mark.

import { createIconProvider } from "@mountaya/elements/icons";

const ourGlyphs = createIconProvider({
  markup: ourGlyphMarkup,
  name: "acme-ui",
  paint: "stroke",
});

Pass that to the provider's controlIcons prop. The path data stays in your tree, where your licence covers it. UI_GLYPHS is the closed vocabulary, so a misspelled role fails to compile rather than drawing an empty button.

There are three tables. controlIcons is the filled family map controls read over terrain. uiIcons is the outline family menu items are drawn from. markerIcons holds the places an itinerary names: a summit, a refuge, a water point. Painting one through another's prop draws a hairline silhouette or a blob rather than failing.

Lucide is the default. Supply your own and nothing else changes.

Override the words

English by default, overridable per key, and deep-merged, so a partial override stays partial:

const strings = { profile: { ascent: "Dénivelé positif" } };

Pass that to the provider's strings prop. React compares strings by content, so an inline object is safe. No translation runtime ships. Units are the exception: Intl.NumberFormat localizes a number and its unit together.

English is built in. Every other language the elements are translated into is an entry of its own, so a page that reads English loads no other words. For French, import frElementsStrings from @mountaya/elements/i18n/fr and pass translations={{ fr: frElementsStrings }} to the provider beside language="fr". A language asked for and not handed over reads in English, and says so once in development.

Every element that renders words also takes a strings of its own, scoped to what it draws:

<ZoomControl strings={{ controls: { zoomIn: "Closer" } }} />

ZoomControlStrings names controls and tooltips and nothing else. An override aimed at a section the control never renders is a type error. It folds onto whatever the provider resolved, and an element that mounts others passes the override down. To build your own surface from these parts, reach for useMountayaScopedStrings(sections, overrides) and ElementStringsScope.

The studio ships a fuller bundle, English built in and French an entry of its own:

import { resolveStudioStrings, studioLanguageOptions } from "@mountaya/studio/strings";
import { frStudioStrings } from "@mountaya/studio/strings/fr";

const translations = { fr: frStudioStrings };
const copy = resolveStudioStrings("fr", { segment: { none: "Aucune étape" } }, translations);
const pickerOptions = studioLanguageOptions(translations);

Overrides are a deep partial. studioLanguageOptions(translations) is what a language picker offers: English, then every language handed over. A studio host takes the same words as i18n.studioTranslations, beside the elements' i18n.translations.

Theme with CSS custom properties

The surface reads --mtya-* custom properties, a flat list of tokens. Set the ones you care about on the element roots, or once through tokens on the provider, and the rest keep their defaults:

.mtya-controls,
.mtya-search,
.mtya-profile {
  --mtya-accent: #e9501e;
  --mtya-panel-radius: 4px;
  --mtya-control-size: 32px;
}

For the dark scheme, set data-mtya-color-scheme="dark" on an ancestor.

Word an error

Everything these packages throw descends from MountayaElementsError. One catch tells a misuse of the SDK apart from a transport failure. A chain paints every glyph one way, so stacking a fill set on a stroke set throws:

import { MountayaElementsError } from "@mountaya/elements/errors";
import { createIconProvider, fallbackProvider, lucideIconProvider } from "@mountaya/elements/icons";

try {
  fallbackProvider(
    createIconProvider({ markup: ourGlyphMarkup, name: "acme-ui", paint: "fill" }),
    lucideIconProvider,
  );
} catch (error) {
  if (error instanceof MountayaElementsError) {
    reportMisuse(error);
  }
}

instanceof compares constructor identity, so two resolved copies of a package answer false for the same shape. Inside a package that peers this one, read the error structurally instead.

A tool dispatch never throws for a refusal. It answers a structured result with a code, and the sentence is yours to write.

Next steps