Knit UI
GitHub

Map

@knitui/map puts MapLibre GL (web) and MapLibre React Native (device) behind one declarative API: the same <Map>, the same sources, the same layers.

npx expo install @knitui/map @maplibre/maplibre-react-native
npm install maplibre-gl   # web
import { Map, GeoJSONSource, CircleLayer } from "@knitui/map";

<Map style={{ flex: 1 }} styleUrl={style} initialCamera={{ center: [4.9, 52.37], zoom: 11 }}>
  <GeoJSONSource id="stops" data={stops}>
    <CircleLayer id="stops-circles" circleRadius={5} circleColor="#0ea5e9" />
  </GeoJSONSource>
</Map>;

Web worker

On web, maplibre-gl parses tiles in a web worker, and v6 can't locate that worker from inside a bundler. Skip this step and the map paints its background colour but never loads a tile, without logging an error. Serve two files from your static directory, maplibre-gl-worker.mjs and the maplibre-gl-shared.mjs it imports, and point the map at the worker once at startup:

mkdir -p public/maplibre
cp node_modules/maplibre-gl/dist/maplibre-gl-{worker,shared}.mjs public/maplibre/
// app entry: _layout.tsx, providers.tsx, main.tsx, …
import { setWorkerUrl } from "@knitui/map/worker";

setWorkerUrl("/maplibre/maplibre-gl-worker.mjs");

Copy the files as part of your build rather than committing them, so they always match the installed maplibre-gl. The @knitui/map/worker entry only stores the URL, so importing it from your app root doesn't pull the map engine into every page. Native maps don't use a web worker, and the call has no effect there.

The API shape

Sources hold data, layers draw it — the MapLibre style-spec model, expressed as components:

Layer props are the style-spec paint and layout properties, typed against a pinned @maplibre/maplibre-gl-style-spec, so an invalid expression is a compile error rather than a silent no-op.

Markers vs symbol layers

This is the decision that determines whether your map is usable at scale.

Marker renders React content at a coordinate. Use it for tens of points, or when the content must be interactive.

SymbolLayer + SvgImage rasterises an SVG once, uploads it to the GPU, and draws thousands of instances. Use it past a few hundred points.

import { SvgImage, SymbolLayer } from "@knitui/map";

<SvgImage id="pin" svg={pinSvg} sdf />
<SymbolLayer id="pins" iconImage="pin" iconAllowOverlap />

SvgImage is one rasterizer for both platforms (an offscreen react-native-svg host feeding addImage), which is what makes a 4,000-point clustered map viable on device.

Clustering

GeoJSON sources support MapLibre's built-in clustering; combine it with a symbol layer for the cluster bubbles and a second one for leaves.

What it does not depend on

@knitui/map deliberately avoids react-native-web and Tamagui — it is the map layer, not a themed component — so it stays usable in a non-kit app and its bundle does not pull the design system.

Edit this page on GitHub