Drupal is a registered trademark of Dries Buytaert
cms 2.1.3 Update released for Drupal core (2.1.3)! drupal 10.5.11 Update released for Drupal core (10.5.11)! drupal 11.3.11 Update released for Drupal core (11.3.11)! drupal 11.2.13 Update released for Drupal core (11.2.13)! drupal 10.6.10 Update released for Drupal core (10.6.10)! cms 2.1.2 Update released for Drupal core (2.1.2)! drupal 11.1.10 Update released for Drupal core (11.1.10)! drupal 10.5.10 Update released for Drupal core (10.5.10)! drupal 10.4.10 Update released for Drupal core (10.4.10)! drupal 11.2.12 Update released for Drupal core (11.2.12)! drupal 11.3.10 Update released for Drupal core (11.3.10)! drupal 10.6.9 Update released for Drupal core (10.6.9)! drupal 10.6.8 Update released for Drupal core (10.6.8)! drupal 11.3.9 Update released for Drupal core (11.3.9)! drupal 11.3.8 Update released for Drupal core (11.3.8)! drupal 11.3.7 Update released for Drupal core (11.3.7)! drupal 11.2.11 Update released for Drupal core (11.2.11)! drupal 10.6.7 Update released for Drupal core (10.6.7)! drupal 10.5.9 Update released for Drupal core (10.5.9)! cms 2.1.1 Update released for Drupal core (2.1.1)!

smart_popup

No security coverage
View on drupal.org

Independent, smooth, smart-positioning, sticky and floating vanilla JS tap, touch, click-interactivity overlay solution, all-in-one for Drupal 10, 11, 12 and above, to create a application-alike feel on additional information related to a part of the context. With extendable themes based on class names, optional flipping arrows, scroll behavior and with full Drupal use-ajax compatibility. Zero third-party libraries.

The goal is to have a suite of vanilla and independent modern Javascript behavior at hand to easily implement popup-alike modals, badges, info bubbles, tooltips and such into an existing Drupal project without disturbing Drupal's use-ajax system. Mainly with admin dashboards and shops in mind.

Features

  • 6 themes: default, soft, slate, bordered, elevated, sharp (all opaque, no gradients - varied by radius, border and shadow)
  • Optional flipping arrow anchored to the trigger
  • Smart positioning - auto-picks the side with the most room
  • Sticky to trigger on scroll - stays in viewport as long as possible, flips when the trigger leaves
  • Reflows on resize / viewport change (uses ResizeObserver when available)
  • Closes only on: outside click Escape key, the in-popup button
  • Preserves use-ajax - Drupal.attachBehaviors is called on popup content; existing AJAX, forms and behaviors keep working inside the popup
  • No HTML strings in JS - all popup DOM is built with document.createElement
  • No third-party libraries - vanilla JS only

Install

  1. Copy the smart_popup/ folder into web/modules/contrib/.
  2. Enable the module: drush en smart_popup (or via /admin/modules).
  3. Or use composer require drupal/smart_popup and drush en smart_popup

How it works (3 pieces)

  1. A hidden source element: any DOM node with an id. A <template> is recommended. But can be <div><span>, you name it.
  2. A trigger element - anything with data-smart-popup-trigger="source-id".
  3. The library - smart_popup/smart_popup attached to the page.

That's it. Click the trigger and the smart popup is built from the source, positioned next to the trigger, and stays near the trigger, where ever you scroll and will close only on click or tap outside, or by pressing "Escape", or by clicking the close button.

Required attribute

Attribute Purpose data-smart-popup-trigger ID of the source element to clone into the popup

Optional attributes

Attribute Values Default data-smart-popup-placement auto, top, bottom, left, right auto data-smart-popup-arrow 1, 0 1

Theme classes (on the trigger)

smart-popup--theme-default - smart-popup--theme-soft - smart-popup--theme-slate - smart-popup--theme-bordered - smart-popup--theme-elevated - smart-popup--theme-sharp

Usage

A. Twig (in a template, block, or node body)

{{ attach_library('smart_popup/smart_popup') }}

<button type="button"
        class="smart-popup-trigger smart-popup--theme-bordered smart-popup--has-arrow"
        data-smart-popup-trigger="sp-source-hello"
        data-smart-popup-placement="auto"
        data-smart-popup-arrow="1">
  Show details
</button>

<template id="sp-source-hello" class="smart-popup-source">
  <h3>Hello from Smart Popup</h3>
  <p>Any markup goes here - including <a href="/node/1" class="use-ajax">use-ajax links</a>.</p>
</template>

B. Render array (controller, block, preprocess)

$build['trigger'] = [
  '#theme'       => 'smart_popup_trigger',
  '#label'       => $this->t('More info'),
  '#target_id'   => 'sp-source-info',
  '#theme_class' => 'smart-popup--theme-slate',
  '#arrow'       => TRUE,
  '#placement'   => 'auto',
  '#trigger_tag' => 'button',
];

$build['source'] = [
  '#theme'   => 'smart_popup_content',
  '#id'      => 'sp-source-info',
  '#content' => ['#markup' => '<p>Hello!</p>'],
];

$build['#attached']['library'][] = 'smart_popup/smart_popup';

C. Views

  1. Add a Custom text field with the <template> source.
  2. Add another Custom text field with the trigger markup pointing at the same id.
  3. In the view's Advanced ? CSS class or via a preprocess hook, attach smart_popup/smart_popup.

D. Content (CKEditor)

If your text format allows the attributes and <template> tag, you can paste the markup directly. Make sure smart_popup/smart_popup is attached on the page (e.g. via a block, or globally in your theme's *.libraries.yml).

use-ajax compatibility

When a popup opens, the module calls:

Drupal.attachBehaviors(popupElement, drupalSettings);

so every Drupal behavior - use-ajax, ajax forms, tooltips, custom behaviors - is attached to the popup content. On close, Drupal.detachBehaviors(popupElement, drupalSettings, 'unload') is called. No existing use-ajax behavior on the page is disabled or replaced.

Public JS API

Drupal build (window.Drupal.smartPopup):

Drupal.smartPopup.open(triggerElement);
Drupal.smartPopup.closeAll();
Drupal.smartPopup.reposition();

Standalone build (window.SmartPopup):

SmartPopup.init();            // re-scan after DOM changes
SmartPopup.open(triggerEl);   // open programmatically
SmartPopup.closeAll();
SmartPopup.reposition();
SmartPopup.onOpen = (contentEl, entry) => { /* hook */ };

Demo

  • Open demo.html inside module folder in a browser for a live, framework-free demonstration.
  • Or go to http://smart-popup.dev

License

GPLv3 - see LICENSE.md.

Activity

Total releases
1
First release
Jun 2026
Latest release
5 days ago
Release cadence
Stability
0% stable

Releases

Version Type Release date
1.0.x-dev Dev Jun 8, 2026