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.
Works with
---
name: arcgis-widgets-ui
description: 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.
license: MIT
---
# 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)
```javascript
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)
```javascript
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)
```javascript
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
| 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-scale-bar` | Display map scale |
| `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 CatalogLayer sublayers |
| `arcgis-search` | Location search |
| `arcgis-placement` | Control widget placement |
| `arcgis-popup` | Feature popups |
| `arcgis-editor` | Feature editing |
| `arcgis-feature` | Display feature info without popup |
| `arcgis-feature-form` | Form-based attribute editing |
| `arcgis-features` | Display multiple features info |
| `arcgis-sketch` | Draw geometries |
| `arcgis-feature-table` | Tabular data view |
| `arcgis-time-slider` | Temporal navigation |
| `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-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 |
| `arcgis-link-chart` | Link chart visualization |
| `arcgis-utility-network-trace` | Utility network tracing |
| `arcgis-version-management` | Manage geodatabase versions |
## Slot-Based Positioning
```html
<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:
```html
<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:
```html
<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
```html
<!-- Map Component -->
<arcgis-legend slot="bottom-left"></arcgis-legend>
```
```javascript
// 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
```html
<!-- Map Component -->
<arcgis-layer-list slot="top-right"></arcgis-layer-list>
```
```javascript
// 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
```html
<!-- Map Component -->
<arcgis-basemap-gallery slot="top-right"></arcgis-basemap-gallery>
```
```javascript
// Core API
import BasemapGallery from "@arcgis/core/widgets/BasemapGallery.js";
const basemapGallery = new BasemapGallery({ view: view });
view.ui.add(basemapGallery, "top-right");
```
### Search
```html
<!-- Map Component -->
<arcgis-search slot="top-right"></arcgis-search>
```
```javascript
// 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
```html
<!-- Map Component -->
<arcgis-feature-table reference-element="arcgis-map"></arcgis-feature-table>
```
```javascript
// 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
```html
<!-- 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>
```
```javascript
// 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
```javascript
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
```html
<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
```html
<!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
```html
<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
| 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
```html
<!-- 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>
</body>
</body>
```
## Widget Events
```javascript
// 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.
```html
<!-- 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 -->
```
```html
<!-- 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`.
```javascript
// Anti-pattern: widget in both container and view.ui
const legend = new Legend({ view: view, container: "legendDiv" });
view.ui.add(legend, "bottom-right"); // Conflicts
```
```javascript
// 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 detailsMore Design Systems skills
stitch-design-taste
leonxlnx/taste-skill
Semantic Design System Skill for Google Stitch. Generates agent-friendly DESIGN.md files that enforce premium, anti-generic UI standards — strict typography, calibrated color, asymmetric layouts, perpetual micro-motion, and hardware-accelerated performance.
figma
heygen-com/hyperframes
Import Figma content into a HyperFrames composition — rendered assets, brand tokens, components, storyboard sections → reconstructed motion (frames read as states, not slides) (REST/CLI), connector-assisted motion when available, and shaders from a connector or native export. Use when the user pastes a figma.com link or asks to bring a Figma design, frame, logo, brand, or animation into a video/composition.
image
coreyhaines31/marketingskills
When the user wants to create, generate, edit, or optimize images for marketing — blog heroes, social graphics, product mockups, profile banners, listing visuals, or brand assets. Also use when the user mentions 'AI image generation,' 'generate an image,' 'create a graphic,' 'product mockup,' 'hero image,' 'social media graphic,' 'banner image,' 'cover photo,' 'profile banner,' 'listing screenshot,' 'Flux,' 'Flux Kontext,' 'Midjourney,' 'DALL-E,' 'GPT Image,' 'ChatGPT Images,' 'Ideogram,' 'Gemini image,' 'Nano Banana,' 'Recraft,' 'Stable Diffusion,' 'Canva,' 'Figma,' 'image optimization,' 'compress images,' 'WebP,' or 'OG image.' Use this for general-purpose marketing image creation and optimization. For paid ad image creative and platform-specific ad specs, see ad-creative. For video production, see video.

