arcgis-widgets-ui

SKILL.md

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 when possible. Esri is transitioning to web components, and some widgets are already deprecated. See Esri's component transition plan.

Map Components Approach

Available Map Components

Component Purpose
arcgis-map 2D map container
arcgis-scene 3D scene container
arcgis-zoom Zoom in/out buttons
arcgis-compass Orientation indicator
arcgis-home Return to initial extent
arcgis-locate Find user location
arcgis-track Track user location
arcgis-navigation-toggle Pan/rotate mode (3D)
arcgis-floor-filter Filter indoor map by floor level
arcgis-fullscreen Toggle fullscreen
arcgis-grid-controls Grid controls for feature tables
arcgis-scale-bar Display map scale
arcgis-scale-range-slider Set visible scale range for layers
arcgis-legend Layer symbology legend
arcgis-layer-list Layer visibility control
arcgis-basemap-gallery Switch basemaps
arcgis-basemap-layer-list Layer list for basemap layers
arcgis-basemap-toggle Toggle two basemaps
arcgis-catalog-layer-list Browse and toggle CatalogLayer sublayers
arcgis-search Location search
arcgis-placement Control widget placement on the view
arcgis-popup Feature popups
arcgis-editor Feature editing
arcgis-feature Display feature information without a popup
arcgis-feature-form Form-based attribute editing
arcgis-features Display multiple features information
arcgis-sketch Draw geometries
arcgis-feature-table Tabular data view
arcgis-time-slider Temporal navigation
arcgis-time-zone-label Display time zone
arcgis-expand Collapsible container
arcgis-print Map printing
arcgis-table-list List and manage feature tables
arcgis-bookmarks Navigate to bookmarks
arcgis-directions Turn-by-turn routing
arcgis-swipe Compare layers
arcgis-coordinate-conversion Coordinate formats
arcgis-daylight 3D lighting control
arcgis-weather 3D weather effects
arcgis-distance-measurement-2d 2D distance measurement
arcgis-area-measurement-2d 2D area measurement
arcgis-direct-line-measurement-3d 3D line measurement
arcgis-area-measurement-3d 3D area measurement
arcgis-directional-pad Directional pad for camera navigation
arcgis-elevation-profile Elevation profile along a path
arcgis-line-of-sight Line of sight analysis (3D)
arcgis-slice Slice through 3D data
arcgis-shadow-cast Shadow cast analysis (3D)
arcgis-oriented-imagery-viewer View oriented imagery
arcgis-video-player Play video feeds from video layers
arcgis-link-chart Link chart visualization
arcgis-link-chart-layout-switcher Switch link chart layouts
arcgis-version-management Manage geodatabase versions
arcgis-utility-network-trace Utility network tracing
arcgis-utility-network-associations Utility associations
arcgis-utility-network-validate-topology Validate utility network topology

Note: Not all widgets have component equivalents yet. Histogram and some specialized widgets only have Core API versions. FeatureForm has the arcgis-feature-form component.

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

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>

Core Widget Approach

Adding Widgets to View

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

// Create widget
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 to 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" // Or document.getElementById("legendDiv")
});
</script>

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");
  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, // ms between stops
  loop: true
});

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

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

Print

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

const print = new Print({
  view: view,
  printServiceUrl: "https://utility.arcgisonline.com/arcgis/rest/services/Utilities/PrintingTools/GPServer/Export%20Web%20Map%20Task"
});

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

Calcite Design System Integration

Basic Layout with Calcite

<!DOCTYPE html>
<html>
<head>
  <script type="module" src="https://js.arcgis.com/calcite-components/3.3.3/calcite.esm.js"></script>
  <script src="https://js.arcgis.com/4.34/"></script>
  <script type="module" src="https://js.arcgis.com/4.34/map-components/"></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>
  // Toggle panels on action click
  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

Component Purpose
calcite-shell App layout container
calcite-shell-panel Side panels
calcite-panel Content panel
calcite-navigation Header/footer
calcite-action-bar Icon button bar
calcite-action Icon button
calcite-button Standard button
calcite-input Text input
calcite-list List container
calcite-list-item List item
calcite-card Card container
calcite-modal Modal dialog
calcite-alert Alert message
calcite-loader Loading 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>

Custom Widget Placement

Manual Positioning

// Add widget at specific position
view.ui.add(widget, {
  position: "manual",
  index: 0
});

// Position with CSS
document.getElementById("myWidget").style.cssText = `
  position: absolute;
  top: 10px;
  left: 50%;
  transform: translateX(-50%);
`;

DOM Container

<div id="mapDiv" style="position: relative;">
  <div id="customWidget" style="position: absolute; top: 10px; right: 10px; z-index: 1;">
    <!-- Custom content -->
  </div>
</div>

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);
});

// TimeSlider time change
timeSlider.watch("timeExtent", (value) => {
  console.log(value.start, value.end);
});

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

TypeScript Usage

Widget configurations use autocasting with type properties. For TypeScript safety, use as const:

// Use 'as const' for widget configurations
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"
    }]];
  }
});

// For layer configurations in widgets
const featureTable = new FeatureTable({
  view: view,
  layer: featureLayer,
  tableTemplate: {
    columnTemplates: [
      { fieldName: "name", label: "Name" },
      { fieldName: "population", label: "Population" }
    ]
  }
});

Tip: See arcgis-core-maps skill for detailed guidance on autocasting vs explicit classes.

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

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, does not render -->
    
    <!-- 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 at all, renders in a broken state, or throws an error about a missing view reference.

  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 element and view.ui.

    // Anti-pattern: widget added to both a container div AND view.ui
    const legend = new Legend({
      view: view,
      container: "legendDiv" // Renders into the DOM element
    });
    view.ui.add(legend, "bottom-right"); // Also adds to the view UI
    
    // Correct: pick one placement strategy
    // Option A: Let view.ui manage placement
    const legend = new Legend({ view: view });
    view.ui.add(legend, "bottom-right");
    
    // Option B: Place in a specific DOM element
    const legend = new Legend({ view: view, container: "legendDiv" });
    

    Impact: The widget renders twice on the page, or the layout breaks because the view UI and the container element compete for control of the widget's DOM node.

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

Weekly Installs
27
GitHub Stars
5
First Seen
Jan 22, 2026
Installed on
opencode24
gemini-cli22
codex22
github-copilot21
cursor21
kimi-cli18