Then in the head I’m trying to link to it:
<?= js('assets/scripts.js') ?>
Here’s the content of the .js file:
/** select the button that opens the nav */
const menuOpener = document.querySelector('.nav-button');
/** apply a click event handler that simply toggles the ".show-nav" class on the body */
menuOpener.addEventListener('click', () => document.body.classList.toggle('nav-shown'));
That looks like the script is actually loading since you are getting a console error in the browser.
Some scripts need to go just before the closing body tag since they need the page to be full loaded. As you have put this script in the head, it is trying to access a part of the page that hasnt been loaded yet.
just move the <?= js('assets/scripts.js') ?> line to just before the closing body tag.
For future completeness: you could also leave it in the <head>, but add a defer attribute to it. In fact, by many this is considered preferable, because it allows the browser to know about the script earlier.
The defer attribute basically makes the browser download the script as soon as it sees the tag, it doesn’t block the parser like “normal” scripts do, and the script is executed only after the whole document has been parsed, but before DOMContentLoaded is fired.
In Kirby you can add a defer attribute to the script tag by adding it to the options array of the js helper: