---
title: "jQuery → Vanilla ES6 Migration"
description: "Migrating jQuery to Vanilla ES6 on Grav CMS: removing an 87 KB dependency, rewriting the mobile menu and tree-menu in native JavaScript."
url: "https://www.arleo.eu/en/posts/jquery-migration/"
language: "en"
datePublished: "2026-04-06T00:00:00+02:00"
dateModified: "2026-05-15T23:51:25+02:00"
tags: ["javascript","nginx","infrastructure","homelab"]
categories: ["infrastructure"]
contentSignal: "ai-train=no, search=yes, ai-input=yes"
---

# jQuery → Vanilla ES6 Migration
Migrating jQuery to Vanilla ES6 on Grav CMS: removing an 87 KB dependency, rewriting the mobile menu and tree-menu in native JavaScript.



## ⚡ 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:**

```html
<!-- 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:**

```html
<!-- Single file, loaded deferred -->
<script src="/user/themes/arleo/js/site.js" defer></script>
```

### ⚙️ Migration example — mobile menu

**Before (jQuery):**

```javascript
jQuery(document).ready(function($) {
    $('#toggle').click(function() {
        $(this).toggleClass('active');
        $('#overlay').toggleClass('open');
        $('body').toggleClass('mobile-nav-open');
    });
});
```

**After (Vanilla ES6):**

```javascript
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:

```javascript
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

```bash
# 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](https://www.arleo.eu/security.txt).



## Tags

- javascript
- nginx
- infrastructure
- homelab

## Categories

- infrastructure
