Clidey UXDocs

Map

A composable set of map components for rendering interactive geographic visualizations. Built on MapLibre GL via react-map-gl, with automatic dark/light style detection and a configKey pattern for centralized theming.

Interactive example#

Map · Live example

A world of connections.

Try the actual MapLibre component with no API key or external tile requests.

Load the map, drag to pan, zoom, and select a marker. The Natural Earth basemap is bundled locally.

The code uses shared demo presentation helpers. Their complete source and styling are included in the LLM build context.

Bundlers and workers#

MapLibre needs its worker module as well as its main JavaScript file. In Vite, import the worker as an asset and configure it before mounting the first map:

TSX

import { setWorkerUrl } from 'maplibre-gl';
import workerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?url';

setWorkerUrl(workerUrl);

The ?url syntax is Vite-specific. With another bundler, emit the worker file using that bundler’s asset handling and pass its public URL to setWorkerUrl. Declare maplibre-gl as a direct dependency when importing it in application code.

mapStyle accepts a style URL or a style specification object. Bundled style objects avoid an extra fetch and work with restrictive same-origin policies. The example bundles Natural Earth land data locally, so it needs no external tiles or API key.

Import#

TSX

import {
  MapContainer,
  MapMarker,
  MapLine,
  MapArc,
  MapPolygon,
  MapCircleLayer,
  MapHeatmap,
  useMap,
} from '@clidey/ux';

Architecture#

MapContainer provides context for all child map components. Every child can access the shared config via the useMap() hook. This is the same pattern used by ChartContainer.

TSX

const config = {
  cities: { label: "City", color: "#ef4444" },
  routes: { label: "Route", color: "#8b5cf6" },
};

<MapContainer config={config}>
  <MapMarker configKey="cities" latitude={40.71} longitude={-74.0} />
  <MapLine configKey="routes" coordinates={[...]} />
</MapContainer>

MapContainer#

The root component that wraps all map layers. It renders a MapLibre GL map instance.

TSX

<MapContainer
  config={{ primary: { label: "Point", color: "#ef4444" } }}
  initialViewState={{ latitude: 39.83, longitude: -98.58, zoom: 3 }}
  className="h-[400px]"
>
  {/* child map components */}
</MapContainer>

Dark mode

MapContainer automatically selects a light or dark basemap based on the current theme. You can override this with the mapStyle prop.

TSX

// Force dark style
<MapContainer
  config={config}
  mapStyle="https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json"
>
  ...
</MapContainer>

Globe projection

Switch from the default flat Mercator to a 3D globe:

TSX

<MapContainer
  config={config}
  projection="globe"
  initialViewState={{ latitude: 30, longitude: -30, zoom: 1.5 }}
>
  ...
</MapContainer>

Props

PropTypeDefaultDescription
configMapConfig—Required. Map of keys to { label?, color? } for child theming.
initialViewState{ latitude, longitude, zoom }US center, zoom 3Starting camera position.
projection"mercator" | "globe""mercator"Map projection mode.
mapStylestringAuto (light/dark)MapLibre style URL.
interactivebooleantrueEnable pan, zoom, and rotate.
showNavigationbooleantrueShow zoom/rotate controls (top-right).
showScalebooleanfalseShow distance scale bar.
mapRefReact.Ref<MapRef>—Ref to the underlying MapLibre instance.
classNamestring—Additional CSS classes.

MapMarker#

Renders a circular marker at a geographic coordinate.

TSX

<MapMarker
  latitude={40.7128}
  longitude={-74.006}
  color="#ef4444"
  size={14}
  label="New York"
/>

Using configKey

Instead of hardcoding colors, reference the container's config:

TSX

<MapContainer config={{ hub: { label: "Hub", color: "#3b82f6" } }}>
  <MapMarker latitude={40.71} longitude={-74.0} configKey="hub" />
</MapContainer>

Custom marker content

Pass children to render any React node instead of the default circle:

TSX

<MapMarker latitude={40.71} longitude={-74.0}>
  <div className="bg-white p-2 rounded shadow text-xs font-bold">HQ</div>
</MapMarker>

Props

PropTypeDefaultDescription
latitudenumber—Required. Latitude coordinate.
longitudenumber—Required. Longitude coordinate.
colorstringvar(--chart-1)Marker fill color.
sizenumber12Diameter in pixels.
labelstring—Text label rendered below the marker.
configKeystring—Key into the container config for color/label.
childrenReactNode—Custom content replaces the default circle.
onClick() => void—Click handler.

MapLine#

Draws a line connecting an array of coordinates.

TSX

<MapLine
  coordinates={[
    [-74.006, 40.7128],
    [-87.63, 41.88],
    [-118.24, 34.05],
  ]}
  color="#8b5cf6"
  width={3}
/>

Dashed lines

TSX

<MapLine coordinates={coords} dashed color="#64748b" />

Props

PropTypeDefaultDescription
coordinates[longitude, latitude][]—Required. Array of [lng, lat] pairs.
colorstringvar(--chart-2)Line stroke color.
widthnumber2Line width in pixels.
configKeystring—Key into container config.
dashedbooleanfalseRender as a dashed line.

MapArc#

Draws a curved arc between two points — useful for flight routes or connections between distant locations.

TSX

<MapArc
  from={[-74.006, 40.7128]}
  to={[2.3522, 48.8566]}
  color="#f59e0b"
  width={2.5}
  height={0.4}
/>

The height prop controls how much the arc bows outward (0–1 range). Higher values create more dramatic curves. Best used with projection="globe" for a dramatic effect.

Props

PropTypeDefaultDescription
from[longitude, latitude]—Required. Start point.
to[longitude, latitude]—Required. End point.
colorstringvar(--chart-2)Arc stroke color.
widthnumber2Arc width in pixels.
heightnumber0.5Arc curvature (0–1). Higher = more curved.
segmentsnumber64Number of line segments composing the arc.
configKeystring—Key into container config.

MapPolygon#

Draws a filled polygon with an optional stroke.

TSX

<MapPolygon
  coordinates={[[
    [-74.006, 40.7128],
    [-87.63, 41.88],
    [-95.37, 29.76],
    [-74.006, 40.7128],  // close the ring
  ]]}
  fillColor="#10b981"
  fillOpacity={0.2}
  strokeWidth={2}
/>

Note: coordinates follow the GeoJSON Polygon format — an array of rings, where each ring is an array of [lng, lat] pairs. The first and last point must be identical to close the polygon.

Props

PropTypeDefaultDescription
coordinates[longitude, latitude][][]—Required. GeoJSON polygon rings.
fillColorstringvar(--chart-3)Fill color.
fillOpacitynumber0.3Fill opacity (0–1).
strokeColorstringSame as fillColorBorder color.
strokeWidthnumber2Border width in pixels.
configKeystring—Key into container config.

MapCircleLayer#

Efficiently renders many points as circles using a single MapLibre layer. Better performance than individual MapMarker components for large datasets.

TSX

<MapCircleLayer
  points={[
    { latitude: 40.71, longitude: -74.0, radius: 10 },
    { latitude: 34.05, longitude: -118.24, radius: 6 },
    { latitude: 41.88, longitude: -87.63, radius: 8 },
  ]}
  color="#3b82f6"
  opacity={0.7}
/>

Props

PropTypeDefaultDescription
points{ latitude, longitude, radius?, properties? }[]—Required. Array of point objects.
colorstringvar(--chart-4)Circle fill color.
opacitynumber0.6Circle opacity (0–1).
configKeystring—Key into container config.

MapHeatmap#

Renders a density heatmap from an array of weighted points.

TSX

<MapHeatmap
  points={[
    { latitude: 40.71, longitude: -74.0, weight: 0.9 },
    { latitude: 34.05, longitude: -118.24, weight: 0.7 },
    // ... hundreds of points
  ]}
  radius={25}
  intensity={0.8}
/>

The color ramp transitions from transparent → blue → cyan → green → yellow → red based on point density.

Props

PropTypeDefaultDescription
points{ latitude, longitude, weight? }[]—Required. Array of weighted points.
radiusnumber20Blur radius in pixels.
intensitynumber1Heatmap intensity multiplier.
configKeystring—Key into container config.

useMap hook#

Access the container's config from any child component:

TSX

import { useMap } from '@clidey/ux';

function CustomLayer() {
  const { config } = useMap();
  const color = config.myKey?.color ?? '#000';
  // use color in a custom MapLibre layer
}

Must be rendered inside a MapContainer. Throws if used outside one.

Full example#

TSX

import {
  MapContainer,
  MapMarker,
  MapLine,
  MapArc,
  MapPolygon,
  MapHeatmap,
  type MapConfig,
} from '@clidey/ux';

const config = {
  cities: { label: "City", color: "#ef4444" },
  routes: { label: "Route", color: "#8b5cf6" },
  region: { label: "Region", color: "#10b981" },
} satisfies MapConfig;

function FlightMap() {
  return (
    <MapContainer
      config={config}
      projection="globe"
      initialViewState={{ latitude: 30, longitude: -30, zoom: 1.5 }}
      className="h-[500px]"
    >
      <MapArc
        from={[-74.006, 40.71]}
        to={[2.35, 48.86]}
        color="#f59e0b"
        width={2.5}
        height={0.4}
      />
      <MapMarker latitude={40.71} longitude={-74.006} configKey="cities" label="NYC" />
      <MapMarker latitude={48.86} longitude={2.35} configKey="cities" label="Paris" />
    </MapContainer>
  );
}

Notes#

  • No API key required — MapLibre GL is fully open-source. The default basemap tiles are from CARTO (free for moderate usage).
  • Coordinate order — All coordinate arrays use [longitude, latitude] order (GeoJSON standard), while marker props use separate latitude and longitude named parameters.
  • Performance — For more than ~50 markers, prefer MapCircleLayer over individual MapMarker components. For density visualization, use MapHeatmap.
  • Dark mode — The container auto-detects theme at mount time. If the theme changes dynamically, remount the component or pass mapStyle explicitly.

Was this page helpful?