Skip to content
Kappa
Maps
@dicehub/kappav0.4.2

Maps

Build interactive location maps with Kappa controls and MapLibre rendering.

<script setup lang="ts">
import * as maplibre from "maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";
import { MapView } from "@dicehub/kappa/components/map-view";
import type { MapOptions } from "maplibre-gl";

const options = {
  style: "https://tiles.openfreemap.org/styles/liberty",
  center: [13.405, 52.52],
  zoom: 10,
} satisfies Omit<MapOptions, "container">;
</script>

<template>
  <MapView
    :engine="maplibre"
    :options="options"
    aria-label="Map of Berlin"
  />
</template>

Installation

MapView uses MapLibre GL JS as an optional peer dependency. Import the component from its isolated path and load MapLibre's required stylesheet once in the application.

pnpm add @dicehub/kappa maplibre-gl

Usage

Pass the imported MapLibre namespace and standard MapLibre options. Kappa supplies the container, resize and disposal lifecycle, controls, markers, states, and accessibility boundary.

<script setup lang="ts">
import * as maplibre from "maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";
import { MapView } from "@dicehub/kappa/components/map-view";
import type { MapOptions } from "maplibre-gl";

const options = {
  style: "https://tiles.openfreemap.org/styles/liberty",
  center: [13.405, 52.52],
  zoom: 10,
} satisfies Omit<MapOptions, "container">;
</script>

<template>
  <MapView
    :engine="maplibre"
    :options="options"
    aria-label="Map of Berlin"
  />
</template>

Engine Boundary

Kappa does not bundle a tile service, style, or access token. Select a provider that meets the application's licensing, privacy, attribution, and availability requirements. The examples use the public OpenFreeMap Liberty style; production applications should supply their own style URL or style object.

  • Use markers for normal labeled locations and application selection.
  • Use ready or getMap() for GeoJSON, vector sources, routes, polygons, terrain, and custom layers.
  • Replace the options object or change revision only when the map must be recreated.
  • Keep attribution enabled when the selected data or tile provider requires it.

Examples

Type at least three characters to get address suggestions, then select one to move the map. This example uses the public Photon service.

Maps · address searchOpen full example (new tab)
<script setup lang="ts">
import { computed, ref } from "vue";
import * as maplibre from "maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";
import {
  Autocomplete,
  type AutocompleteInputValueChangeDetails,
  type AutocompleteValueChangeDetails,
} from "@dicehub/kappa/components/autocomplete";
import { MapView, type MapViewMarker } from "@dicehub/kappa/components/map-view";
import type { Map as MapLibreMap, MapOptions } from "maplibre-gl";

interface AddressResult extends MapViewMarker {
  id: string;
}

const query = ref("");
const results = ref<AddressResult[]>([]);
const selected = ref<AddressResult>();
const searchOpen = ref(false);
const markers = computed(() => selected.value ? [selected.value] : []);
let debounceTimer: ReturnType<typeof setTimeout> | undefined;
let searchController: AbortController | undefined;
const options = {
  style: "https://tiles.openfreemap.org/styles/liberty",
  center: [12.1, 52.45],
  zoom: 5.6,
} satisfies Omit<MapOptions, "container">;

async function searchAddresses(value: string) {
  searchController?.abort();
  const controller = new AbortController();
  searchController = controller;
  try {
    const url = new URL("https://photon.komoot.io/api/");
    url.searchParams.set("q", value);
    url.searchParams.set("limit", "5");
    const response = await fetch(url, { signal: controller.signal });
    if (!response.ok) throw new Error("Address search returned " + response.status);
    const data = await response.json() as {
      features: Array<{
        geometry: { coordinates: [number, number] };
        properties: { name?: string; city?: string; osm_id?: number };
      }>;
    };
    results.value = data.features.map((feature, index) => ({
      id: String(feature.properties.osm_id ?? index),
      label: feature.properties.name ?? feature.properties.city ?? "Unnamed location",
      coordinates: feature.geometry.coordinates,
    }));
  } catch {
    if (!controller.signal.aborted) results.value = [];
  }
}

function suggestAddresses(details: AutocompleteInputValueChangeDetails) {
  query.value = details.inputValue;
  if (details.reason === "item-select") return;
  searchOpen.value = details.inputValue.trim().length > 0;
  if (debounceTimer) clearTimeout(debounceTimer);
  searchController?.abort();
  results.value = [];
  if (details.inputValue.trim().length < 3) return;
  debounceTimer = setTimeout(() => searchAddresses(details.inputValue), 320);
}

function selectAddress(
  map: MapLibreMap,
  details: AutocompleteValueChangeDetails<AddressResult>,
) {
  selected.value = details.items[0];
  if (selected.value) {
    searchOpen.value = false;
    map.flyTo({ center: selected.value.coordinates, zoom: 13 });
  }
}
</script>

<template>
  <MapView
    :engine="maplibre"
    :options="options"
    :markers="markers"
    aria-label="Address search map"
  >
    <template #overlay="{ map }">
      <Autocomplete
        v-model:input-value="query"
        v-model:open="searchOpen"
        :filter="false"
        :input-attrs="{ 'aria-label': 'Find an address' }"
        :items="results"
        :item-to-string="(item: AddressResult) => item.label"
        :item-to-value="(item: AddressResult) => item.id"
        input-behavior="autohighlight"
        placeholder="Find an address..."
        @input-value-change="suggestAddresses"
        @value-change="selectAddress(map, $event)"
      />
    </template>
  </MapView>
</template>

Locations

Keep location data declarative while Kappa owns marker focus, selection, safe text popups, and visual tones.

<script setup lang="ts">
import { ref } from "vue";
import * as maplibre from "maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";
import {
  MapView,
  type MapViewMarker,
} from "@dicehub/kappa/components/map-view";
import type { MapOptions } from "maplibre-gl";

const selectedId = ref<string | number>();
const options = {
  style: "https://tiles.openfreemap.org/styles/liberty",
  center: [12.1, 52.45],
  zoom: 5.6,
} satisfies Omit<MapOptions, "container">;
const markers: MapViewMarker[] = [
  {
    id: "berlin",
    coordinates: [13.405, 52.52],
    label: "Berlin",
    description: "Primary engineering office",
    tone: "accent",
  },
  {
    id: "hamburg",
    coordinates: [9.9937, 53.5511],
    label: "Hamburg",
    description: "Test facility",
    tone: "success",
  },
];
</script>

<template>
  <MapView
    :engine="maplibre"
    :options="options"
    :markers="markers"
    :selected-marker-id="selectedId"
    aria-label="Engineering locations"
    @marker-click="selectedId = $event.id"
  />
</template>

Routes and Areas

Use the ready event for advanced MapLibre sources and layers. Kappa keeps the frame and controls stable.

<script setup lang="ts">
import * as maplibre from "maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";
import { MapView } from "@dicehub/kappa/components/map-view";
import type { Map as MapLibreMap, MapOptions } from "maplibre-gl";

const options = {
  style: "https://tiles.openfreemap.org/styles/liberty",
  center: [11.85, 52.35],
  zoom: 5.35,
} satisfies Omit<MapOptions, "container">;

const operationsGeoJson = {
  type: "FeatureCollection",
  features: [
    {
      type: "Feature",
      properties: {},
      geometry: {
        type: "Polygon",
        coordinates: [[
          [8.35, 54.05], [10.45, 54.55],
          [14.55, 52.25], [12.7, 50.65],
          [9.15, 51.2], [8.35, 54.05],
        ]],
      },
    },
  ],
} satisfies Parameters<MapLibreMap["addSource"]>[1];

function addOperationsLayer(map: MapLibreMap) {
  map.addSource("operations", {
    type: "geojson",
    data: operationsGeoJson,
  });
  map.addLayer({
    id: "operations-area",
    type: "fill",
    source: "operations",
    paint: {
      "fill-color": "#247ab7",
      "fill-opacity": 0.12,
    },
  });
}
</script>

<template>
  <MapView
    :engine="maplibre"
    :options="options"
    aria-label="Operations area and route"
    @ready="addOperationsLayer"
  />
</template>

Data States

Reserve one stable map region while location data or the map service changes state.

<script setup lang="ts">
import * as maplibre from "maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";
import { MapView } from "@dicehub/kappa/components/map-view";
import type { MapOptions } from "maplibre-gl";

const options = {
  style: "https://tiles.openfreemap.org/styles/liberty",
  center: [13.405, 52.52],
  zoom: 10,
} satisfies Omit<MapOptions, "container">;
</script>

<template>
  <MapView :engine="maplibre" :options="options" aria-label="Map" loading />
  <MapView :engine="maplibre" :options="options" aria-label="Map" empty />
  <MapView
    :engine="maplibre"
    :options="options"
    aria-label="Map"
    error="The location service is unavailable."
  />
</template>

Accessibility

  • Give every map a specific ariaLabel and a concise text summary in ariaDescription.
  • Each Kappa marker is a keyboard-focusable button. Marker labels become accessible names.
  • Address suggestions use Kappa Autocomplete. Arrow keys move through results, Enter selects one, and Escape closes the list.
  • Do not make the map the only way to reach essential locations or values. Provide a linked list or table for critical data.
  • Translate the control and state labels when the application supports more than one language.

Security and Data

  • Kappa marker labels and descriptions use DOM text nodes. They do not accept HTML.
  • Treat style URLs, tile endpoints, GeoJSON, and custom MapLibre callbacks as application-controlled input.
  • Do not expose private coordinates to a third-party tile service without an approved data policy.
  • Address queries may contain personal data. Review the selected geocoding provider's privacy policy, limits, and production terms.
  • Restrict remote map hosts with the application's Content Security Policy.

API Reference

PropTypeDefaultDescription
ariaLabelstring—Required accessible name for the interactive map canvas.
ariaDescriptionstringKeyboard instructionsInstructions or a short data summary linked to the canvas.
engineMapViewEngine—Imported MapLibre namespace. Keeps the engine outside the Kappa bundle.
optionsMapViewOptions—MapLibre initialization options except container.
markersreadonly MapViewMarker[][]Safe text markers with optional descriptions, tones, and popups.
selectedMarkerIdstring | number—Marks one location as selected without owning application state.
heightstring | number"28rem"Map height. Numbers are pixels.
showControlsbooleantrueShows Kappa zoom and reset controls.
loading / empty / errorboolean / boolean / stringfalse / false / —Explicit asynchronous and data states.
revisionnumber | string0Recreates the map after in-place option changes.

Events

EventPayloadDescription
readyMapLibreMapFires after the map style loads. Add custom sources and layers here.
markerClickMapViewMarkerFires when a Kappa marker is selected.
moveEndMapViewStateReturns center, zoom, bearing, and pitch after movement.
errorunknownReports construction and MapLibre runtime errors.

Exposed Methods

MethodDescription
getMap()Returns the MapLibre map instance for advanced engine operations.
resize()Recalculates the canvas size. ResizeObserver calls this automatically.
reset()Returns to the initial camera position.
zoomIn() / zoomOut()Changes zoom with the engine animation.

Import the component and public types from @dicehub/kappa/components/map-view. See the MapLibre GL JS documentation for engine options, sources, and layers.