2 minute read

Currently Minimal Mistakes doesn’t support sticky Table of Contents for mobile display, even if you have set toc_sticky: true (see Table of contents). Now I want to enable it for mobile with minimal changes so that there is a sticky TOC icon on the screen and when clicked it shows/hides the TOC. This should not affect the desktop display and should apply regardless of the drafting format in use (Org or Markdown).

The fastest, most efficient approach to implement this is to rip the .toc out of the standard mobile document flow, apply fixed positioning to turn it into a popup, and inject a floating toggle button using JavaScript.

Because Minimal Mistakes builds the toc_sticky sidebar via Liquid layouts, this DOM-level manipulation works perfectly regardless of whether the post was drafted in Org-mode or Markdown.

Here are the specific updates you need to make to your files.

  1. Update your Stylesheet

    Append this CSS to the bottom of assets/css/main.scss. This code uses Minimal Mistakes’ standard $large breakpoint (64em or 1024px) to transform the TOC into a floating modal, while inheriting the native light/dark theme colors already defined by the .toc class.

    // Mobile Sticky TOC Toggle
    .toc-mobile-btn {
      display: none;
    }
    
    @media (max-width: 64em) {
      // 1. The floating button
      .has-toc .toc-mobile-btn {
        display: flex;
        position: fixed;
        bottom: 25px;
        right: 25px;
        z-index: 9999;
        background: rgba(128, 128, 128, 0.2); // Light/Dark mode agnostic
        backdrop-filter: blur(5px);
        -webkit-backdrop-filter: blur(5px);
        color: inherit;
        border: 1px solid rgba(128, 128, 128, 0.3);
        border-radius: 50%;
        width: 50px;
        height: 50px;
        font-size: 1.2rem;
        box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
        cursor: pointer;
        align-items: center;
        justify-content: center;
        transition: transform 0.2s ease;
    
        &:active {
          transform: scale(0.95);
        }
      }
    
      // 2. The TOC Popup Panel
      .sidebar__right.sticky .toc {
        position: fixed;
        bottom: 90px;
        right: 25px;
        width: 320px;
        max-width: calc(100vw - 50px);
        max-height: 60vh;
        overflow-y: auto;
        z-index: 9998;
        box-shadow: 0 5px 25px rgba(0, 0, 0, 0.2);
        margin: 0;
    
        // Animation states
        opacity: 0;
        visibility: hidden;
        transform: translateY(20px);
        transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
    
        &.is-active {
          opacity: 1;
          visibility: visible;
          transform: translateY(0);
        }
      }
    }
  2. Inject the Button via JavaScript

    To ensure the button doesn’t clutter pages that don’t have a TOC, we dynamically inject it. Append this snippet to the very end of your _includes/head/custom.html file:

    <script>
      function initMobileTOC() {
        var toc = document.querySelector('.sidebar__right.sticky .toc');
        // Only initialize if a sticky TOC exists and the button hasn't been added yet
        if (toc && !document.querySelector('.toc-mobile-btn')) {
          document.body.classList.add('has-toc');
    
          var btn = document.createElement('button');
          btn.className = 'toc-mobile-btn';
          btn.innerHTML = '<i class="fas fa-list-ul"></i>';
          btn.setAttribute('aria-label', 'Toggle Table of Contents');
          document.body.appendChild(btn);
    
          // Toggle TOC visibility on button click
          btn.addEventListener('click', function(e) {
            e.stopPropagation();
            toc.classList.toggle('is-active');
          });
    
          // Close TOC when clicking anywhere outside of it
          document.addEventListener('click', function(e) {
            if (toc.classList.contains('is-active') && !toc.contains(e.target) && e.target !== btn) {
              toc.classList.remove('is-active');
            }
          });
    
          // Automatically close the TOC popup after tapping a link inside it
          // var tocLinks = toc.querySelectorAll('a');
          // tocLinks.forEach(function(link) {
          //   link.addEventListener('click', function() {
          //     if (window.innerWidth <= 1024) { // Matches the 64em media query
          //       toc.classList.remove('is-active');
          //     }
          //   });
          // });
        }
      }
    
      document.addEventListener('DOMContentLoaded', initMobileTOC);
    </script>
  3. Handle Encrypted Posts (Optional Safety Net)

    Just in case you ever decide to disable toc_sticky on an encrypted post (which moves the TOC completely inside the encrypted section.page__content container), it is best practice to wake this script up after decryption.

    Open _includes/secure_ui.html and drop this block right next to your Gumshoe initialization:

    // Re-initialize Mobile TOC Toggle
    try {
        if (typeof initMobileTOC === 'function') {
            initMobileTOC();
        }
    } catch (e) {
        console.warn("Mobile TOC failed to load", e);
    }

Comments