2022-07-17

1658030400
guides
 17 Jul 2022  guides

Collapsing Bootstrap's Dropdown Navigation Automatically in SvelteKit

When using SvelteKit paired with the Bootstrap framework, it’s normal to for my default __layout to look something like this:

<script>
  import Header from "$lib/header.svelte";
  import Footer from "$lib/footer.svelte";
</script>

<Header/>

<slot></slot>

<Footer/>

In the above markup, the <Header/> component contains HTML for the navigation menu. SvelteKit by default hydrates pages by fetching the content between the <slot> tags, enabling instant navigation and a great user experience.

But because of this hydration, the <Header/> component is never reinstantiated when a new page is navigated to, and what I’ve found is that Bootstrap’s mobile navigation menu is left in an opened state after page navigation occurs.

A few classes and attributes are mutated when the navigation toggle is clicked, namely the menu toggler button and the dropdown menu element:

<!-- Navbar in an opened state -->
<button class="navbar-toggler border-0" aria-expanded="true"></button>
<div class="navbar-collapse text-right collapse show"></div>

<!-- Navbar in a closed state -->
<button class="navbar-toggler border-0 collapsed" aria-expanded="false"></button>
<div class="navbar-collapse text-right collapse"></div>

If the navigation menu is left in an opened state after a new page is loaded, then:

  • the collapsed class will be absent on the menu toggler button’s class list
  • the show class will be present on the dropdown menu element’s class list

The result of this menu being left in an opened state is not ideal and is problematic in terms of user experience — definitely a bug. Since fixing this behavior requires reading from the browser’s document variable, Svelte offers a way to run code after a page has been rendered using the onMount feature. This allows the DOM to be safely traversed when client-side events need to be triggered, and is a the perfect way to run a check on the navigation menu upon each page load.

Creating the Close Function

Firstly, I created the reusable logic and placed it in an endpoint under $lib so that it can be be easily imported:

// nav.js

export function AutoCloseNav() {
  const toggler = document.getElementById("navbarToggler");
  const dropdown = document.getElementById("navbarNavDropdown");

  if (dropdown.classList.contains("show")) {
    dropdown.classList.toggle("show");
    toggler.classList.toggle("collapsed");
    toggler.setAttribute("aria-expanded", false);
   };
}

The above code reads the dropdown menu element’s class list and removes the show class if present, thereby hiding the menu and returning it to the default closed state.

The above code also reads the menu toggler button’s class list. If the collapsed class is not present in the list then the function simply toggles it, returning it to the default closed state. For accessibility purposes, it also sets the aria-expanded attribute to false when closing the menu.

It’s important to note that by default in Bootstrap, the menu toggler button does NOT have an id attribute. I’ve manually added id="navbarToggler" to the <button> element in order to utilize the document.getElementById() method.

Executing the Close Function on Page Load

On each Svelte route (page) the function is imported from the nav.js endpoint and is invoked in onMount once the DOM has been hydrated:

<script>
  import { onMount } from 'svelte';
  import { AutoCloseNav } from "$lib/nav";

  onMount(() => {
    AutoCloseNav();
  });
</script>

Since this happens within the context of the Svelte’s routing, there isn’t any visible delay. Thus, Bootstrap’s non-hydrated menu state can be updated from the hydrated page content directly.

Full disclosure: there’s probably other (and more elegant) ways to achieve this, for example using Bootstrap’s native Collapse methods, or perhaps via Svelte’s hooks.js endpoint.

At the end of the day this method worked for me and fixed the problem so that I could move on. If you discover or know of a better approach, feel free to let me know in the comments!

Webmentions & Comments

Copyright © Paramdeo Singh · Made with Jekyll & ☕️ in 🇬🇾 · All Rights Reserved

Last Site Build on 05 Sep 2022

1MB Club Badge

Paramdeo Singh Guyana

Riding the wave of consciousness in this treacherous mortal sea.

Technology Design Strategy Literature Personal Blogs