Skip to header Skip to main navigation Skip to main content Skip to footer
Cookies UI
Alaa Haddad Offers Exceptional Drupal Custom Theming and Modules in Austin TX Alaa Haddad - Drupal Expert
Main navigation
  • Professional Profile
  • Drupal Services
    • Drupal Development
    • Drupal Developer
    • Drupal Themer
    • Drupal Architect
    • Drupal Consultant
  • My Drupal Modules & Themes
      • Cloudflare Purge
      • Solo Copy Blocks
      • W3CSS Paragraphs
      • Paragraphs Bundles
      • Acquia Purge Varnish
      • Reference Blocked Users
      • Module Matrix
      • Paragraphs Bundles Import
      • Selectify
      • Solo Utilities
      • Utilikit
      • Solo
      • Amun
      • Anhur
      • Amunet
      • W3CSS Theme
      • 3D Carousel
      • 3D FlipBox
      • Accordion
      • Carousel
      • Hero
      • Lightbox
      • Parallax
      • Reveal
      • Slideshow
      • Tabs
  • Blog
  • Videos
  • Contact
  • Hire Me (opens in new tab)
Search form
User login
CAPTCHA
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.
  • Reset your password
User account menu
  • Hire Me (opens in new tab)
  • Drupal Services
  • Blog
Site branding
Alaa Haddad - Drupal Expert
Consultant • Architect • Developer • Themer - Expert Drupal Solutions
Article Title Image - Block 1

Scoped vs Global JavaScript Selection, Done Right in Drupal

Content Info - Article Info
 
  10:55 PM CDT, Sun September 13, 2026
Share

Breadcrumbs

Breadcrumb

  • Home
  • JavaScript
  • Scoped vs Global JavaScript Selection, Done Right in Drupal

Main page content

Scoped selection searches inside one container; global selection searches the whole document — and in Drupal the decision is usually made for you, because the same component can appear on a page more than once and you will not be told when it does.

That is what makes it a correctness question rather than a style question. A site builder can place the same block into three regions, embed a Views display twice, add a Paragraph type five times to one node, or drop any of those anywhere with Layout Builder. None of it reaches your JavaScript as a warning. It arrives as a bug report saying "the tabs at the bottom of the page open the ones at the top."

The bug that makes the decision for you

Take a tabbed component. A global implementation collects every tab pane on the page:

const panes = document.querySelectorAll('.tabs-pane');
const buttons = document.querySelectorAll('.tabs-button');

With one instance that is correct and simple. With two, panes holds the panes of both components, so clicking a tab in the second instance closes the panes in the first — the "hide everything, then show one" step hides everything on the page. The failure is also invisible during development, where you almost always build with a single instance in front of you.

The same trap has a Drupal-flavoured cousin: duplicate IDs. If your Twig template hard-codes id="tabs-pane-1", two instances produce two elements with the same ID, document.getElementById() returns whichever the parser saw first, and the markup is invalid HTML into the bargain. This is the same class of problem as the one behind Drupal blocks vs block content and why your paragraphs are duplicating — one authored thing, several rendered copies, and code that assumed there would be one.

Scoped selection, in practice

Every query starts from a container element rather than from document. The container is passed in; nothing inside the function cares what else is on the page.

function handleTabClick(tabContainer, event) {
  const button = event.target.closest('.tabs-button');
  if (!button) return;

  // Every lookup below is bounded by tabContainer.
  const panes = tabContainer.querySelectorAll('.tabs-pane');
  const buttons = tabContainer.querySelectorAll('.tabs-button');
  const pane = tabContainer.querySelector(`#${paneIdFor(button)}`);
  if (!pane) return;

  panes.forEach((p) => { p.style.display = 'none'; p.setAttribute('aria-hidden', 'true'); });
  buttons.forEach((b) => b.setAttribute('aria-selected', 'false'));
  pane.style.display = 'block';
  pane.setAttribute('aria-hidden', 'false');
  button.setAttribute('aria-selected', 'true');
}

Note the third lookup: even the ID is resolved with tabContainer.querySelector('#…'), not document.getElementById(). That way a duplicated ID degrades into "nothing happens here" instead of "the wrong instance responds" — a far cheaper bug to diagnose.

Why :scope > matters once components nest

A container is not enough on its own if the component can contain another copy of itself — a tab pane holding a Views block that is itself tabbed, which happens more often than you would like. container.querySelector('.inner') searches all descendants and will reach into the nested instance. The :scope pseudo-class anchors the selector to the element you called the method on:

// Only the direct child wrapper, never a nested instance's wrapper.
this._inner = this.querySelector(':scope > .vvjt-inner');

That line is from js/vvjt-tabs-element.js in VVJ Tabs, a Views display-format module I maintain. One extra token, and it is the difference between a component that can be nested and one that cannot.

Selection bugs like these surface late, usually in a behaviour somebody added under deadline. Drupal development covers reviewing that kind of code before it ships.

Where global selection is still the right answer

Scoping is not a rule to apply everywhere. Three cases genuinely call for a document-wide reach:

  • Genuinely page-level singletons. A skip link, a back-to-top button, the one status-message region. There is one per document by definition, and pretending otherwise adds ceremony for nothing.
  • Cross-component coordination. Closing every open dropdown on Escape requires knowing about all of them. Do it by dispatching an event on document that each instance listens for, not by reaching into other instances' internals.
  • Browser APIs that are only global. document.startViewTransition() animates the document; there is no scoped version. Even a strictly scoped component calls that one on document.

The test I use is simple: if a second copy of this component appeared on the page, would this line still be correct? If yes, global is fine. If no, scope it.

The Drupal layer everyone forgets: context and once()

This is the part that turns a working component into an intermittent one. Drupal does not run your JavaScript once on page load. Drupal.attachBehaviors() runs on load, and then again after every AJAX response, every BigPipe placeholder that arrives, every Views exposed-filter refresh and every off-canvas dialog. Core's own documentation in core/misc/drupal.js spells out the contract: behaviours should use once('behavior-name', selector, context) so that a behaviour attaches only once to a given element.

Drupal.behaviors.myTabs = {
  attach(context) {
    // `context` is the new fragment on an AJAX response, `document` on load.
    once('my-tabs', '.tabs-wrapper', context).forEach((container) => {
      container.addEventListener('click', (e) => handleTabClick(container, e));
    });
  },
};

Two mistakes are worth naming because both look fine in testing:

  1. Querying document instead of context. On an AJAX refresh the behaviour re-processes the whole page, not just the new fragment. With a missing once() as well, you attach a second click listener to every existing element and the component starts firing twice, then three times.
  2. Skipping once() because "it only runs on load." It does, until someone enables BigPipe or adds a pager, and then it runs again with no code change of yours to blame.

They solve two halves of one problem: once() stops you binding twice, scoping stops you binding to the wrong thing. You need both. The same discipline shows up in form widgets — it is the approach behind Selectify's five custom form elements.

Side by side

Scoped versus global DOM selection, judged on the things that break in production
ConcernScoped selectionGlobal selection
Multiple instances on a pageCorrect by constructionBreaks silently
Duplicate IDs in markupDegrades to a no-op in that instanceWrong instance responds
Nested instancesSafe with :scope >Unsafe
Search costBounded by the subtreeWhole document each call
Drupal AJAX and BigPipePairs naturally with contextRe-processes the whole page
Code overheadContainer must be passed or heldNone
Best fitAnything a site builder can place twicePage singletons and document-level APIs

How I removed the choice entirely

In VVJ Tabs 1.x the container was a function parameter, exactly as above. That worked, but it relied on every future contributor threading it through. In 2.x the component is a custom element, <vvjt-tabs>, and the container stops being a parameter because it is this. Three consequences are worth stealing even if you never write a custom element:

  • Scope becomes structural. There is no document to reach for by accident; the natural thing to type is this.querySelector().
  • Teardown becomes automatic. Every listener is registered with { signal: this.signal } from an AbortController that disconnectedCallback() aborts. When Drupal's AJAX replaces the markup, the listeners go with it — no detach bookkeeping.
  • Re-attachment stays explicit. After revealing a pane the element calls Drupal.attachBehaviors(activePane, drupalSettings), so a nested Views block or field group inside it wakes up. That is the one place a component legitimately reaches outward, and passing the pane rather than document keeps even that scoped.

What is left of Drupal.behaviors does nothing but mark the elements with once(). Everything else lives in the element's own lifecycle.

Common questions

Can I just use unique IDs from the Twig template instead?

You should generate unique IDs anyway, because duplicates are invalid HTML and break aria-controls and aria-labelledby. But unique IDs plus global lookups still leave you doing string surgery to work out which instance you are in. Scoping removes the question instead of answering it.

What about jQuery's $(selector, context)?

Same idea, and it worked. The reason not to reach for it in new Drupal code is that jQuery is no longer a given, and pulling it in solely to scope a query is a large bill for one argument. element.querySelectorAll() is the same operation with no dependency.

Does any of this affect accessibility?

Directly. The attributes that make a tab set usable — aria-selected, aria-expanded, aria-controls, roving tabindex — are all per-instance state. A global implementation sets them on every instance at once, so a screen reader is told two different tabs are selected. If you are already thinking about announcements, ARIA live regions for Drupal 11 system messages covers the other half of that problem.

How do I test for this without building a second instance by hand?

Place the block twice in different regions, or add a second Views embed, then drive the first instance and watch the second. If you can, nest one inside the other — that catches the descendant-versus-child bug that plain scoping misses.

Next step

Open the most interactive component on your site and check three things: does it query document, does its behaviour use context, and does it call once()? Any one of those missing is a bug waiting for a site builder to trip it.

If you would rather someone else made that pass, this is the kind of Drupal development work I do, and a quote needs only a short description — what the component is, how many can appear on a page, and what it currently does wrong.

JavaScript
Slide 1 of 27
Acquia Purge Varnish - API V2 (Drupal Module)
Slide 2 of 27
Amun - W3CSS Sub-Theme (Drupal Theme)
Slide 3 of 27
Amunet - W3CSS Sub-Theme (Drupal Theme)
Slide 4 of 27
Anhur - W3CSS Sub-Theme (Drupal Theme)
Slide 5 of 27
Cloudflare Purge (Drupal Module)
Slide 6 of 27
Module Matrix (Drupal Module)
Slide 7 of 27
Paragraphs Bundles (Drupal Module)
Slide 8 of 27
Paragraphs Bundles Import (Drupal Module)
Slide 9 of 27
Reference Blocked Users (Drupal Module)
Slide 10 of 27
Selectify (Drupal Module)
Slide 11 of 27
Solo (Drupal Theme)
Slide 12 of 27
Solo Copy Blocks (Drupal Module)
Slide 13 of 27
Solo Utilities (Drupal Module)
Slide 14 of 27
Utilikit (Drupal Module)
Slide 15 of 27
Views 3D Carousel (Drupal Module)
Slide 16 of 27
Views 3D FlipBox (Drupal Module)
Slide 17 of 27
Views Accordion (Drupal Module)
Slide 18 of 27
Views Carousel (Drupal Module)
Slide 19 of 27
Views Hero (Drupal Module)
Slide 20 of 27
Views Lightbox (Drupal Module)
Slide 21 of 27
Views Parallax (Drupal Module)
Slide 22 of 27
Views Reveal (Drupal Module)
Slide 23 of 27
Views Slideshow (Drupal Module)
Slide 24 of 27
Views Tabs (Drupal Module)
Slide 25 of 27
VVJ Core (Drupal Module)
Slide 26 of 27
W3CSS Paragraphs (Drupal Module)
Slide 27 of 27
W3CSS Theme (Drupal Theme)
1 of 27

Our mission is to make Drupal more accessible and user-friendly, empowering businesses of all sizes, especially small enterprises with powerful tools that streamline content customization and enhance digital experiences.

Need help with your Drupal project? Hire Me through Flash Web Center, LLC.

Search

The Drupal 11 Module and Theme Stack, With Versions Checked

ARIA Live Regions in Drupal 11: What Core Does, What You Add

Drupal Architect: The Decisions Expensive to Reverse

Drupal Services: Development, Theming, Upgrades | Austin TX

Paragraphs Bundles

Drupal Module - Paragraphs Bundles

Drupal Theme - Solo

Drupal Theme - Solo

Drupal Work List - Drupal Work

Solo Utilities (Drupal Module)

Reference Blocked Users (Drupal Module)

Solo Copy Blocks (Drupal Module)

Views Hero (Drupal Module)

Amun - W3CSS Sub-Theme (Drupal Theme)

Drupal Theme - W3CSS Theme

Drupal Theme - W3CSS Theme

Drupal Module - W3CSS Paragraphs

Drupal Module - W3CSS Paragraphs

Inspiration

Inspiration is the fuel that powers our creative engine, often coming from our surroundings, experiences, or the works of others. It's that magical moment when something clicks inside your brain, and you suddenly see a path forward that you hadn't noticed before. Inspiration can strike at any time, providing the motivation and energy needed to explore new possibilities and bring your ideas to life.

Unique Ideas

Unique Ideas

Unique ideas are the seeds of innovation, representing original thoughts or concepts that stand out from the usual. They're the sparks that ignite the process of creating something new and different, often leading to unexpected and groundbreaking solutions or products. Whether in art, science, business, or technology, unique ideas challenge the status quo and pave the way for progress.

Brainstorming

Brainstorming

Brainstorming is a creative group activity designed to generate a large number of ideas or solutions to a problem. It's a free-flowing and open-ended discussion where every suggestion is welcomed and considered, no matter how outlandish it may seem. Brainstorming encourages thinking outside the box, fostering an environment where creativity and collaboration lead to innovative solutions.

Planning

Planning

Planning is the blueprint for turning your ideas into reality. It involves setting goals, outlining steps, and organizing resources in a way that makes achieving your objectives possible. Good planning considers potential challenges and opportunities, making it easier to navigate the journey from concept to completion. It's about preparing the groundwork so that your projects can grow and flourish.

Drupal Developer

A Drupal Developer stands as the technical powerhouse behind dynamic websites, wielding expertise in PHP, custom module development, and Drupal's sophisticated API ecosystem. This role transforms business requirements into functional, secure, and scalable web solutions that power everything from small business sites to enterprise platforms serving millions of users. A Drupal Developer's expertise spans the entire development lifecycle—from architecting custom modules and integrating third-party services to optimizing performance and ensuring security compliance. Discover the complete guide to Drupal Developer skills, career paths, and hiring strategies.

Drupal Themer

A Drupal Themer serves as the artistic craftsperson who transforms wireframes and design mockups into pixel-perfect, accessible, and responsive user experiences using Twig templates, CSS, and JavaScript. This specialized role bridges the gap between design vision and technical implementation, ensuring every website not only looks exceptional but performs flawlessly across all devices and meets WCAG accessibility standards. A Drupal Themer's work encompasses the entire front-end ecosystem—from creating custom theme architectures and optimizing Core Web Vitals to implementing complex responsive designs and ensuring seamless integration with Drupal's rendering system. Explore the comprehensive guide to Drupal Themer expertise, theming best practices, and career advancement.

Drupal Architect

A Drupal Architect emerges as the strategic visionary of web construction, armed with encyclopedic knowledge of enterprise architecture patterns, infrastructure design, and Drupal's extensive technological ecosystem. This senior role operates at the highest technical level, making critical decisions that determine whether complex implementations scale successfully or collapse under real-world demands. A Drupal Architect's responsibilities begin long before development starts—during strategic planning phases where the foundation for secure, performant, and maintainable systems is established through careful evaluation of technology stacks, integration strategies, and scalability requirements. Learn about Drupal Architect skills, enterprise architecture strategies, and career progression to principal roles.

Footer menu

  • Contact
  • Professional Resume
  • Resume Summary
  • Technical Skills
  • Privacy Policy
  • Terms & Conditions
  • Search
  • Login
  • Sitemap

Copyright © 2026 Flash Web Center, LLC - All rights reserved

Developed & Designed by Alaa Haddad