JavaScript can improve an interface, but every interaction must preserve the semantics, keyboard operation, focus behavior, and status information people need. Test the rendered component in every state. The presence of JavaScript, ARIA, or an automated test result does not establish that an interaction is accessible.

Start with native HTML

Use the native element that matches the interaction whenever possible. Buttons, links, form controls, details and summary, and dialogs provide semantics and browser behavior that a generic div or span does not.

Not every application feature has to work without JavaScript. However, the initial page and failure state must remain understandable. Do not leave an empty container, an inoperable control, or instructions for an interface that was not created.

ARIA can expose a role, name, property, or state. It does not add keyboard behavior, focus management, or event handling. A custom ARIA control is only complete when its full interaction pattern is implemented and tested.

Attach Drupal JavaScript correctly

Use Drupal.behaviors so an enhancement runs on the initial page and after content is added by Ajax or BigPipe. Use once() when each matching element should be processed only once.

(function (Drupal, once) {
  Drupal.behaviors.example = {
    attach: function (context) {
      once('example', '[data-example]', context).forEach(function (element) {
        // Enhance each matching element once.
      });
    }
  };
})(Drupal, once);

Declare core/drupal and core/once as library dependencies. Add jQuery only when the implementation actually needs it. Code that runs only on the first document-ready event can miss content added later. Code that does not use once() where needed can add duplicate handlers, announcements, or controls.

If a behavior adds global event listeners, timers, or other state that must be removed, implement the behavior's detach function as well. Test repeated attachment and removal, not only the initial page load.

Keep names, roles, and states accurate

Every interactive element needs an accessible name that describes its purpose. Prefer visible text associated with the native control. When an ARIA label is necessary, keep it consistent with the visible label.

Update programmatic states at the same time as the visible interface. Depending on the component, this can include aria-expanded, aria-selected, aria-pressed, aria-checked, aria-current, or aria-busy. Use only properties that apply to the element's role and interaction pattern.

Do not expose an element as a control before it is operable. Do not leave a stale expanded, selected, busy, or invalid state after the visual state changes.

Provide complete keyboard operation

People must be able to reach and operate every interactive component with a keyboard. Native controls already provide expected keyboard behavior. Custom components must reproduce the applicable interaction pattern.

Test keyboard behavior against the relevant ARIA Authoring Practices Guide pattern. Treat its example code as a pattern demonstration, not as a Drupal component that can be copied without integration and testing.

Manage focus when context changes

Do not move focus merely because content changed. Move it when an action creates a new context or when the focused element no longer exists.

Do not use focus as a substitute for a status message. Focus movement changes the person's location and can interrupt their task.

Communicate dynamic updates

A visual change is not necessarily announced by assistive technology. Use native states and relationships first. Use a status message when important information is added without moving focus.

Drupal provides Drupal.announce() for messages that need to be exposed through an ARIA live region:

Drupal.announce(Drupal.t('The results have been updated.'));

The default polite priority waits for the current announcement to finish. Use the assertive priority only for urgent information that must interrupt it. Announce the concise result of the action, not the whole updated region. Translate user-facing messages with Drupal.t().

Do not announce every loading step, keystroke, or visual change. Repeated or redundant messages make an interface harder to use. If a region is being updated, aria-busy="true" can indicate that its changes are not complete, but it does not replace a useful completion message.

Use established components and patterns

Use Drupal core components and established interaction patterns before building a custom widget. A component must remain accessible in every state, not only when it is closed or at rest.

Dialogs

A modal dialog needs an accessible name, focus inside it when opened, keyboard containment while open, an operable close mechanism, and predictable focus restoration. Content behind a modal dialog must not remain available to keyboard or assistive-technology navigation. Prefer Drupal's dialog facilities to a new implementation, then test the rendered result in its actual theme and context.

Disclosures

Use a button to show or hide content. Keep its aria-expanded value synchronized with the visible state. Associate it with the controlled content when that relationship is useful. Opening a disclosure does not usually require moving focus.

Content on hover or focus

Tooltips, menus, and other content that appears on hover or focus must be dismissible without moving pointer or keyboard focus, remain available when the pointer moves over the added content, and persist until dismissed, no longer relevant, or invalid. Do not make information available only on hover.

Handle Ajax and form updates

Ajax must not remove the structure and feedback a form needs. Preserve values, labels, instructions, errors, and focus as the form changes. Identify invalid fields programmatically and provide a useful error summary when several errors occur. Announce progress or completion when it is important and is not otherwise communicated.

Loading indicators must have a text alternative or an equivalent status message. A spinner alone does not tell a screen-reader user what is happening. See Building accessible forms for form-specific guidance.

Support pointer and touch operation

Do not require precise pointer movement, hover, or dragging as the only way to complete an action.

Limit motion and flashing

Provide a way to pause, stop, or hide moving, blinking, scrolling, or automatically updating information when WCAG requires it. Do not start decorative motion that continues alongside other content without a control.

Respect prefers-reduced-motion. Reduce or replace non-essential transitions, parallax effects, zooming, and motion triggered by interaction. Preserve necessary state changes and feedback instead of hiding them. WCAG's Animation from Interactions success criterion is Level AAA, so it is aspirational for a project targeting AA unless the project adopts it as an additional requirement.

Do not create content that flashes more than three times in any one-second period unless it is below the WCAG flash thresholds. Avoid flashing content rather than relying on threshold calculations.

Review third-party widgets

An external library, embedded service, or contributed module does not transfer responsibility for accessibility to its maintainer. Review its output, keyboard behavior, focus, announcements, zoom behavior, touch operation, reduced-motion support, and high-contrast presentation. Test updates before deployment and track unresolved barriers like other defects.

A conformance claim in a vendor document is not evidence that the configured widget works in the Drupal page where it is used.

Test interactive components

Automated tools can detect some missing names, invalid ARIA relationships, focusable hidden content, and other deterministic failures. They cannot determine whether a custom interaction is understandable, whether focus moves appropriately, whether an announcement is useful, or whether every state is operable. Barriers affect people, WCAG success criteria define requirements, and tools test only a limited set of rules. Only a smaller high-confidence subset is suitable for an automated gate.

Related guidance