CtrlK
BlogDocsLog inGet started
Tessl Logo

arcgis-widgets-ui

Build map user interfaces with ArcGIS widgets, Map Components, and Calcite Design System. Use for adding legends, layer lists, search, tables, time sliders, and custom UI layouts.

88

Quality

86%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Passed

No known issues

SKILL.md
Quality
Evals
Security

ArcGIS Widgets & UI

Use this skill when building user interfaces with widgets, Map Components, and Calcite.

Best Practice: Prefer Map Components (web components like arcgis-legend, arcgis-search) over Core API widgets. Esri has transitioned to web components as the primary approach.

Import Patterns

Map Components (ESM)

import "@arcgis/map-components/dist/components/arcgis-map";
import "@arcgis/map-components/dist/components/arcgis-legend";
import "@arcgis/map-components/dist/components/arcgis-search";
import "@arcgis/map-components/dist/components/arcgis-layer-list";
import "@arcgis/map-components/dist/components/arcgis-expand";

Core API Widgets (ESM)

import Legend from "@arcgis/core/widgets/Legend.js";
import LayerList from "@arcgis/core/widgets/LayerList.js";
import Search from "@arcgis/core/widgets/Search.js";
import BasemapGallery from "@arcgis/core/widgets/BasemapGallery.js";

Dynamic Imports (CDN)

const Legend = await $arcgis.import("@arcgis/core/widgets/Legend.js");
const Search = await $arcgis.import("@arcgis/core/widgets/Search.js");

Note: CSS for Map Components loads automatically via npm. Core API widgets require: @import "@arcgis/core/assets/esri/themes/light/main.css";

Map Components Overview

ComponentPurpose
arcgis-map2D map container
arcgis-scene3D scene container
arcgis-zoomZoom in/out buttons
arcgis-compassOrientation indicator
arcgis-homeReturn to initial extent
arcgis-locateFind user location
arcgis-trackTrack user location
arcgis-navigation-togglePan/rotate mode (3D)
arcgis-floor-filterFilter indoor map by floor level
arcgis-fullscreenToggle fullscreen
arcgis-scale-barDisplay map scale
arcgis-legendLayer symbology legend
arcgis-layer-listLayer visibility control
arcgis-basemap-gallerySwitch basemaps
arcgis-basemap-layer-listLayer list for basemap layers
arcgis-basemap-toggleToggle two basemaps
arcgis-catalog-layer-listBrowse CatalogLayer sublayers
arcgis-searchLocation search
arcgis-placementControl widget placement
arcgis-popupFeature popups
arcgis-editorFeature editing
arcgis-featureDisplay feature info without popup
arcgis-feature-formForm-based attribute editing
arcgis-featuresDisplay multiple features info
arcgis-sketchDraw geometries
arcgis-feature-tableTabular data view
arcgis-time-sliderTemporal navigation
arcgis-expandCollapsible container
arcgis-printMap printing
arcgis-table-listList and manage feature tables
arcgis-bookmarksNavigate to bookmarks
arcgis-directionsTurn-by-turn routing
arcgis-swipeCompare layers
arcgis-coordinate-conversionCoordinate formats
arcgis-daylight3D lighting control
arcgis-weather3D weather effects
arcgis-distance-measurement-2d2D distance measurement
arcgis-area-measurement-2d2D area measurement
arcgis-direct-line-measurement-3d3D line measurement
arcgis-area-measurement-3d3D area measurement
arcgis-elevation-profileElevation profile along a path
arcgis-line-of-sightLine of sight analysis (3D)
arcgis-sliceSlice through 3D data
arcgis-shadow-castShadow cast analysis (3D)
arcgis-oriented-imagery-viewerView oriented imagery
arcgis-video-playerPlay video feeds
arcgis-link-chartLink chart visualization
arcgis-utility-network-traceUtility network tracing
arcgis-version-managementManage geodatabase versions

Slot-Based Positioning

<arcgis-map basemap="streets-vector">
  <!-- Position widgets using slots -->
  <arcgis-zoom slot="top-left"></arcgis-zoom>
  <arcgis-home slot="top-left"></arcgis-home>
  <arcgis-compass slot="top-left"></arcgis-compass>

  <arcgis-search slot="top-right"></arcgis-search>
  <arcgis-layer-list slot="top-right"></arcgis-layer-list>

  <arcgis-legend slot="bottom-left"></arcgis-legend>
  <arcgis-scale-bar slot="bottom-right"></arcgis-scale-bar>

  <!-- Popup must use popup slot -->
  <arcgis-popup slot="popup"></arcgis-popup>
</arcgis-map>

Available slots: top-left, top-right, bottom-left, bottom-right, top-start, top-end, bottom-start, bottom-end, popup

Widgets in the same slot stack vertically in DOM order.

Expand Component

Wrap widgets in arcgis-expand for collapsible behavior:

<arcgis-map basemap="streets-vector">
  <arcgis-expand slot="top-right" expand-tooltip="Show Legend" mode="floating">
    <arcgis-legend></arcgis-legend>
  </arcgis-expand>

  <arcgis-expand slot="top-left" expanded>
    <arcgis-layer-list></arcgis-layer-list>
  </arcgis-expand>
</arcgis-map>

Reference Element (External Components)

Place components outside the map and reference them:

<calcite-shell>
  <calcite-shell-panel slot="panel-start">
    <arcgis-legend reference-element="arcgis-map"></arcgis-legend>
  </calcite-shell-panel>

  <arcgis-map id="arcgis-map" basemap="topo-vector">
    <arcgis-zoom slot="top-left"></arcgis-zoom>
  </arcgis-map>
</calcite-shell>

Common Widgets

Legend

<!-- Map Component -->
<arcgis-legend slot="bottom-left"></arcgis-legend>
// Core API
import Legend from "@arcgis/core/widgets/Legend.js";

const legend = new Legend({
  view: view,
  layerInfos: [{
    layer: featureLayer,
    title: "Custom Title"
  }]
});

view.ui.add(legend, "bottom-left");

LayerList

<!-- Map Component -->
<arcgis-layer-list slot="top-right"></arcgis-layer-list>
// Core API with actions
import LayerList from "@arcgis/core/widgets/LayerList.js";

const layerList = new LayerList({
  view: view,
  listItemCreatedFunction: (event) => {
    const item = event.item;
    item.actionsSections = [[{
      title: "Zoom to layer",
      icon: "zoom-to-object",
      id: "zoom-to"
    }]];
  }
});

layerList.on("trigger-action", (event) => {
  if (event.action.id === "zoom-to") {
    view.goTo(event.item.layer.fullExtent);
  }
});

view.ui.add(layerList, "top-right");

BasemapGallery

<!-- Map Component -->
<arcgis-basemap-gallery slot="top-right"></arcgis-basemap-gallery>
// Core API
import BasemapGallery from "@arcgis/core/widgets/BasemapGallery.js";

const basemapGallery = new BasemapGallery({ view: view });
view.ui.add(basemapGallery, "top-right");

Search

<!-- Map Component -->
<arcgis-search slot="top-right"></arcgis-search>
// Core API with custom sources
import Search from "@arcgis/core/widgets/Search.js";

const search = new Search({
  view: view,
  sources: [{
    layer: featureLayer,
    searchFields: ["name", "address"],
    displayField: "name",
    exactMatch: false,
    outFields: ["*"],
    name: "My Layer",
    placeholder: "Search features"
  }]
});

view.ui.add(search, "top-right");

// Events
search.on("select-result", (event) => {
  console.log("Selected:", event.result);
});

FeatureTable

<!-- Map Component -->
<arcgis-feature-table reference-element="arcgis-map"></arcgis-feature-table>
// Core API
import FeatureTable from "@arcgis/core/widgets/FeatureTable.js";

const featureTable = new FeatureTable({
  view: view,
  layer: featureLayer,
  container: "tableDiv",
  visibleElements: {
    header: true,
    columnMenus: true,
    selectionColumn: true
  },
  tableTemplate: {
    columnTemplates: [
      { fieldName: "name", label: "Name" },
      { fieldName: "population", label: "Population" }
    ]
  }
});

// Watch selection via highlightIds
featureTable.highlightIds.on("change", (event) => {
  console.log("Selected IDs:", featureTable.highlightIds.toArray());
});

TimeSlider

<!-- Map Component -->
<arcgis-time-slider
  slot="bottom-right"
  layout="auto"
  mode="time-window"
  time-visible
  loop>
</arcgis-time-slider>

<script type="module">
  const timeSlider = document.querySelector("arcgis-time-slider");
  const mapEl = document.querySelector("arcgis-map");
  await mapEl.viewOnReady();

  await layer.load();
  timeSlider.fullTimeExtent = layer.timeInfo.fullTimeExtent;
  timeSlider.stops = { interval: layer.timeInfo.interval };
</script>
// Core API
import TimeSlider from "@arcgis/core/widgets/TimeSlider.js";

const timeSlider = new TimeSlider({
  view: view,
  mode: "time-window", // instant, time-window, cumulative-from-start, cumulative-from-end
  fullTimeExtent: layer.timeInfo.fullTimeExtent,
  stops: {
    interval: { value: 1, unit: "hours" }
  },
  playRate: 1000,
  loop: true
});

view.ui.add(timeSlider, "bottom-right");

// Events
import * as reactiveUtils from "@arcgis/core/core/reactiveUtils.js";

reactiveUtils.watch(
  () => timeSlider.timeExtent,
  (timeExtent) => {
    console.log("Time changed:", timeExtent.start, timeExtent.end);
  }
);

Core Widget Approach

Adding Widgets to View

import Legend from "@arcgis/core/widgets/Legend.js";

const legend = new Legend({ view: view });

// Add to view UI
view.ui.add(legend, "bottom-left");

// Add multiple widgets
view.ui.add([
  { component: legend, position: "bottom-left" },
  { component: search, position: "top-right" }
]);

// Add at specific index (order in position)
view.ui.add(legend, { position: "bottom-left", index: 0 });

// Remove widget
view.ui.remove(legend);

Widget in Custom Container

<div id="legendDiv"></div>

<script type="module">
import Legend from "@arcgis/core/widgets/Legend.js";

const legend = new Legend({
  view: view,
  container: "legendDiv"
});
</script>

Calcite Design System Integration

Basic Layout with Calcite

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="https://js.arcgis.com/5.0/@arcgis/core/assets/esri/themes/light/main.css" />
  <script src="https://js.arcgis.com/5.0/"></script>
  <script type="module" src="https://js.arcgis.com/map-components/5.0/arcgis-map-components.esm.js"></script>
  <style>
    html, body { height: 100%; margin: 0; }
  </style>
</head>
<body class="calcite-mode-light">
  <calcite-shell>
    <!-- Header -->
    <calcite-navigation slot="header">
      <calcite-navigation-logo slot="logo" heading="My Map App"></calcite-navigation-logo>
    </calcite-navigation>

    <!-- Side Panel -->
    <calcite-shell-panel slot="panel-start">
      <calcite-panel heading="Layers">
        <arcgis-layer-list reference-element="map"></arcgis-layer-list>
      </calcite-panel>
    </calcite-shell-panel>

    <!-- Map -->
    <arcgis-map id="map" basemap="streets-vector">
      <arcgis-zoom slot="top-left"></arcgis-zoom>
    </arcgis-map>

    <!-- End Panel -->
    <calcite-shell-panel slot="panel-end">
      <calcite-panel heading="Legend">
        <arcgis-legend reference-element="map"></arcgis-legend>
      </calcite-panel>
    </calcite-shell-panel>
  </calcite-shell>
</body>
</html>

Calcite Action Bar

<calcite-shell>
  <calcite-shell-panel slot="panel-start">
    <calcite-action-bar slot="action-bar">
      <calcite-action icon="layers" text="Layers" data-panel="layers"></calcite-action>
      <calcite-action icon="legend" text="Legend" data-panel="legend"></calcite-action>
      <calcite-action icon="bookmark" text="Bookmarks" data-panel="bookmarks"></calcite-action>
    </calcite-action-bar>

    <calcite-panel id="layers" heading="Layers">
      <arcgis-layer-list reference-element="map"></arcgis-layer-list>
    </calcite-panel>

    <calcite-panel id="legend" heading="Legend" hidden>
      <arcgis-legend reference-element="map"></arcgis-legend>
    </calcite-panel>
  </calcite-shell-panel>

  <arcgis-map id="map" basemap="topo-vector"></arcgis-map>
</calcite-shell>

<script>
  document.querySelectorAll("calcite-action").forEach(action => {
    action.addEventListener("click", () => {
      const panelId = action.dataset.panel;
      document.querySelectorAll("calcite-panel").forEach(panel => {
        panel.hidden = panel.id !== panelId;
      });
    });
  });
</script>

Common Calcite Components

ComponentPurpose
calcite-shellApp layout container
calcite-shell-panelSide panels
calcite-panelContent panel
calcite-navigationHeader/footer
calcite-action-barIcon button bar
calcite-actionIcon button
calcite-buttonStandard button
calcite-inputText input
calcite-listList container
calcite-list-itemList item
calcite-cardCard container
calcite-modalModal dialog
calcite-alertAlert message
calcite-loaderLoading indicator

Theming

<!-- Light mode -->
<body class="calcite-mode-light">

<!-- Dark mode -->
<body class="calcite-mode-dark">

<!-- Custom theme colors -->
<style>
  :root {
    --calcite-color-brand: #007ac2;
    --calcite-color-brand-hover: #005a8e;
    --calcite-color-text-1: #323232;
  }
</style>

Widget Events

// Search select
search.on("select-result", (event) => {
  console.log(event.result);
});

// LayerList trigger action
layerList.on("trigger-action", (event) => {
  console.log(event.action, event.item);
});

// FeatureTable selection
featureTable.highlightIds.on("change", (event) => {
  console.log(event.added, event.removed);
});

Reference Samples

  • legend - Legend widget for layer symbology
  • widgets-layerlist - LayerList widget for layer management
  • widgets-search-multiplesource - Search widget with multiple sources
  • widgets-featuretable - FeatureTable widget integration
  • basemap-gallery - BasemapGallery for switching basemaps
  • widgets-timeslider - TimeSlider widget for temporal data

Common Pitfalls

  1. Missing reference-element: Components placed outside the <arcgis-map> tag cannot find the view without an explicit reference.

    <!-- Anti-pattern: component outside arcgis-map with no reference -->
    <arcgis-map id="myMap" basemap="topo-vector"></arcgis-map>
    <arcgis-search></arcgis-search> <!-- Cannot find the view -->
    <!-- Correct: use reference-element to link to the map -->
    <arcgis-map id="myMap" basemap="topo-vector"></arcgis-map>
    <arcgis-search reference-element="myMap"></arcgis-search>

    Impact: The component cannot discover the associated view. It either does not render or throws an error.

  2. Slot names are specific: Use exact slot names (top-left, not topleft).

  3. Calcite CSS not loading: Ensure Calcite script is loaded before using Calcite components.

  4. Widget container conflicts: Do not add the same widget to both a DOM container and view.ui.

    // Anti-pattern: widget in both container and view.ui
    const legend = new Legend({ view: view, container: "legendDiv" });
    view.ui.add(legend, "bottom-right"); // Conflicts
    // Correct: pick one placement strategy
    const legend = new Legend({ view: view });
    view.ui.add(legend, "bottom-right");

    Impact: The widget renders twice or the layout breaks.

  5. Dark/light mode mismatch: Add calcite-mode-light or calcite-mode-dark class to body.

  6. Core API widgets missing CSS: When using Core API widgets (not Map Components), you must import @arcgis/core/assets/esri/themes/light/main.css. Map Components handle CSS automatically.

Related Skills

  • See arcgis-widgets-advanced for specialized widgets (BuildingExplorer, FloorFilter, Track, Locate, etc.)
  • See arcgis-map-tools for measurement, print, directions, and swipe tools
  • See arcgis-editing for Editor and Sketch widgets
  • See arcgis-tables-forms for FeatureTable and FeatureForm details
Repository
SaschaBrunnerCH/arcgis-maps-sdk-js-ai-context
Last updated
Created

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.