Skip to content

JavaScript in SDCs

When to Use

  • You're adding interactive behavior to components
  • You need to use Drupal.behaviors pattern
  • You're integrating with once() or other Drupal JS APIs

Decision

Attach behavior with Drupal.behaviors and scope every query to the passed context, guarded with once() to prevent duplicate initialization across AJAX/BigPipe attaches. Implement detach to clean up.

Pattern

Pattern: Drupal.behaviors with once()

Reference: /themes/contrib/radix/ JavaScript patterns

/**
 * @file
 * Component behavior for my-component.
 */

(function (Drupal, once) {
  'use strict';

  /**
   * Attaches component behavior.
   */
  Drupal.behaviors.myComponent = {
    attach: function (context, settings) {
      // Use once() to prevent multiple initialization
      once('my-component', '.my-component', context).forEach(function (element) {

        // Component initialization logic here
        element.addEventListener('click', function (event) {
          // Handle event
        });

      });
    },

    detach: function (context, settings, trigger) {
      // Cleanup when behavior is detached
      if (trigger === 'unload') {
        // Remove event listeners, destroy instances
      }
    }
  };

})(Drupal, once);

Pattern: Library Dependencies

Declare JS dependencies in component YAML. There is no libraryDependencies key — see Component YAML Schema.

libraryOverrides:
  dependencies:
    - core/drupal
    - core/once
    - core/drupal.ajax
  js:
    my-component.js:
      attributes: { defer: true }
      preprocess: false

Pattern: Progressive Enhancement

Assume component works without JavaScript, enhance with JS.

Drupal.behaviors.myComponent = {
  attach: function (context) {
    once('my-component-enhanced', '.my-component', context).forEach(function (element) {

      // Add enhanced functionality marker
      element.classList.add('my-component--js-enhanced');

      // Initialize interactive features
      const toggle = element.querySelector('.my-component__toggle');
      if (toggle) {
        toggle.addEventListener('click', function (event) {
          event.preventDefault();
          element.classList.toggle('my-component--expanded');
        });
      }

    });
  }
};

Common Mistakes

Common Mistake: Not using once() to prevent duplicate initialization. WHY: Drupal.behaviors can attach multiple times (AJAX, BigPipe). Without once(), event listeners get added multiple times, causing bugs.

Common Mistake: Querying entire document instead of scoped context. WHY: Drupal passes context to limit behavior to new content. Ignoring it causes performance issues and processes elements multiple times.

Common Mistake: Not implementing detach method. WHY: Without cleanup, event listeners and instances persist after elements removed, causing memory leaks.

See Also

  • Reference: /core/misc/drupal.js — Core Drupal JavaScript
  • Reference: /core/assets/vendor/once/once.jsonce() implementation
  • Performance
  • Drupal JavaScript API