#Issue with absolute positioned elements

18 messages · Page 1 of 1 (latest)

tribal moonBOT
#

Hi @near oriole. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

When using Astro’s <ClientRouter /> for view transitions, the DOM is swapped during navigation: the <body> is completely replaced, and only elements marked with transition:persist are carried over to the new page if they exist there as well. This means that any absolutely positioned element (like your mobile menu) will be removed and recreated on navigation—resetting its state and potentially causing it not to appear as expected after a route change.

How to fix:
To persist your mobile navigation menu (or any component/stateful element) across page transitions, add the transition:persist directive to the element or component. This will ensure that the element is not replaced during navigation, and its state (such as open/closed) is preserved:

<nav class="mobile-menu" transition:persist>
  <!-- your menu content -->
</nav>

If your menu is an Astro island (a client-rendered component), you can also use transition:persist on the component:

<MobileMenu client:load transition:persist />

Note:

  • If your menu receives props that should not be updated on navigation, you can also use transition:persist-props to keep the existing props instead of re-rendering with new ones.
  • Not all state can be preserved (e.g., CSS animations may restart), but for most interactive components, this approach works well.

For more details, see the Maintaining State section in the Astro documentation.

If your menu relies on JavaScript to function (e.g., toggling open/closed), make sure your script is re-executed after navigation. You can do this by listening for the astro:page-load event:

#
document.addEventListener("astro:page-load", () => {
  // re-attach event listeners or re-initialize menu logic here
});

Script re-execution

By combining transition:persist and proper script re-initialization, your absolutely positioned mobile menu should work correctly across route changes with view transitions enabled.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@near oriole Kapa.ai is still learning and improving, please let me know how I did by reacting below.

near oriole
#

@tribal moon ok I have the following code

  <nav class="mobile-nav-test" id="mobile-nav-test">
    <button aria-expanded="false">Menu</button>
    <ul>
      {
        navLinks.map((link) => (
          <a href={link.url} class="link">
            {link.label}
          </a>
        ))
      }
    </ul>
  </nav>
  .mobile-nav-test {
    display: grid;
    padding-inline: var(--spacing-400);
    position: relative;
    grid-template-rows: auto 0fr;
  }

  .mobile-nav-test button {
    padding-block: var(--spacing-200);
  }

  .mobile-nav-test ul {
    display: flex;
    flex-direction: column;
    text-align: center;
    position: absolute;
    background-color: bisque;
    top: 100%;
    inset-inline: var(--spacing-400);
    transform: scaleY(0);
    overflow-y: hidden;
    transform-origin: top;
  }

  .mobile-nav-test a {
    padding-block: var(--spacing-400);
  }

  .mobile-nav-test:has(button[aria-expanded="true"]) ul {
    transform: scaleY(1);
  }
  const mobileNav = document.getElementById("mobile-nav-test");
  const menuBtn = mobileNav?.firstElementChild;
  const menu = mobileNav?.lastElementChild;

  menuBtn?.addEventListener("click", function () {
    menuBtn.ariaExpanded = menuBtn.ariaExpanded === "false" ? "true" : "false";
  });

Can you apply the solution for this specific case?

tribal moonBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

To ensure your absolutely positioned mobile navigation menu works correctly with Astro’s view transitions and <ClientRouter />, you need to do two things:

  1. Persist the navigation element across route changes
    Add the transition:persist directive to your <nav> element. This will prevent Astro from replacing it during navigation, preserving its state (such as whether the menu is open or closed):

    <nav class="mobile-nav-test" id="mobile-nav-test" transition:persist>
      <button aria-expanded="false">Menu</button>
      <ul>
        {
          navLinks.map((link) => (
            <a href={link.url} class="link">
              {link.label}
            </a>
          ))
        }
      </ul>
    </nav>
    

    This directive ensures the element is carried over to the new page if it exists there as well, maintaining its state across navigations Maintaining State.

  2. Re-attach your JavaScript after navigation
    With view transitions, scripts do not automatically re-run after navigation. You should wrap your event listener setup in an astro:page-load event handler to ensure it is re-initialized after every navigation:

    document.addEventListener("astro:page-load", () => {
      const mobileNav = document.getElementById("mobile-nav-test");
      const menuBtn = mobileNav?.firstElementChild;
      const menu = mobileNav?.lastElementChild;
    
      menuBtn?.addEventListener("click", function () {
        menuBtn.ariaExpanded = menuBtn.ariaExpanded === "false" ? "true" : "false";
      });
    });
    

    This ensures your menu button remains interactive after navigating to a new page Script re-execution.

#

Summary:

  • Add transition:persist to your <nav>.
  • Wrap your script in an astro:page-load event listener.

This will resolve the issue of your absolutely positioned menu not showing up after navigation with view transitions enabled.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@near oriole Kapa.ai is still learning and improving, please let me know how I did by reacting below.

near oriole
#

@tribal moon ok seems to work, but the button gets triggered multiple times when I click it

tribal moonBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

The issue of the button being triggered multiple times is likely because each time the astro:page-load event fires, you are adding a new event listener to the same button without removing the previous one. Since the element is persisted with transition:persist, the event listeners accumulate on each navigation.

How to fix:
Before adding a new event listener, remove any existing ones or use the { once: true } option if appropriate. However, for toggling menus, the best practice is to define the handler function once and ensure it is not attached multiple times.

A common pattern is to remove the old event listener before adding a new one, or to check if the listener is already attached. Since the knowledge sources do not provide a built-in Astro solution for this, the standard JavaScript approach applies.

Here’s how you can modify your script to avoid multiple event listeners:

document.addEventListener("astro:page-load", () => {
  const mobileNav = document.getElementById("mobile-nav-test");
  const menuBtn = mobileNav?.firstElementChild;

  // Remove any existing event listener by assigning a named function and removing it first
  function toggleMenu() {
    menuBtn.ariaExpanded = menuBtn.ariaExpanded === "false" ? "true" : "false";
  }

  menuBtn?.removeEventListener("click", toggleMenu);
  menuBtn?.addEventListener("click", toggleMenu);
});

This ensures only one event listener is attached after each navigation, preventing the handler from being triggered multiple times Script re-execution.

If you need to support more complex scenarios, consider using a flag or a more robust event delegation pattern.

Summary:

  • Use a named function for your event handler.
  • Remove the event listener before adding it again on each astro:page-load.
#

This approach is consistent with the guidance in the Astro documentation for handling script re-execution with view transitions.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@near oriole Kapa.ai is still learning and improving, please let me know how I did by reacting below.

near oriole
#

@tribal moon unfortunately is not working properly

tribal moonBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Thank you for your feedback. Based on the knowledge sources, the recommended approach for handling scripts with persisted elements in Astro’s view transitions is to wrap your event listener setup in the astro:page-load event. However, the documentation does not provide a built-in way to prevent multiple event listeners from being attached when using transition:persist and re-initializing scripts after navigation.

The Astro documentation suggests that you should ensure your script is re-executed at the right time, but it does not specify a method for deduplication of event listeners. The example provided simply attaches the event listener on every astro:page-load event, which can lead to multiple listeners if the element is persisted across navigations.

If you are experiencing multiple triggers, it is likely because the persisted element retains its previous event listeners, and new ones are added on each navigation. The documentation does not provide a direct solution for this scenario.

Summary:

  • The knowledge sources confirm that scripts should be re-initialized on astro:page-load when using view transitions and persisted elements.
  • They do not provide a built-in or recommended pattern for removing or deduplicating event listeners in this context.
  • If you need to avoid multiple event listeners, you may need to implement your own logic to track or remove listeners, but this is not covered in the official documentation.
#

If you need further guidance, you may want to consult community forums or raise an issue with the Astro team, as the current documentation does not explicitly address this edge case.
Script re-execution

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: