jQuery Migration
ArchitectUI is in the middle of a multi-year migration away from jQuery to vanilla JavaScript and Bootstrap 5 native APIs. This page covers the current state, what's already been replaced, what's still pending, and how to write new code that doesn't fight the migration.
Current State
As of v4.8.1, ArchitectUI ships jQuery 3.7.1 as a compatibility layer for plugins that haven't yet been replaced. Roughly 76% of the original jQuery plugin dependency footprint has been removed, leaving four remaining holdouts (MetisMenu, Slick Carousel, Fancytree, Bar Rating) where vanilla alternatives don't yet match the feature set.
jQuery 4.0 was released in 2026 but is incompatible with Bootstrap 5.3's ESM bundle and Webpack's default-export interop. ArchitectUI will revisit the jump to jQuery 4 when Bootstrap ships a compatible release, or when the remaining jQuery plugins have been replaced and jQuery can be removed entirely.
Replacements Already Shipped
The following plugins have been removed from package.json and replaced with native or vanilla alternatives. Use the replacement when adding new code — do not reintroduce the deprecated plugins.
| Removed | Replacement | Migration version |
|---|---|---|
bootstrap4-toggle | Bootstrap 5 native form switches (.form-check.form-switch) | v4.6.0 |
jquery-circle-progress | ApexCharts radialBar charts | v4.6.0 |
jquery-sparkline | ApexCharts sparklines | v4.6.0 |
jquery-validation | Bootstrap 5 native validation (was-validated, is-invalid) | v4.6.0 |
jquery-cropper | Cropper.js 2 (vanilla) | v4.6.0 |
select2 | Bootstrap 5 native selects + src/utils/multiselect.js | v4.6.0 |
toastr | Bootstrap 5 native toasts + src/utils/toast.js | v4.6.0 |
inputmask | Native HTML5 patterns + src/utils/input-formatter.js | v4.6.0 |
block-ui | Native JS — src/utils/block-ui-native.js | v4.7.0 |
| DataTables jQuery init | new DataTable('#id', opts) constructor | v4.7.0 |
bootstrap-tour | Removed (unused) | v4.5.0 |
bootstrap-select | Removed (unused) | v4.5.0 |
bootstrap-multiselect | Bootstrap 5 dropdowns | v4.5.0 |
What Still Depends on jQuery
Four plugins still require jQuery at runtime:
| Plugin | Used by | Why not replaced |
|---|---|---|
| MetisMenu 3.0.7 | Sidebar nav | Provides collapse/expand animations and nested menu state. Replacing it requires either Bootstrap 5 collapse (less polished) or a custom rewrite. |
| Slick Carousel 1.8 | Carousel showcase, dashboard sliders | Feature-complete carousel with no equivalent vanilla library. Swiper or Embla would be replacement candidates but require significant API rework. |
| jQuery Fancytree 2.38 | Tree view showcase | Mature tree library with checkboxes, lazy loading, drag-drop. Vanilla alternatives (e.g. Inspire-Tree) exist but feel less polished. |
| jQuery Bar Rating 1.2 | Ratings showcase | Star/bar rating control with theme support. Could be replaced with a vanilla rewrite (~100 lines). |
Additionally, src/app.js still uses jQuery for selectors and event delegation in many places — the file is on the migration roadmap but hasn't been touched yet.
The Custom Dropdown Portal
ArchitectUI's biggest custom JavaScript is the dropdown portal logic in src/app.js (lines 139–234). It replaces Bootstrap's native dropdown plugin with a delegation-based handler that "portals" page dropdowns to document.body to escape parents with overflow: hidden or transform stacking contexts.
Header dropdowns (anything inside .app-header, .header-megamenu, .header-dots, or .header-btn-lg) are left in place because they need to inherit header styling.
Implementation Pattern
document.addEventListener('click', function(event) {
var trigger = event.target.closest('[data-bs-toggle="dropdown"]');
if (trigger) {
event.preventDefault();
event.stopPropagation();
// Close any other open dropdowns first
// ...
var menu = trigger.nextElementSibling;
var isHeaderDropdown = trigger.closest(
'.app-header, .header-megamenu, .header-dots, .header-btn-lg'
);
if (isHeaderDropdown) {
// In-place show
menu.classList.add('show');
} else {
// Portal to body with position: fixed
var triggerRect = trigger.getBoundingClientRect();
menu._originalParent = menu.parentNode;
menu._originalNextSibling = menu.nextSibling;
document.body.appendChild(menu);
menu.style.position = 'fixed';
menu.style.top = (triggerRect.bottom + 2) + 'px';
menu.style.left = triggerRect.left + 'px';
menu.style.zIndex = '99999';
menu.classList.add('show');
}
}
// ...
});
Don't add new bootstrap.Dropdown(el) calls anywhere — they will fight this handler. Markup with data-bs-toggle="dropdown" works automatically.
Writing New Code: Vanilla First
When adding new feature initializers under src/scripts-init/, prefer vanilla DOM APIs over jQuery. The pattern:
// src/scripts-init/my-feature.js
document.addEventListener("DOMContentLoaded", function () {
const triggers = document.querySelectorAll("[data-my-feature]");
if (!triggers.length) return; // safe no-op on pages without the trigger
triggers.forEach((trigger) => {
trigger.addEventListener("click", function (event) {
event.preventDefault();
// your code here
});
});
});
API Mapping Cheat Sheet
| jQuery | Vanilla equivalent |
|---|---|
$(".foo") | document.querySelectorAll(".foo") |
$el.addClass("x") | el.classList.add("x") |
$el.toggleClass("x") | el.classList.toggle("x") |
$el.hasClass("x") | el.classList.contains("x") |
$el.attr("href") | el.getAttribute("href") |
$el.data("foo") | el.dataset.foo |
$el.text("hi") | el.textContent = "hi" |
$el.html("<b>hi</b>") | el.innerHTML = "<b>hi</b>" |
$el.on("click", fn) | el.addEventListener("click", fn) |
$(document).ready(fn) | document.addEventListener("DOMContentLoaded", fn) |
$el.closest(".foo") | el.closest(".foo") |
$el.find(".bar") | el.querySelectorAll(".bar") |
$el.parent() | el.parentElement |
$.ajax({...}) | fetch(url, options).then(r => r.json()) |
Why jQuery Is Still Around
Two reasons:
- The four remaining plugins (MetisMenu, Slick, Fancytree, Bar Rating) require jQuery. Removing jQuery means replacing all four with vanilla alternatives, each of which is its own migration project.
src/app.jshas many jQuery callsites that still need to be ported — sidebar toggle, drawer toggle, search-icon click, popover initialization, etc. This is straightforward but tedious work that hasn't been scheduled yet.
Once both are resolved, jQuery can be removed entirely — ProvidePlugin entries cleaned out of webpack.config.js, the jquery dependency dropped from package.json, and the vendor bundle gets ~30 KB smaller.
Suppressing jQuery on a Specific Page
If you have a page that doesn't use any of the jQuery-dependent plugins, you can theoretically skip loading the main bundle. In practice this is rarely worth the complexity — HtmlWebpackPlugin's default behavior is to inject every entry into every page, and customizing per-page chunk selection requires touching webpack.config.js.
The cleaner answer is to wait until jQuery is fully removed.