jQuery → Vanilla ES6 Migration

⚡ In short
This site was migrated from jQuery to native JavaScript (Vanilla ES6). An 87 KB external dependency removed, replaced by modern code that does exactly the same thing — no extra download, no third-party library.
For visitors, nothing changes. For security and performance, it’s a measurable improvement.
🧠 Why
jQuery was essential in 2010 when browsers all behaved differently. In 2026, modern browsers natively support everything jQuery did:
$(selector)→document.querySelector()$.ajax()→fetch()$(el).on('click', fn)→el.addEventListener('click', fn)$(document).ready()→document.addEventListener('DOMContentLoaded', fn)
Keeping jQuery today means loading 87 KB of code you no longer need.
🔧 What was done
The default Grav theme (Quark) used jQuery for four functions:
- The scrolled header — add/remove a CSS class when scrolling the page
- The parallax effect — offset the background based on scroll position
- The mobile menu — open/close the navigation overlay on click
- The tree menu — manage submenu toggling in mobile navigation
Each was rewritten in native ES6 JavaScript, with no external dependencies. An independent arleo theme was created from Quark, then Quark was removed from the server.
🔍 Before / After — JS loading
Before:
<!-- Loading jQuery (87 KB) -->
<script src="/system/assets/jquery/jquery-3.x.min.js"></script>
<!-- jQuery-dependent treemenu plugin -->
<script src="/user/themes/quark/js/jquery.treemenu.js"></script>
<!-- Application code using $ -->
<script src="/user/themes/quark/js/site.js"></script>After:
<!-- Single file, loaded deferred -->
<script src="/user/themes/arleo/js/site.js" defer></script>⚙️ Migration example — mobile menu
Before (jQuery):
jQuery(document).ready(function($) {
$('#toggle').click(function() {
$(this).toggleClass('active');
$('#overlay').toggleClass('open');
$('body').toggleClass('mobile-nav-open');
});
});After (Vanilla ES6):
document.addEventListener('DOMContentLoaded', () => {
const toggle = document.getElementById('toggle');
const overlay = document.getElementById('overlay');
if (toggle && overlay) {
toggle.addEventListener('click', () => {
toggle.classList.toggle('active');
overlay.classList.toggle('open');
document.body.classList.toggle('mobile-nav-open');
});
}
});⚙️ Tree menu without a plugin
The jquery.treemenu.js plugin was replaced by a native implementation:
document.querySelectorAll('.tree li').forEach(item => {
const children = item.querySelector('ul');
if (children) {
item.classList.add('tree-closed');
const toggler = document.createElement('span');
toggler.className = 'toggler';
item.insertBefore(toggler, item.firstChild);
toggler.addEventListener('click', () => {
setTimeout(() => {
item.classList.toggle('tree-closed');
item.classList.toggle('tree-opened');
}, 300);
});
}
});⚙️ Creating an independent theme
# Copy Quark to arleo
cp -r /var/www/grav/user/themes/quark /var/www/grav/user/themes/arleo
# Remove jQuery from the base template
# partials/base.html.twig: javascripts block rewritten
# Activate the theme
sed -i 's/theme: quark/theme: arleo/' /var/www/grav/user/config/system.yaml
# Remove Quark
rm -rf /var/www/grav/user/themes/quark🏁 Conclusion
| Criterion | Before | After |
|---|---|---|
| Scripts loaded | 3 (jQuery + treemenu + site.js) | 1 (site.js) |
| External JS weight | ~87 KB | 0 KB |
defer attribute | No | Yes |
| Third-party dependencies | 2 | 0 |
| Trusted Types compatible (CSP) | No | Yes |
| Theme independent of Quark | No | Yes |
This migration is part of a broader effort to harden the security of the arleo.eu server, including PGP-signed security.txt configuration, CSP optimisation, and removal of unnecessary dependencies.
💡 Theme source code available on request via security.txt.