Skip to content

The ArcGIS Experience Builder framework, component libraries, and design patterns provided in the developer edition are built to support compliance with WCAG 2.1 , Section 508 , and assistive technology requirements. This topic is intended for developers building custom widgets and components using the developer edition.

Key accessibility principles

The Experience Builder framework and its component libraries provide a strong accessibility foundation. Custom widget developers are responsible for following established best practices to ensure accessible behavior when extending the framework. When building custom widgets that integrate seamlessly into accessible Experience Builder applications, keep the following principles in mind.

Accessibility support in Experience Builder

Experience Builder framework handles many accessibility concerns automatically, including:

  • Keyboard navigation across pages, layouts, and widgets.
  • Screen reader–friendly page structure.
  • Logical focus order based on layout and DOM order.
  • Accessible window, panel, and dialog behavior.
  • Theme-based color contrast and focus indicators.

Component libraries

Experience Builder contains three primary component systems that shape accessible custom widgets. Using these libraries is the most effective way to meet accessibility requirements.

Jimu UI

Jimu UI is the native React component library in Experience Builder. This library is the default library that ships with the developer edition of Experience Builder.

Jimu UI supports accessibility through:

  • Built with semantic HTML.
  • Keyboard interactions implemented by default.
  • Focus states and tab order managed internally.
  • Proper ARIA roles, states, and properties applied.
  • Designed to work consistently with screen readers.
Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
import { Button } from 'jimu-ui';

export function SubmitButton() {
  return (
    <Button type="primary" onClick={handleSubmit}>
      Submit
    </Button>
  );
}

Calcite Components

Calcite Components are framework-agnostic, accessible web components used across the ArcGIS products.

Calcite Components supports accessibility through:

  • WCAG-aligned color contrast and focus indicators.
  • Keyboard navigation built in.
  • Screen reader announcements handled internally.
  • ARIA Authoring Practices followed for complex widgets.

Calcite Components are ideal when building advanced UI such as dialogs, dropdowns, or panels.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
import '@esri/calcite-components/dist/components/calcite-dialog';

export function ConfirmDialog() {
  return (
    <calcite-dialog open heading="Confirm delete">
      Are you sure you want to delete this item?
    </calcite-dialog>
  );
}

ArcGIS Maps SDK for JavaScript components

You can create map-centric widgets using the ArcGIS Maps SDK for JavaScript components in Experience Builder. This SDK provides a variety of UI components for building map interactions. The Maps SDK components foundationally are built on Calcite Components, and are designed with accessibility in mind.

The Maps SDK components provide support for accessibility such as:

  • Map UI components expose keyboard support where applicable.
  • Popups, feature tables, and editors follow accessible patterns.
  • Screen reader announcements are supported for non-visual interaction.
  • ARIA roles and states are applied as needed.
Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- Load Calcite components and the JavaScript Maps SDK core API-->
<script
  type="module"
  src="https://js.arcgis.com/calcite-components/3.3.3/calcite.esm.js"></script>

<link rel="stylesheet" href="https://js.arcgis.com/4.34/esri/themes/dark/main.css" />
<script src="https://js.arcgis.com/4.34/"></script>

<!-- Load Map components -->
<script
  type="module"
  src="https://js.arcgis.com/4.34/map-components/"
></script>

...

<arcgis-map item-id="05e015c5f0314db9a487a9b46cb37eca"></arcgis-map>

Best practices

When building custom widgets, follow these best practices to ensure accessibility compliance.

Semantic HTML

Semantic HTML is the foundation of accessibility. The recommended approach is to use native elements over generic containers. Native elements provide:

  • Built-in keyboard support.
  • Correct roles and states.
  • Predictable behavior across assistive technologies.
Use dark colors for code blocksCopy
1
2
3
4
5
<!-- Avoid -->
<div onClick={onClick}>Save</div>

<!-- Prefer -->
<button onClick={onClick}>Save</button>

Keyboard and focus management

All widget functionality must be operable using a keyboard. The key principles are:

  • Do not trap keyboard focus unintentionally.
  • Maintain focus after UI updates.
  • Show visible focus indicators.
  • Avoid positive tabindex values.
Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
<button
  ref={buttonRef}
  onClick={() => {
    doAction();
    buttonRef.current?.focus();
  }}
>
  Apply
</button>

Managing composite components

For components such as lists, menus, or tab panels:

  • Only one element should be in the tab order.
  • Use arrow keys for internal navigation.
  • Apply roving tabindex or aria-activedescendant.
Use dark colors for code blocksCopy
1
2
3
4
<ul role="listbox" aria-label="Options">
  <li role="option" aria-selected="true">Option A</li>
  <li role="option">Option B</li>
</ul>

ARIA usage

Use semantic HTML as the first and preferred approach. When native elements cannot fully express the required roles, states, or relationships, use ARIA to supplement but never replace semantic HTML entirely. Follow these guidelines:

Common use cases

  • Providing accessible names when no visible label exists.
  • Communicating expanded or collapsed state.
  • Announcing dynamic updates.
Use dark colors for code blocksCopy
1
2
3
4
5
6
<button
  aria-expanded={isOpen}
  aria-controls="panel-1"
>
  Filters
</button>

ARIA best practices

It is recommended to limit the use of ARIA attributes to necessary cases to ensure optimal accessibility. Follow these guidelines:

  • Do not override native semantics unnecessarily.
  • All interactive elements must be keyboard accessible.
  • Never hide visible, focusable elements with aria-hidden.
  • Ensure every interactive element has an accessible name.

Text, labels, and alternatives

Clear and concise text improves usability for all users. Topics below outline key considerations.

Labels for form controls

Every input must have an accessible label.

Use dark colors for code blocksCopy
1
2
<label for="search">Search</label>
<input id="search" type="text" />

If a visible label is not possible:

Use dark colors for code blocksCopy
1
2
3
4
<input
  type="text"
  aria-label="Search locations"
/>

Images and icons

When using images or icons:

  • Informative images require meaningful alt text.
  • Decorative images must use empty alt attributes.
Use dark colors for code blocksCopy
1
2
3
4
5
<!-- Informative image -->
<img src="warning.svg" alt="This is a warning icon displaying a warning message" />

<!-- Decorative image -->
<img src="divider.svg" alt="" />

Color and contrast

Experience Builder themes are designed to meet contrast requirements, but custom styling must also comply.

Below are requirements from WCAG 2.1:

  • Text contrast ≥ 4.5:1.
  • UI component contrast ≥ 3:1.
  • Do not rely on color alone to convey meaning.

Here is an example of sufficient contrast:

Use dark colors for code blocksCopy
1
2
3
4
5
/* Ensure sufficient contrast */
.button {
  background-color: #005a9c; /* Dark blue */
  color: #ffffff; /* White */
}

Testing and auditing

Automated tools catch only a portion of accessibility issues. It is recommended to use a combination of approaches:

Developer checklist

Before shipping a widget, confirm:

All functionality works with keyboard only.
Focus order is logical and visible.
Accessible names are present.
ARIA roles and states are correct.
No keyboard traps exist.

Code examples

Create an accessible action button

This example shows how to create an accessible button using the Jimu UI component library:

  • Uses semantic <button> internally.
  • Keyboard activation (Enter, Space) supported automatically.
  • Focus indicator provided by theme.
  • Accessible name derived from button text.
  • No ARIA required.

Jimu UI handles keyboard, focus, and ARIA internally, and Experience Builder themes ensure contrast and focus visibility.

Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
import { Button } from 'jimu-ui';

export function ApplyButton({ onApply }: { onApply: () => void }) {
  return (
    <Button
      type="primary"
      onClick={onApply}
    >
      Apply
    </Button>
  );
}

Create an accessible icon-only button

This example demonstrates an accessible icon-only button using Jimu UI:

  • aria-label provides an accessible name
  • Icon is decorative and not announced
  • Button remains keyboard operable
Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
import { Button, Icon } from 'jimu-ui';

export function CloseButton({ onClose }: { onClose: () => void }) {
  return (
    <Button
      icon
      aria-label="Close panel"
      onClick={onClose}>
      <Icon icon="close" />
    </Button>
  );
}

Create an accessible panel

This example shows how to build an accessible panel with a form using Calcite Components. It includes:

  • Labels and names

    • Use CalciteLabel to provide a visible label for the select
    • Screen readers announce the control and its purpose
    • No extra aria-label or aria-labelledby needed when using wrapped components
  • Keyboard support

    • Tab moves between controls
    • Arrow keys navigate options
    • Enter activates buttons
    • Focus order is logical and predictable
  • Focus visibility

    • Calcite applies theme-consistent focus indicators
    • No custom CSS required
    • Meets WCAG focus visibility requirements
  • Semantics and roles

    • Correct roles are applied internally
    • States (expanded, selected) are communicated automatically
    • No risk of incorrect ARIA usage
Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { useState } from 'react';
import {
  CalcitePanel,
  CalciteSelect,
  CalciteOption,
  CalciteButton,
  CalciteLabel
} from '@esri/calcite-components-react';

export function LayerFilterPanel() {
  const [layer, setLayer] = useState('parcels');

  return (
    <CalcitePanel heading="Layer filters" description="Select a layer and apply filters">
      <CalciteLabel>
        Select layer
        <CalciteSelect
          value={layer}
          onCalciteSelectChange={(event: any) => setLayer(event.target.value)}
        >
          <CalciteOption value="parcels">Parcels</CalciteOption>
          <CalciteOption value="buildings">Buildings</CalciteOption>
          <CalciteOption value="roads">Roads</CalciteOption>
        </CalciteSelect>
      </CalciteLabel>

      <CalciteButton appearance="solid" width="full" onClick={() => applyFilter(layer)}>
        Apply filters
      </CalciteButton>
    </CalcitePanel>
  );
}

Create an accessible map with updates and feature details

This example demonstrates how to create an accessible map component using the ArcGIS Maps SDK for JavaScript components. The example:

  • Uses official map component patterns

    • <arcgis-map> can contain other components and connect them via slots.
    • arcgisViewReadyChange / viewOnReady() is the recommended readiness signal before touching map/view state.
  • Uses SDK-supported ARIA for the view container

    • Apply ARIA attributes on the map container (e.g., aria-label, optionally aria-describedby) to provide an accessible name and description.
  • Provides better screen reader experience with features outside the map

    • <arcgis-features> provides popup-style content in a separate container, which is ideal for screen readers and keyboard users.
Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import "@arcgis/map-components/components/arcgis-map";
import "@arcgis/map-components/components/arcgis-search";
import "@arcgis/map-components/components/arcgis-zoom";
import "@arcgis/map-components/components/arcgis-legend";
import "@arcgis/map-components/components/arcgis-feature";

import React, { useEffect, useRef, useState } from "react";

export function AccessibleMapWithDetails() {
  const mapElRef = useRef<HTMLArcgisMapElement | null>(null);
  const [status, setStatus] = useState("Loading map…");

  useEffect(() => {
    const mapEl = mapElRef.current;
    if (!mapEl) return;

    const onViewReady = async () => {
      // Ensure view is ready before interacting (best practice with components)
      await mapEl.viewOnReady();

      // Provide a concise accessible name for assistive technologies
      mapEl.setAttribute("aria-label", "Interactive web map");

      setStatus("Map loaded. Use the Search control to find a place.");
    };
Expand

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.