Get started
Get started

Install and set up

Install and set up

Every page in this section starts here. Install what you need, import one stylesheet, mount one provider.

Install the packages

npm install @mountaya/elements-react @mountaya/elements @mountaya/client maplibre-gl@6.16.0 react

That line is a map in React. Add or drop packages for what you are building:

What you are buildingPackages
A server, a worker, or a script@mountaya/client
A React application with no map@mountaya/client-react, @mountaya/client, and @tanstack/react-query
A map without React@mountaya/elements, @mountaya/client and maplibre-gl
A map in React@mountaya/elements-react, @mountaya/elements, @mountaya/client, maplibre-gl and react
An itinerary editor in React@mountaya/studio-react on top of that line

maplibre-gl is the renderer, and every map needs it. Install it at 6.16.0 exactly, the version the elements' map worker is built from, so the worker and the renderer stay one release. @mountaya/elements authenticates the requests the renderer makes, with the credential of the client a map is handed.

One exception, and only if you edit: the map client behind an itinerary editor sits behind @mountaya/elements/editor, whose drawing library and router are optional peers. Install terra-draw, terra-draw-maplibre-gl-adapter and @maplibre/maplibre-gl-directions beside it. See editing an itinerary.

Import the stylesheet

One import brings everything the elements draw with:

import "@mountaya/elements-react/styles.css";

MapLibre's own rules are inside it. Without them the canvas has no size and popups have no frame. Markers lose their anchoring, and the attribution credit does not render, which is a licence obligation.

@mountaya/studio-react has one sheet, which takes the place of the elements' one:

import "@mountaya/studio-react/styles.css";

It holds Mantine's rules first, wrapped in a @layer mantine, then the whole family in cascade order: MapLibre's rules, the elements' over them, the studio's over both. The layered Mantine rules lose to every unlayered rule, so an application that already imports @mantine/core/styles.css keeps its own and the order of the two imports does not matter.

Mount the provider

MapView constructs the map and hands it back through onReady. Put that instance on the provider and everything under it can drive it:

import { useState } from "react";
import { Mountaya } from "@mountaya/client/browser";
import type { MountayaMap } from "@mountaya/elements";
import { MapControls, MapView, MountayaElementsProvider } from "@mountaya/elements-react";

const client = new Mountaya({ credential: { publishableKey: "pk_live_…" } });

export const Map = () => {
  const [map, setMap] = useState<MountayaMap | null>(null);

  return (
    <MountayaElementsProvider client={client} map={map}>
      <MapView center={[6.86, 45.83]} client={client} onReady={setMap} zoom={12} />
      <MapControls compass geolocate layers preferences zoom />
    </MountayaElementsProvider>
  );
};

The client comes from the browser entry point, so the secret-key credential cannot reach a bundle you ship. MapView fills its container, so give that container a height.

map is optional. Leave it off and the canvas-free elements still render: the elevation profile and the route tables. Every control renders disabled until a map resolves.

Nest providers

A provider inherits. A field a nested provider does not state is the enclosing one's. Outside any provider it is the bundled default.

A component that owns a canvas publishes its map by mounting a second provider under yours. MountayaStudio does exactly that. Your glyph tables, language, translations and strings pass through untouched.

import { frElementsStrings } from "@mountaya/elements/i18n/fr";

<MountayaElementsProvider
  controlIcons={ourIcons}
  language="fr"
  translations={{ fr: frElementsStrings }}
>
  {/* the map arrives here, and nothing above it is lost */}
  <MountayaElementsProvider map={map}>{ourSubtree}</MountayaElementsProvider>
</MountayaElementsProvider>;

map={null} is a statement rather than an omission. A subtree that holds no map can say so under a provider that does. Strings re-resolve from a new language's base when the nested provider states one.

Import from the right entry point

The entry points are disjoint: exactly one of them publishes any given name.

Import fromWhat it publishes
@mountaya/clientThe isomorphic client for the three APIs
@mountaya/client/browserThe same client, without the secret-key credential
@mountaya/client-reactThe client provider and its query and mutation hooks
@mountaya/elementsThe imperative map, geometry, and elevation maths
@mountaya/elements-reactThe map view, the control stack, and the panels
@mountaya/elements-react/elementsThe elements that need no canvas
@mountaya/elements-react/editorThe editor map and its drawing props
@mountaya/studio-reactThe assembled studio and every surface it places
@mountaya/studio-react/headlessThe studio hooks, with no markup
@mountaya/studio/handlersThe published tool handlers

Reach for the narrowest entry that serves you. @mountaya/elements at its root brings the renderer. @mountaya/elements/errors, @mountaya/elements/profile and @mountaya/elements/geo exist so a caller who wants only those pays no map engine.

Handler props override the default

Every element takes optional on* props. Setting one suppresses the element's own default and makes that element presentational:

<ZoomControl />;                        // zooms the map
<ZoomControl onZoomIn={bus.zoomIn} />;  // calls you, and does not zoom

The names follow a pattern:

ShapeMeans
onClick, on<Noun>ClickA press the element cannot act on itself
on<Verb>Overrides that imperative action
onToggle, onToggle<Scope>Overrides a visibility flip
on<Noun>Change(next)Reports a new value
onSelect(item)Reports a choice
onReady, onError, onAccessRequired, onDestroy, onUnavailableLifecycle, and additive rather than suppressing
onHoverTakes the cursor, and the map marker the element would have pinned with it

onHover reads like lifecycle and is not. On the elevation profile and the segment table it is a subscription to the cursor, and the element's default for that cursor is to pin a marker on the map. Set it and the marker is yours to draw.

Two controls take a required handler instead of an optional one: they have nothing to drive. See showing a map.

Next steps