Repository: mui/base-ui (2025-07-07T07:05:38Z)

Issue:
[navigation menu] Support inlined nesting
# Bug report

## Current behavior

### 1. `--popup-height` is not updated on content change

When the content inside `NavigationMenu.Content` changes (e.g. dynamically loaded or conditionally rendered), the `--popup-height` variable is not updated to reflect the new height. As a result, if you're animating the popup height using this variable, the transition breaks — the height appears stuck at its previous value (`auto`), and no animation occurs. The only way to update it is by activating another trigger, which forces a re-evaluation, but nothing listens to content size changes dynamically.

### 2. No transition on nested NavigationMenu

When nesting another `NavigationMenu` inside a parent item (e.g. for multi-level navigation), and using a `defaultValue` to preselect a nested item, the transition effect is skipped entirely. It seems like the inner `NavigationMenu.Viewport` is delayed in rendering or measuring, and that causes the quick jump instead of a smooth transition.

### 3. No direction-based transitions when skipping `Positioner`/`Popover`

Migrating from Radix UI's `NavigationMenu`, we had the ability to render the `Viewport` without a `Portal`, `Positioner`, or `Popover`. This was useful for building custom layouts like nested menus without extra wrappers.

With Base UI's current setup, skipping `Positioner` or `Popover` breaks direction-based transitions. The `Content` seems unaware of the "activation direction" (`left | right | top | bottom`), so the enter animation doesn't apply. It seems necessary to wrap everything again inside `Positioner`/`Popover`, even if it's not needed visually, just to get the state propagation working.

## Expected behavior

- The `--popup-height` CSS variable should update reactively when content height changes, enabling smooth transitions.
- Nested `NavigationMenu` transitions should respect the correct enter/exit animations when `defaultValue` is set.
- `NavigationMenu.Content` should still be able to determine the activation direction and animate correctly even if `Popover`/`Positioner` are not used, to support more flexible layout use cases.

## Reproducible example

https://stackblitz.com/edit/vitejs-vite-4erpxnu7?file=src%2FApp.tsx

## Base UI version

1.0.0-beta.1

## Which browser are you using?

Chrome (latest version)

## Which OS are you using?

macOS

## Which assistive tech are you using (if applicable)?

N/A

## Additional context

https://github.com/user-attachments/assets/47e13de8-7367-4536-bf4c-8f4fe1729c62

https://github.com/user-attachments/assets/17918dc4-acab-43d0-a1c2-7faf0aecd5b2



Discussion:
Hey! 👋 Thanks for the detailed bug report — super helpful.

I ran into similar issues while working with dynamic content and nested menus, so I did some digging. Here are a few thoughts on possible workarounds/fixes:

1. Dynamic --popup-height 
   This can be handled by attaching a `ResizeObserver` to the `NavigationMenu.Content` element. That way, when the content changes (even conditionally or after async updates), we can recalculate and update the `--popup-height` variable to keep the transition smooth.

2. No transition on nested menus with defaultValue 
   This seems related to layout timing. A small delay using `requestAnimationFrame` or `setTimeout` before setting the default value can help give the DOM enough time to mount and animate properly.

3. Broken direction-based transitions without Popover/Positioner
   For custom layouts, it would be really helpful if direction state could be passed manually (or exposed via context), so we don’t rely strictly on `Popover` or `Positioner`. As a workaround, we can manually apply animation classes or styles based on direction logic.

Happy to help prototype one or more of these if needed — just let me know! 

Thanks for offering to help! The main reason I’m posting these issues is that all of this works as expected with the Radix Navigation Menu out of the box, without requiring extra workarounds. I’m still using the Radix implementation for now, but I’m exploring ways to mitigate the gaps, so that later migration off the Radix will be easier not only for me but for others too.
These examples are very useful, thank you!

I think Radix avoids anchor positioning and uses CSS Grid, which has some drawbacks, but it ends up handling nested content transitions automatically. We also investigated CSS Grid for Collapsible but decided not to use it (for now): https://github.com/mui/base-ui/pull/1770

These should all be possible to fix without changing the positioning implementation, but I'll need to look into it

---

The key issue is that our implementation assumes you're creating a new popup when nesting `NavigationMenu` (like a `Menu` submenu), not inlining its viewport content inside of the root popup itself. This assumption essentially prevents nested inline content from working in most ways (most of the event handler logic/keyboard navigation fails to work correctly in your demo).

Allowing nested inlined content is likely going to take quite a bit of work and I don't have enough bandwidth atm to work on it (Combobox is taking priority), but I suppose fixing dot point 1 alone would allow custom implementations (i.e. custom event handlers to show/hide content inside the popup) as a workaround of sorts
This fix will be available in the next npm release of Base UI.

In the meantime, you can try it out on our Canary release channel:

```sh
npm i https://pkg.pr.new/@base-ui-components/react@2269
```



Patch:
diff --git a/docs/src/app/(private)/experiments/navigation-menu.module.css b/docs/src/app/(private)/experiments/navigation-menu.module.css
index 069bf612a0..ad3c538077 100644
--- a/docs/src/app/(private)/experiments/navigation-menu.module.css
+++ b/docs/src/app/(private)/experiments/navigation-menu.module.css
@@ -121,12 +121,9 @@
   transition-property: opacity, transform, width, height;
   transition-duration: var(--duration);
   transition-timing-function: var(--easing);
-  width: max-content;
+  width: var(--popup-width);
   height: var(--popup-height);
-
-  @media (min-width: 500px) {
-    width: var(--popup-width);
-  }
+  max-height: var(--available-height);
 
   &[data-starting-style],
   &[data-ending-style] {
@@ -164,6 +161,8 @@
   padding: 1.5rem;
   width: calc(100vw - 40px);
   height: 100%;
+  overflow-y: auto;
+  max-height: var(--available-height);
 
   @media (min-width: 500px) {
     width: max-content;
diff --git a/docs/src/app/(private)/experiments/navigation-menu.tsx b/docs/src/app/(private)/experiments/navigation-menu.tsx
index c51d9b7748..5d7133bd47 100644
--- a/docs/src/app/(private)/experiments/navigation-menu.tsx
+++ b/docs/src/app/(private)/experiments/navigation-menu.tsx
@@ -1,37 +1,15 @@
 'use client';
 import * as React from 'react';
 import { NavigationMenu } from '@base-ui-components/react/navigation-menu';
+import { Collapsible } from '@base-ui-components/react/collapsible';
 import styles from './navigation-menu.module.css';
 
 export default function ExampleNavigationMenu() {
   const [renderExtraItem, setRenderExtraItem] = React.useState(false);
-  const [renderExtraItem2, setRenderExtraItem2] = React.useState(false);
-
-  React.useEffect(() => {
-    setTimeout(() => {
-      setRenderExtraItem(true);
-    }, 2500);
-    setTimeout(() => {
-      setRenderExtraItem2(true);
-    }, 5000);
-  }, []);
+  const [hoveredLink, setHoveredLink] = React.useState<string | null>(null);
 
   return (
     <div>
-      <ol style={{ listStyle: 'decimal' }}>
-        <li>Open the menu</li>
-        <li>
-          After 2500ms since mount, a new item will be rendered in the Overview menu
-        </li>
-        <li>Move to the Handbook menu</li>
-        <li>
-          After 5000ms since mount, a new item will be rendered in the Handbook menu
-        </li>
-        <li>
-          Resizing while open also works as expected - the menu will resize to the
-          new size
-        </li>
-      </ol>
       <NavigationMenu.Root className={styles.Root}>
         <NavigationMenu.List className={styles.List}>
           <NavigationMenu.Item>
@@ -42,8 +20,112 @@ export default function ExampleNavigationMenu() {
               </NavigationMenu.Icon>
             </NavigationMenu.Trigger>
             <NavigationMenu.Content className={styles.Content}>
-              <ul className={styles.GridLinkList}>
-                {overviewLinks.map((item) => (
+              <div style={{ display: 'flex', gap: 20 }}>
+                {/* Left side - Links */}
+                <div style={{ flex: 1 }}>
+                  <ul className={styles.GridLinkList}>
+                    {overviewLinks.map((item) => (
+                      <li key={item.href}>
+                        <Link
+                          className={styles.LinkCard}
+                          href={item.href}
+                          onMouseEnter={() => {
+                            setHoveredLink(item.href);
+                          }}
+                        >
+                          <h3 className={styles.LinkTitle}>{item.title}</h3>
+                          <p className={styles.LinkDescription}>{item.description}</p>
+                        </Link>
+                      </li>
+                    ))}
+                  </ul>
+                </div>
+
+                {/* Right side - Dynamic content */}
+                <div style={{ flex: 1 }}>
+                  {hoveredLink && (
+                    <div
+                      style={{
+                        backgroundColor: 'black',
+                        color: 'white',
+                        padding: 20,
+                        borderRadius: 8,
+                        minHeight: 200,
+                        width: hoveredLink === '/react/overview/quick-start' ? 200 : 400,
+                        height: hoveredLink === '/react/overview/accessibility' ? 150 : 300,
+                      }}
+                    >
+                      {hoveredLink === '/react/overview/quick-start' && (
+                        <div>
+                          <h4 style={{ marginBottom: 10 }}>Quick Start Guide</h4>
+                          <p>
+                            All components are included in a single package. Base UI is
+                            tree-shakeable, so your app bundle will contain only the components that
+                            you actually use.
+                          </p>
+                        </div>
+                      )}
+                      {hoveredLink === '/react/overview/accessibility' && (
+                        <div>
+                          <h4 style={{ marginBottom: 10 }}>Accessibility Features</h4>
+                          <p>
+                            ARIA attributes built-in, keyboard navigation, screen reader support,
+                            and focus management.
+                          </p>
+                        </div>
+                      )}
+                      {hoveredLink === '/react/overview/releases' && (
+                        <div>
+                          <h4 style={{ marginBottom: 10 }}>Latest Releases</h4>
+                          <p>
+                            v1.0.0 - Major release with new components, performance improvements,
+                            and bug fixes.
+                          </p>
+                        </div>
+                      )}
+                      {hoveredLink === '/react/overview/about' && (
+                        <div>
+                          <h4 style={{ marginBottom: 10 }}>About Base UI</h4>
+                          <p>
+                            Unstyled components that are fully customizable, and built by the MUI
+                            team.
+                          </p>
+                        </div>
+                      )}
+                    </div>
+                  )}
+                </div>
+              </div>
+            </NavigationMenu.Content>
+          </NavigationMenu.Item>
+
+          <NavigationMenu.Item>
+            <NavigationMenu.Trigger className={styles.Trigger}>
+              Handbook
+              <NavigationMenu.Icon className={styles.Icon}>
+                <ChevronDownIcon />
+              </NavigationMenu.Icon>
+            </NavigationMenu.Trigger>
+            <NavigationMenu.Content className={styles.Content}>
+              <Collapsible.Root className="flex w-56 flex-col justify-center text-gray-900">
+                <Collapsible.Trigger className="group flex items-center gap-2 rounded-sm bg-gray-100 px-2 py-1 text-sm font-medium hover:bg-gray-200 focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-800 active:bg-gray-200">
+                  Recovery keys
+                </Collapsible.Trigger>
+                <Collapsible.Panel className="flex h-[var(--collapsible-panel-height)] flex-col justify-end overflow-hidden text-sm transition-all ease-out data-[ending-style]:h-0 data-[starting-style]:h-0">
+                  <div className="mt-1 flex cursor-text flex-col gap-2 rounded-sm bg-gray-100 py-2 pl-7">
+                    <div>alien-bean-pasta</div>
+                    <div>wild-irish-burrito</div>
+                    <div>horse-battery-staple</div>
+                  </div>
+                </Collapsible.Panel>
+              </Collapsible.Root>
+
+              <button type="button" onClick={() => setRenderExtraItem(!renderExtraItem)}>
+                Render extra item
+              </button>
+
+              <ul className={styles.FlexLinkList}>
+                {handbookLinks.map((item) => (
                   <li key={item.href}>
                     <Link className={styles.LinkCard} href={item.href}>
                       <h3 className={styles.LinkTitle}>{item.title}</h3>
@@ -53,10 +135,7 @@ export default function ExampleNavigationMenu() {
                 ))}
                 {renderExtraItem && (
                   <li>
-                    <Link
-                      className={styles.LinkCard}
-                      href="/react/overview/quick-start"
-                    >
+                    <Link className={styles.LinkCard} href="/react/overview/quick-start">
                       <h3 className={styles.LinkTitle}>Quick Start</h3>
                       <p className={styles.LinkDescription}>
                         Install and assemble your first component.
@@ -70,34 +149,102 @@ export default function ExampleNavigationMenu() {
 
           <NavigationMenu.Item>
             <NavigationMenu.Trigger className={styles.Trigger}>
-              Handbook
+              Nested
               <NavigationMenu.Icon className={styles.Icon}>
                 <ChevronDownIcon />
               </NavigationMenu.Icon>
             </NavigationMenu.Trigger>
             <NavigationMenu.Content className={styles.Content}>
-              <ul className={styles.FlexLinkList}>
-                {handbookLinks.map((item) => (
-                  <li key={item.href}>
-                    <Link className={styles.LinkCard} href={item.href}>
-                      <h3 className={styles.LinkTitle}>{item.title}</h3>
-                      <p className={styles.LinkDescription}>{item.description}</p>
+              <NavigationMenu.Root className={styles.Root} defaultValue="overview">
+                <NavigationMenu.List className={styles.List}>
+                  <NavigationMenu.Item value="overview">
+                    <NavigationMenu.Trigger className={styles.Trigger}>
+                      Overview
+                      <NavigationMenu.Icon className={styles.Icon}>
+                        <ChevronDownIcon />
+                      </NavigationMenu.Icon>
+                    </NavigationMenu.Trigger>
+                    <NavigationMenu.Content className={styles.Content}>
+                      <ul className={styles.GridLinkList}>
+                        {overviewLinks.map((item) => (
+                          <li key={item.href}>
+                            <Link className={styles.LinkCard} href={item.href}>
+                              <h3 className={styles.LinkTitle}>{item.title}</h3>
+                              <p className={styles.LinkDescription}>{item.description}</p>
+                            </Link>
+                          </li>
+                        ))}
+                      </ul>
+                    </NavigationMenu.Content>
+                  </NavigationMenu.Item>
+
+                  <NavigationMenu.Item value="handbook">
+                    <NavigationMenu.Trigger className={styles.Trigger}>
+                      Handbook
+                      <NavigationMenu.Icon className={styles.Icon}>
+                        <ChevronDownIcon />
+                      </NavigationMenu.Icon>
+                    </NavigationMenu.Trigger>
+                    <NavigationMenu.Content className={styles.Content}>
+                      <button type="button" onClick={() => setRenderExtraItem(!renderExtraItem)}>
+                        Render extra item
+                      </button>
+
+                      <ul className={styles.FlexLinkList}>
+                        {handbookLinks.map((item) => (
+                          <li key={item.href}>
+                            <Link className={styles.LinkCard} href={item.href}>
+                              <h3 className={styles.LinkTitle}>{item.title}</h3>
+                              <p className={styles.LinkDescription}>{item.description}</p>
+                            </Link>
+                          </li>
+                        ))}
+                        {renderExtraItem && (
+                          <li>
+                            <Link className={styles.LinkCard} href="/react/overview/quick-start">
+                              <h3 className={styles.LinkTitle}>Quick Start</h3>
+                              <p className={styles.LinkDescription}>
+                                Install and assemble your first component.
+                              </p>
+                            </Link>
+                          </li>
+                        )}
+                      </ul>
+                    </NavigationMenu.Content>
+                  </NavigationMenu.Item>
+
+                  <NavigationMenu.Item>
+                    <Link className={styles.Trigger} href="https://github.com/mui/base-ui">
+                      GitHub
                     </Link>
-                  </li>
-                ))}
-                {renderExtraItem2 && (
-                  <li>
-                    <Link
-                      className={styles.LinkCard}
-                      href="/react/overview/quick-start"
-                    >
-                      <h3 className={styles.LinkTitle}>Quick Start</h3>
+                  </NavigationMenu.Item>
+                </NavigationMenu.List>
+
+                <NavigationMenu.Viewport className={styles.Viewport} />
+              </NavigationMenu.Root>
+            </NavigationMenu.Content>
+          </NavigationMenu.Item>
+
+          <NavigationMenu.Item>
+            <NavigationMenu.Trigger className={styles.Trigger}>
+              Long List
+              <NavigationMenu.Icon className={styles.Icon}>
+                <ChevronDownIcon />
+              </NavigationMenu.Icon>
+            </NavigationMenu.Trigger>
+            <NavigationMenu.Content className={styles.Content}>
+              <ul className={styles.LongList}>
+                {Array.from({ length: 50 }, (_, index) => (
+                  <li key={index}>
+                    <Link className={styles.LinkCard} href={`/item-${index}`}>
+                      <h3 className={styles.LinkTitle}>Item {index + 1}</h3>
                       <p className={styles.LinkDescription}>
-                        Install and assemble your first component.
+                        This is item number {index + 1} in the long list for testing scrollable
+                        menus.
                       </p>
                     </Link>
                   </li>
-                )}
+                ))}
               </ul>
             </NavigationMenu.Content>
           </NavigationMenu.Item>
@@ -111,8 +258,10 @@ export default function ExampleNavigationMenu() {
 
         <NavigationMenu.Portal>
           <NavigationMenu.Positioner
+            align="start"
             className={styles.Positioner}
             sideOffset={10}
+            alignOffset={-100}
             collisionPadding={{ top: 5, bottom: 5, left: 20, right: 20 }}
           >
             <NavigationMenu.Popup className={styles.Popup}>
@@ -125,10 +274,7 @@ export default function ExampleNavigationMenu() {
         </NavigationMenu.Portal>
       </NavigationMenu.Root>
 
-      <NavigationMenu.Root
-        className={styles.Root}
-        style={{ position: 'absolute', bottom: 100 }}
-      >
+      <NavigationMenu.Root className={styles.Root} style={{ position: 'absolute', bottom: 100 }}>
         <NavigationMenu.List className={styles.List}>
           <NavigationMenu.Item>
             <NavigationMenu.Trigger className={styles.Trigger}>
@@ -147,19 +293,6 @@ export default function ExampleNavigationMenu() {
                     </Link>
                   </li>
                 ))}
-                {renderExtraItem && (
-                  <li>
-                    <Link
-                      className={styles.LinkCard}
-                      href="/react/overview/quick-start"
-                    >
-                      <h3 className={styles.LinkTitle}>Quick Start</h3>
-                      <p className={styles.LinkDescription}>
-                        Install and assemble your first component.
-                      </p>
-                    </Link>
-                  </li>
-                )}
               </ul>
             </NavigationMenu.Content>
           </NavigationMenu.Item>
@@ -172,6 +305,10 @@ export default function ExampleNavigationMenu() {
               </NavigationMenu.Icon>
             </NavigationMenu.Trigger>
             <NavigationMenu.Content className={styles.Content}>
+              <button type="button" onClick={() => setRenderExtraItem(!renderExtraItem)}>
+                Render extra item
+              </button>
+
               <ul className={styles.FlexLinkList}>
                 {handbookLinks.map((item) => (
                   <li key={item.href}>
@@ -181,12 +318,9 @@ export default function ExampleNavigationMenu() {
                     </Link>
                   </li>
                 ))}
-                {renderExtraItem2 && (
+                {renderExtraItem && (
                   <li>
-                    <Link
-                      className={styles.LinkCard}
-                      href="/react/overview/quick-start"
-                    >
+                    <Link className={styles.LinkCard} href="/react/overview/quick-start">
                       <h3 className={styles.LinkTitle}>Quick Start</h3>
                       <p className={styles.LinkDescription}>
                         Install and assemble your first component.
@@ -284,7 +418,7 @@ const overviewLinks = [
   {
     href: '/react/overview/about',
     title: 'About',
-    description: 'Learn more about Base UI and our mission.',
+    description: 'Learn more about Base UI and our mission.',
   },
 ] as const;
 
@@ -293,18 +427,18 @@ const handbookLinks = [
     href: '/react/handbook/styling',
     title: 'Styling',
     description:
-      'Base UI components can be styled with plain CSS, Tailwind CSS, CSS-in-JS, or CSS Modules.',
+      'Base UI components can be styled with plain CSS, Tailwind CSS, CSS-in-JS, or CSS Modules.',
   },
   {
     href: '/react/handbook/animation',
     title: 'Animation',
     description:
-      'Base UI components can be animated with CSS transitions, CSS animations, or JavaScript libraries.',
+      'Base UI components can be animated with CSS transitions, CSS animations, or JavaScript libraries.',
   },
   {
     href: '/react/handbook/composition',
     title: 'Composition',
     description:
-      'Base UI components can be replaced and composed with your own existing components.',
+      'Base UI components can be replaced and composed with your own existing components.',
   },
 ] as const;
diff --git a/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/hero/css-modules/index.module.css b/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/hero/css-modules/index.module.css
index 069bf612a0..9f20641089 100644
--- a/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/hero/css-modules/index.module.css
+++ b/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/hero/css-modules/index.module.css
@@ -121,13 +121,9 @@
   transition-property: opacity, transform, width, height;
   transition-duration: var(--duration);
   transition-timing-function: var(--easing);
-  width: max-content;
+  width: var(--popup-width);
   height: var(--popup-height);
 
-  @media (min-width: 500px) {
-    width: var(--popup-width);
-  }
-
   &[data-starting-style],
   &[data-ending-style] {
     opacity: 0;
diff --git a/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/hero/css-modules/index.tsx b/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/hero/css-modules/index.tsx
index 67863915e9..9945a117f7 100644
--- a/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/hero/css-modules/index.tsx
+++ b/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/hero/css-modules/index.tsx
@@ -60,6 +60,7 @@ export default function ExampleNavigationMenu() {
           className={styles.Positioner}
           sideOffset={10}
           collisionPadding={{ top: 5, bottom: 5, left: 20, right: 20 }}
+          collisionAvoidance={{ side: 'none' }}
         >
           <NavigationMenu.Popup className={styles.Popup}>
             <NavigationMenu.Arrow className={styles.Arrow}>
diff --git a/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/hero/tailwind/index.tsx b/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/hero/tailwind/index.tsx
index 942985e3cf..d113cc8188 100644
--- a/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/hero/tailwind/index.tsx
+++ b/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/hero/tailwind/index.tsx
@@ -18,12 +18,8 @@ export default function ExampleNavigationMenu() {
               {overviewLinks.map((item) => (
                 <li key={item.href}>
                   <Link href={item.href} className={linkCardClassName}>
-                    <h3 className="m-0 mb-1 text-base leading-5 font-medium">
-                      {item.title}
-                    </h3>
-                    <p className="m-0 text-sm leading-5 text-gray-500">
-                      {item.description}
-                    </p>
+                    <h3 className="m-0 mb-1 text-base leading-5 font-medium">{item.title}</h3>
+                    <p className="m-0 text-sm leading-5 text-gray-500">{item.description}</p>
                   </Link>
                 </li>
               ))}
@@ -44,12 +40,8 @@ export default function ExampleNavigationMenu() {
               {handbookLinks.map((item) => (
                 <li key={item.href}>
                   <Link href={item.href} className={linkCardClassName}>
-                    <h3 className="m-0 mb-1 text-base leading-5 font-medium">
-                      {item.title}
-                    </h3>
-                    <p className="m-0 text-sm leading-5 text-gray-500">
-                      {item.description}
-                    </p>
+                    <h3 className="m-0 mb-1 text-base leading-5 font-medium">{item.title}</h3>
+                    <p className="m-0 text-sm leading-5 text-gray-500">{item.description}</p>
                   </Link>
                 </li>
               ))}
@@ -68,13 +60,14 @@ export default function ExampleNavigationMenu() {
         <NavigationMenu.Positioner
           sideOffset={10}
           collisionPadding={{ top: 5, bottom: 5, left: 20, right: 20 }}
+          collisionAvoidance={{ side: 'none' }}
           className="box-border h-[var(--positioner-height)] w-[var(--positioner-width)] max-w-[var(--available-width)] transition-[top,left,right,bottom] duration-[var(--duration)] ease-[var(--easing)] before:absolute before:content-[''] data-[instant]:transition-none data-[side=bottom]:before:top-[-10px] data-[side=bottom]:before:right-0 data-[side=bottom]:before:left-0 data-[side=bottom]:before:h-2.5 data-[side=left]:before:top-0 data-[side=left]:before:right-[-10px] data-[side=left]:before:bottom-0 data-[side=left]:before:w-2.5 data-[side=right]:before:top-0 data-[side=right]:before:bottom-0 data-[side=right]:before:left-[-10px] data-[side=right]:before:w-2.5 data-[side=top]:before:right-0 data-[side=top]:before:bottom-[-10px] data-[side=top]:before:left-0 data-[side=top]:before:h-2.5"
           style={{
             ['--duration' as string]: '0.35s',
             ['--easing' as string]: 'cubic-bezier(0.22, 1, 0.36, 1)',
           }}
         >
-          <NavigationMenu.Popup className="data-[ending-style]:easing-[ease] relative h-[var(--popup-height)] w-max origin-[var(--transform-origin)] rounded-lg bg-[canvas] text-gray-900 shadow-lg shadow-gray-200 outline outline-1 outline-gray-200 transition-[opacity,transform,width,height,scale,translate] duration-[var(--duration)] ease-[var(--easing)] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[ending-style]:duration-150 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 min-[500px]:w-[var(--popup-width)] xs:w-[var(--popup-width)] dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300">
+          <NavigationMenu.Popup className="data-[ending-style]:easing-[ease] relative h-[var(--popup-height)] origin-[var(--transform-origin)] rounded-lg bg-[canvas] text-gray-900 shadow-lg shadow-gray-200 outline outline-1 outline-gray-200 transition-[opacity,transform,width,height,scale,translate] duration-[var(--duration)] ease-[var(--easing)] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[ending-style]:duration-150 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 w-[var(--popup-width)] xs:w-[var(--popup-width)] dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300">
             <NavigationMenu.Arrow className="flex transition-[left] duration-[var(--duration)] ease-[var(--easing)] data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180">
               <ArrowSvg />
             </NavigationMenu.Arrow>
diff --git a/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/nested/css-modules/index.module.css b/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/nested/css-modules/index.module.css
index 95d8340325..e7f032c3a0 100644
--- a/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/nested/css-modules/index.module.css
+++ b/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/nested/css-modules/index.module.css
@@ -122,13 +122,9 @@
   transition-property: opacity, transform, width, height;
   transition-duration: var(--duration);
   transition-timing-function: var(--easing);
-  width: max-content;
+  width: var(--popup-width);
   height: var(--popup-height);
 
-  @media (min-width: 500px) {
-    width: var(--popup-width);
-  }
-
   &[data-starting-style],
   &[data-ending-style] {
     opacity: 0;
diff --git a/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/nested/css-modules/index.tsx b/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/nested/css-modules/index.tsx
index c03c75a185..c74c6b5fe3 100644
--- a/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/nested/css-modules/index.tsx
+++ b/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/nested/css-modules/index.tsx
@@ -28,9 +28,7 @@ export default function ExampleNavigationMenu() {
                   <NavigationMenu.Item>
                     <NavigationMenu.Trigger className={styles.LinkCard}>
                       <span className={styles.LinkTitle}>Handbook</span>
-                      <p className={styles.LinkDescription}>
-                        How to use Base UI effectively.
-                      </p>
+                      <p className={styles.LinkDescription}>How to use Base UI effectively.</p>
                       <NavigationMenu.Icon className={styles.NestedIcon}>
                         <ChevronRightIcon />
                       </NavigationMenu.Icon>
@@ -41,9 +39,7 @@ export default function ExampleNavigationMenu() {
                           <li key={item.href}>
                             <Link className={styles.LinkCard} href={item.href}>
                               <h3 className={styles.LinkTitle}>{item.title}</h3>
-                              <p className={styles.LinkDescription}>
-                                {item.description}
-                              </p>
+                              <p className={styles.LinkDescription}>{item.description}</p>
                             </Link>
                           </li>
                         ))}
diff --git a/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/nested/tailwind/index.tsx b/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/nested/tailwind/index.tsx
index 1e693744b8..c0dc4e1993 100644
--- a/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/nested/tailwind/index.tsx
+++ b/docs/src/app/(public)/(content)/react/components/navigation-menu/demos/nested/tailwind/index.tsx
@@ -18,12 +18,8 @@ export default function ExampleNavigationMenu() {
               {overviewLinks.map((item) => (
                 <li key={item.href}>
                   <Link href={item.href} className={linkCardClassName}>
-                    <h3 className="m-0 mb-1 text-base leading-5 font-medium">
-                      {item.title}
-                    </h3>
-                    <p className="m-0 text-sm leading-5 text-gray-500">
-                      {item.description}
-                    </p>
+                    <h3 className="m-0 mb-1 text-base leading-5 font-medium">{item.title}</h3>
+                    <p className="m-0 text-sm leading-5 text-gray-500">{item.description}</p>
                   </Link>
                 </li>
               ))}
@@ -31,9 +27,7 @@ export default function ExampleNavigationMenu() {
                 <NavigationMenu.Root orientation="vertical">
                   <NavigationMenu.Item>
                     <NavigationMenu.Trigger className={linkCardClassName}>
-                      <span className="m-0 mb-1 text-base leading-5 font-medium">
-                        Handbook
-                      </span>
+                      <span className="m-0 mb-1 text-base leading-5 font-medium">Handbook</span>
                       <p className="m-0 text-sm leading-5 text-gray-500">
                         How to use Base UI effectively.
                       </p>
@@ -93,7 +87,7 @@ export default function ExampleNavigationMenu() {
             ['--easing' as string]: 'cubic-bezier(0.22, 1, 0.36, 1)',
           }}
         >
-          <NavigationMenu.Popup className="data-[ending-style]:easing-[ease] relative h-[var(--popup-height)] w-max origin-[var(--transform-origin)] rounded-lg bg-[canvas] text-gray-900 shadow-lg shadow-gray-200 outline outline-1 outline-gray-200 transition-[opacity,transform,width,height,scale,translate] duration-[var(--duration)] ease-[var(--easing)] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[ending-style]:duration-150 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 min-[500px]:w-[var(--popup-width)] xs:w-[var(--popup-width)] dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300">
+          <NavigationMenu.Popup className="data-[ending-style]:easing-[ease] relative h-[var(--popup-height)] origin-[var(--transform-origin)] rounded-lg bg-[canvas] text-gray-900 shadow-lg shadow-gray-200 outline outline-1 outline-gray-200 transition-[opacity,transform,width,height,scale,translate] duration-[var(--duration)] ease-[var(--easing)] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[ending-style]:duration-150 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 w-[var(--popup-width)] dark:shadow-none dark:-outline-offset-1 dark:outline-gray-300">
             <NavigationMenu.Arrow className="flex transition-[left] duration-[var(--duration)] ease-[var(--easing)] data-[side=bottom]:top-[-8px] data-[side=left]:right-[-13px] data-[side=left]:rotate-90 data-[side=right]:left-[-13px] data-[side=right]:-rotate-90 data-[side=top]:bottom-[-8px] data-[side=top]:rotate-180">
               <ArrowSvg />
             </NavigationMenu.Arrow>
diff --git a/docs/src/app/(public)/(content)/react/components/navigation-menu/page.mdx b/docs/src/app/(public)/(content)/react/components/navigation-menu/page.mdx
index ddd96c0bc9..0ca7150121 100644
--- a/docs/src/app/(public)/(content)/react/components/navigation-menu/page.mdx
+++ b/docs/src/app/(public)/(content)/react/components/navigation-menu/page.mdx
@@ -81,8 +81,8 @@ When you have large menu content that doesn't fit in the viewport in some cases,
 You can change the layout of the navigation menu to render less content or be more compact by reducing the space it takes up. If your content is flexible, you can use the `max-height` property on `Popup` to limit the height of the navigation menu to let it compress itself while preventing overflow.
 
 ```css title="Compact layout"
-.Popup,
-.Content {
+.Content,
+.Popup {
   max-height: var(--available-height);
 }
 ```
@@ -90,8 +90,8 @@ You can change the layout of the navigation menu to render less content or be mo
 2. Make the navigation menu scrollable
 
 ```css title="Scrollable layout"
-.Popup,
-.Content {
+.Content,
+.Popup {
   max-height: var(--available-height);
 }
 
diff --git a/packages/react/src/floating-ui-react/middleware/arrow.ts b/packages/react/src/floating-ui-react/middleware/arrow.ts
new file mode 100644
index 0000000000..8d011fb363
--- /dev/null
+++ b/packages/react/src/floating-ui-react/middleware/arrow.ts
@@ -0,0 +1,123 @@
+import type { Derivable, Middleware, Padding } from '@floating-ui/react-dom';
+import {
+  clamp,
+  evaluate,
+  getAlignment,
+  getAlignmentAxis,
+  getAxisLength,
+  getPaddingObject,
+} from '@floating-ui/utils';
+
+export interface ArrowOptions {
+  /**
+   * The arrow element to be positioned.
+   * @default undefined
+   */
+  element: any;
+  /**
+   * The padding between the arrow element and the floating element edges.
+   * Useful when the floating element has rounded corners.
+   * @default 0
+   */
+  padding?: Padding;
+  /**
+   * Which element to use as the offset parent.
+   * @default 'real'
+   */
+  offsetParent: 'real' | 'floating';
+}
+
+/**
+ * Fork of the original `arrow` middleware from Floating UI that allows
+ * configuring the offset parent.
+ */
+export const baseArrow = (options: ArrowOptions | Derivable<ArrowOptions>): Middleware => ({
+  name: 'arrow',
+  options,
+  async fn(state) {
+    const { x, y, placement, rects, platform, elements, middlewareData } = state;
+    // Since `element` is required, we don't Partial<> the type.
+    const { element, padding = 0, offsetParent = 'real' } = evaluate(options, state) || {};
+
+    if (element == null) {
+      return {};
+    }
+
+    const paddingObject = getPaddingObject(padding);
+    const coords = { x, y };
+    const axis = getAlignmentAxis(placement);
+    const length = getAxisLength(axis);
+    const arrowDimensions = await platform.getDimensions(element);
+    const isYAxis = axis === 'y';
+    const minProp = isYAxis ? 'top' : 'left';
+    const maxProp = isYAxis ? 'bottom' : 'right';
+    const clientProp = isYAxis ? 'clientHeight' : 'clientWidth';
+
+    const endDiff =
+      rects.reference[length] + rects.reference[axis] - coords[axis] - rects.floating[length];
+    const startDiff = coords[axis] - rects.reference[axis];
+
+    const arrowOffsetParent =
+      offsetParent === 'real' ? await platform.getOffsetParent?.(element) : elements.floating;
+    let clientSize = elements.floating[clientProp] || rects.floating[length];
+
+    // DOM platform can return `window` as the `offsetParent`.
+    if (!clientSize || !(await platform.isElement?.(arrowOffsetParent))) {
+      clientSize = elements.floating[clientProp] || rects.floating[length];
+    }
+
+    const centerToReference = endDiff / 2 - startDiff / 2;
+
+    // If the padding is large enough that it causes the arrow to no longer be
+    // centered, modify the padding so that it is centered.
+    const largestPossiblePadding = clientSize / 2 - arrowDimensions[length] / 2 - 1;
+    const minPadding = Math.min(paddingObject[minProp], largestPossiblePadding);
+    const maxPadding = Math.min(paddingObject[maxProp], largestPossiblePadding);
+
+    // Make sure the arrow doesn't overflow the floating element if the center
+    // point is outside the floating element's bounds.
+    const min = minPadding;
+    const max = clientSize - arrowDimensions[length] - maxPadding;
+    const center = clientSize / 2 - arrowDimensions[length] / 2 + centerToReference;
+    const offset = clamp(min, center, max);
+
+    // If the reference is small enough that the arrow's padding causes it to
+    // to point to nothing for an aligned placement, adjust the offset of the
+    // floating element itself. To ensure `shift()` continues to take action,
+    // a single reset is performed when this is true.
+    const shouldAddOffset =
+      !middlewareData.arrow &&
+      getAlignment(placement) != null &&
+      center !== offset &&
+      rects.reference[length] / 2 -
+        (center < min ? minPadding : maxPadding) -
+        arrowDimensions[length] / 2 <
+        0;
+    // eslint-disable-next-line no-nested-ternary
+    const alignmentOffset = shouldAddOffset ? (center < min ? center - min : center - max) : 0;
+
+    return {
+      [axis]: coords[axis] + alignmentOffset,
+      data: {
+        [axis]: offset,
+        centerOffset: center - offset - alignmentOffset,
+        ...(shouldAddOffset && { alignmentOffset }),
+      },
+      reset: shouldAddOffset,
+    };
+  },
+});
+
+/**
+ * Provides data to position an inner element of the floating element so that it
+ * appears centered to the reference element.
+ * This wraps the core `arrow` middleware to allow React refs as the element.
+ * @see https://floating-ui.com/docs/arrow
+ */
+export const arrow = (
+  options: ArrowOptions | Derivable<ArrowOptions>,
+  deps?: React.DependencyList,
+): Middleware => ({
+  ...baseArrow(options),
+  options: [options, deps],
+});
diff --git a/packages/react/src/navigation-menu/content/NavigationMenuContent.tsx b/packages/react/src/navigation-menu/content/NavigationMenuContent.tsx
index d14ae8ce42..535573499e 100644
--- a/packages/react/src/navigation-menu/content/NavigationMenuContent.tsx
+++ b/packages/react/src/navigation-menu/content/NavigationMenuContent.tsx
@@ -49,6 +49,7 @@ export const NavigationMenuContent = React.forwardRef(function NavigationMenuCon
     value,
     activationDirection,
     currentContentRef,
+    viewportTargetElement,
   } = useNavigationMenuRootContext();
   const itemValue = useNavigationMenuItemContext();
   const nodeId = useNavigationMenuTreeContext();
@@ -109,9 +110,10 @@ export const NavigationMenuContent = React.forwardRef(function NavigationMenuCon
         }
       : commonProps;
 
-  const shouldRender = viewportElement !== null && mounted;
+  const portalContainer = viewportTargetElement || viewportElement;
+  const shouldRender = portalContainer !== null && mounted;
 
-  if (!viewportElement || !shouldRender) {
+  if (!portalContainer || !shouldRender) {
     return null;
   }
 
@@ -127,7 +129,7 @@ export const NavigationMenuContent = React.forwardRef(function NavigationMenuCon
         stopEventPropagation
       />
     </FloatingNode>,
-    viewportElement,
+    portalContainer,
   );
 });
 
diff --git a/packages/react/src/navigation-menu/link/NavigationMenuLink.tsx b/packages/react/src/navigation-menu/link/NavigationMenuLink.tsx
index f7ad731c23..0d378af55b 100644
--- a/packages/react/src/navigation-menu/link/NavigationMenuLink.tsx
+++ b/packages/react/src/navigation-menu/link/NavigationMenuLink.tsx
@@ -21,7 +21,7 @@ export const NavigationMenuLink = React.forwardRef(function NavigationMenuLink(
 ) {
   const { className, render, ...elementProps } = componentProps;
 
-  const { setValue, popupElement, rootRef } = useNavigationMenuRootContext();
+  const { setValue, popupElement, positionerElement, rootRef } = useNavigationMenuRootContext();
   const nodeId = useNavigationMenuTreeContext();
   const tree = useFloatingTree();
 
@@ -29,6 +29,8 @@ export const NavigationMenuLink = React.forwardRef(function NavigationMenuLink(
     tabIndex: undefined,
     onBlur(event) {
       if (
+        positionerElement &&
+        popupElement &&
         isOutsideMenuEvent(
           {
             currentTarget: event.currentTarget,
@@ -37,7 +39,7 @@ export const NavigationMenuLink = React.forwardRef(function NavigationMenuLink(
           { popupElement, rootRef, tree, nodeId },
         )
       ) {
-        setValue(null, event.nativeEvent, undefined);
+        setValue(null, event.nativeEvent, 'focus-out');
       }
     },
   };
diff --git a/packages/react/src/navigation-menu/list/NavigationMenuList.tsx b/packages/react/src/navigation-menu/list/NavigationMenuList.tsx
index b02967c19a..528ab4e247 100644
--- a/packages/react/src/navigation-menu/list/NavigationMenuList.tsx
+++ b/packages/react/src/navigation-menu/list/NavigationMenuList.tsx
@@ -1,6 +1,6 @@
 'use client';
 import * as React from 'react';
-import type { BaseUIComponentProps } from '../../utils/types';
+import type { BaseUIComponentProps, HTMLProps } from '../../utils/types';
 import { CompositeRoot } from '../../composite/root/CompositeRoot';
 import { useNavigationMenuRootContext } from '../root/NavigationMenuRootContext';
 
@@ -25,13 +25,28 @@ export const NavigationMenuList = React.forwardRef(function NavigationMenuList(
     [open],
   );
 
+  const defaultProps: HTMLProps = {
+    // `stopEventPropagation` won't stop the propagation if the end of the list is reached,
+    // but we want to block it in this case.
+    onKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
+      const shouldStop =
+        (orientation === 'horizontal' &&
+          (event.key === 'ArrowLeft' || event.key === 'ArrowRight')) ||
+        (orientation === 'vertical' && (event.key === 'ArrowUp' || event.key === 'ArrowDown'));
+
+      if (shouldStop) {
+        event.stopPropagation();
+      }
+    },
+  };
+
   return (
     <CompositeRoot
       render={render}
       className={className}
       state={state}
       refs={[forwardedRef]}
-      props={[elementProps]}
+      props={[defaultProps, elementProps]}
       loop={false}
       orientation={orientation}
       stopEventPropagation
diff --git a/packages/react/src/navigation-menu/popup/NavigationMenuPopup.tsx b/packages/react/src/navigation-menu/popup/NavigationMenuPopup.tsx
index 43ad2e8281..9887d5efc4 100644
--- a/packages/react/src/navigation-menu/popup/NavigationMenuPopup.tsx
+++ b/packages/react/src/navigation-menu/popup/NavigationMenuPopup.tsx
@@ -1,18 +1,11 @@
 'use client';
 import * as React from 'react';
-import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect';
-import {
-  getNextTabbable,
-  getPreviousTabbable,
-  isOutsideEvent,
-} from '../../floating-ui-react/utils';
 import type { BaseUIComponentProps } from '../../utils/types';
 import { useRenderElement } from '../../utils/useRenderElement';
 import { useNavigationMenuRootContext } from '../root/NavigationMenuRootContext';
 import type { TransitionStatus } from '../../utils/useTransitionStatus';
 import { transitionStatusMapping } from '../../utils/styleHookMapping';
 import { useBaseUiId } from '../../utils/useBaseUiId';
-import { FocusGuard } from '../../utils/FocusGuard';
 import { useNavigationMenuPositionerContext } from '../positioner/NavigationMenuPositionerContext';
 import { useDirection } from '../../direction-provider/DirectionContext';
 import { CustomStyleHookMapping } from '../../utils/getStyleHookProps';
@@ -35,17 +28,7 @@ export const NavigationMenuPopup = React.forwardRef(function NavigationMenuPopup
 ) {
   const { className, render, id: idProp, ...elementProps } = componentProps;
 
-  const {
-    open,
-    transitionStatus,
-    popupElement,
-    positionerElement,
-    setPopupElement,
-    beforeInsideRef,
-    beforeOutsideRef,
-    afterInsideRef,
-    afterOutsideRef,
-  } = useNavigationMenuRootContext();
+  const { open, transitionStatus, setPopupElement } = useNavigationMenuRootContext();
   const positioning = useNavigationMenuPositionerContext();
   const direction = useDirection();
 
@@ -62,19 +45,6 @@ export const NavigationMenuPopup = React.forwardRef(function NavigationMenuPopup
     [open, transitionStatus, positioning.side, positioning.align, positioning.anchorHidden],
   );
 
-  // Allow the arrow to transition while the popup's size transitions.
-  useIsoLayoutEffect(() => {
-    if (!popupElement || typeof ResizeObserver === 'undefined') {
-      return undefined;
-    }
-
-    const observer = new ResizeObserver(positioning.update);
-    observer.observe(popupElement);
-    return () => {
-      observer.disconnect();
-    };
-  }, [popupElement, positioning.update]);
-
   // Ensure popup size transitions correctly when anchored to `bottom` (side=top) or `right` (side=left).
   let isOriginSide = positioning.side === 'top';
   let isPhysicalLeft = positioning.side === 'left';
@@ -106,31 +76,7 @@ export const NavigationMenuPopup = React.forwardRef(function NavigationMenuPopup
     customStyleHookMapping,
   });
 
-  return (
-    <React.Fragment>
-      <FocusGuard
-        ref={beforeInsideRef}
-        onFocus={(event) => {
-          if (positionerElement && isOutsideEvent(event, positionerElement)) {
-            getNextTabbable(positionerElement)?.focus();
-          } else {
-            beforeOutsideRef.current?.focus();
-          }
-        }}
-      />
-      {element}
-      <FocusGuard
-        ref={afterInsideRef}
-        onFocus={(event) => {
-          if (positionerElement && isOutsideEvent(event, positionerElement)) {
-            getPreviousTabbable(positionerElement)?.focus();
-          } else {
-            afterOutsideRef.current?.focus();
-          }
-        }}
-      />
-    </React.Fragment>
-  );
+  return element;
 });
 
 export namespace NavigationMenuPopup {
diff --git a/packages/react/src/navigation-menu/positioner/NavigationMenuPositioner.tsx b/packages/react/src/navigation-menu/positioner/NavigationMenuPositioner.tsx
index fd28d20b31..26c4847d53 100644
--- a/packages/react/src/navigation-menu/positioner/NavigationMenuPositioner.tsx
+++ b/packages/react/src/navigation-menu/positioner/NavigationMenuPositioner.tsx
@@ -1,8 +1,9 @@
 'use client';
 import * as React from 'react';
+import * as ReactDOM from 'react-dom';
 import { getSide } from '@floating-ui/utils';
-import { ownerDocument } from '@base-ui-components/utils/owner';
-import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect';
+import { ownerDocument, ownerWindow } from '@base-ui-components/utils/owner';
+import { useTimeout } from '@base-ui-components/utils/useTimeout';
 import type { Middleware } from '../../floating-ui-react';
 import {
   disableFocusInside,
@@ -18,7 +19,6 @@ import {
 import { useNavigationMenuPortalContext } from '../portal/NavigationMenuPortalContext';
 import { useAnchorPositioning, type Align, type Side } from '../../utils/useAnchorPositioning';
 import { NavigationMenuPositionerContext } from './NavigationMenuPositionerContext';
-import { useAnimationsFinished } from '../../utils/useAnimationsFinished';
 import { popupStateMapping } from '../../utils/popupStateMapping';
 import { DROPDOWN_COLLISION_AVOIDANCE, POPUP_COLLISION_AVOIDANCE } from '../../utils/constants';
 
@@ -114,41 +114,13 @@ export const NavigationMenuPositioner = React.forwardRef(function NavigationMenu
   const keepMounted = useNavigationMenuPortalContext();
   const nodeId = useNavigationMenuTreeContext();
 
-  const [instant, setInstant] = React.useState(true);
+  const resizeTimeout = useTimeout();
+
+  const [instant, setInstant] = React.useState(false);
 
   const positionerRef = React.useRef<HTMLDivElement | null>(null);
   const prevTriggerElementRef = React.useRef<Element | null>(null);
 
-  const runOnceAnimationsFinish = useAnimationsFinished(positionerRef);
-
-  // When the current trigger element changes, enable transitions on the
-  // positioner temporarily
-  useIsoLayoutEffect(() => {
-    const currentTriggerElement = floatingRootContext?.elements.domReference;
-    const prevTriggerElement = prevTriggerElementRef.current;
-
-    if (currentTriggerElement) {
-      prevTriggerElementRef.current = currentTriggerElement;
-    }
-
-    if (
-      prevTriggerElement &&
-      currentTriggerElement &&
-      currentTriggerElement !== prevTriggerElement
-    ) {
-      setInstant(false);
-      const ac = new AbortController();
-      runOnceAnimationsFinish(() => {
-        setInstant(true);
-      }, ac.signal);
-      return () => {
-        ac.abort();
-      };
-    }
-
-    return undefined;
-  }, [floatingRootContext?.elements.domReference, runOnceAnimationsFinish]);
-
   // https://codesandbox.io/s/tabbable-portal-f4tng?file=/src/TabbablePortal.tsx
   React.useEffect(() => {
     if (!positionerElement) {
@@ -226,6 +198,28 @@ export const NavigationMenuPositioner = React.forwardRef(function NavigationMenu
     [open, positioning.side, positioning.align, positioning.anchorHidden, instant],
   );
 
+  React.useEffect(() => {
+    if (!open) {
+      return undefined;
+    }
+
+    function handleResize() {
+      ReactDOM.flushSync(() => {
+        setInstant(true);
+      });
+
+      resizeTimeout.start(100, () => {
+        setInstant(false);
+      });
+    }
+
+    const win = ownerWindow(positionerElement);
+    win.addEventListener('resize', handleResize);
+    return () => {
+      win.removeEventListener('resize', handleResize);
+    };
+  }, [open, resizeTimeout, positionerElement]);
+
   const element = useRenderElement('div', componentProps, {
     state,
     ref: [forwardedRef, setPositionerElement, positionerRef],
diff --git a/packages/react/src/navigation-menu/positioner/NavigationMenuPositionerContext.ts b/packages/react/src/navigation-menu/positioner/NavigationMenuPositionerContext.ts
index c890451504..513941482c 100644
--- a/packages/react/src/navigation-menu/positioner/NavigationMenuPositionerContext.ts
+++ b/packages/react/src/navigation-menu/positioner/NavigationMenuPositionerContext.ts
@@ -2,17 +2,21 @@
 import * as React from 'react';
 import { useAnchorPositioning } from '../../utils/useAnchorPositioning';
 
+export type NavigationMenuPositionerContext = ReturnType<typeof useAnchorPositioning>;
+
 export const NavigationMenuPositionerContext = React.createContext<
-  ReturnType<typeof useAnchorPositioning> | undefined
+  NavigationMenuPositionerContext | undefined
 >(undefined);
 
-if (process.env.NODE_ENV !== 'production') {
-  NavigationMenuPositionerContext.displayName = 'NavigationMenuPositionerContext';
-}
-
-export function useNavigationMenuPositionerContext() {
+export function useNavigationMenuPositionerContext(
+  optional: true,
+): NavigationMenuPositionerContext | undefined;
+export function useNavigationMenuPositionerContext(
+  optional?: false,
+): NavigationMenuPositionerContext;
+export function useNavigationMenuPositionerContext(optional = false) {
   const context = React.useContext(NavigationMenuPositionerContext);
-  if (context === undefined) {
+  if (!context && !optional) {
     throw new Error(
       'Base UI: NavigationMenuPositionerContext is missing. NavigationMenuPositioner parts must be placed within <NavigationMenu.Positioner>.',
     );
diff --git a/packages/react/src/navigation-menu/root/NavigationMenuRoot.tsx b/packages/react/src/navigation-menu/root/NavigationMenuRoot.tsx
index 0375b53061..ac1a8d2527 100644
--- a/packages/react/src/navigation-menu/root/NavigationMenuRoot.tsx
+++ b/packages/react/src/navigation-menu/root/NavigationMenuRoot.tsx
@@ -21,6 +21,7 @@ import {
 import type { BaseOpenChangeReason } from '../../utils/translateOpenChangeReason';
 import { useOpenChangeComplete } from '../../utils/useOpenChangeComplete';
 import { useTransitionStatus } from '../../utils/useTransitionStatus';
+import { setFixedSize } from '../utils/setFixedSize';
 
 /**
  * Groups all parts of the navigation menu.
@@ -61,11 +62,15 @@ export const NavigationMenuRoot = React.forwardRef(function NavigationMenuRoot(
   const [positionerElement, setPositionerElement] = React.useState<HTMLElement | null>(null);
   const [popupElement, setPopupElement] = React.useState<HTMLElement | null>(null);
   const [viewportElement, setViewportElement] = React.useState<HTMLElement | null>(null);
+  const [viewportTargetElement, setViewportTargetElement] = React.useState<HTMLElement | null>(
+    null,
+  );
   const [activationDirection, setActivationDirection] =
     React.useState<NavigationMenuRootContext['activationDirection']>(null);
   const [floatingRootContext, setFloatingRootContext] = React.useState<
     FloatingRootContext | undefined
   >(undefined);
+  const [viewportInert, setViewportInert] = React.useState(false);
 
   const prevTriggerElementRef = React.useRef<Element | null | undefined>(null);
   const currentContentRef = React.useRef<HTMLDivElement | null>(null);
@@ -76,12 +81,21 @@ export const NavigationMenuRoot = React.forwardRef(function NavigationMenuRoot(
 
   const { mounted, setMounted, transitionStatus } = useTransitionStatus(open);
 
+  React.useEffect(() => {
+    setViewportInert(false);
+  }, [value]);
+
   const setValue = useEventCallback(
     (nextValue: any, event: Event | undefined, reason: BaseOpenChangeReason | undefined) => {
       if (!nextValue) {
         closeReasonRef.current = reason;
         setActivationDirection(null);
         setFloatingRootContext(undefined);
+
+        if (positionerElement && popupElement) {
+          setFixedSize(popupElement, 'popup');
+          setFixedSize(positionerElement, 'positioner');
+        }
       }
 
       if (nextValue !== value) {
@@ -99,7 +113,8 @@ export const NavigationMenuRoot = React.forwardRef(function NavigationMenuRoot(
     if (
       closeReasonRef.current !== 'trigger-hover' &&
       isHTMLElement(prevTriggerElementRef.current) &&
-      (contains(popupElement, activeEl) || activeEl === doc.body)
+      contains(popupElement, activeEl) &&
+      popupElement
     ) {
       prevTriggerElementRef.current.focus({ preventScroll: true });
       prevTriggerElementRef.current = undefined;
@@ -125,6 +140,17 @@ export const NavigationMenuRoot = React.forwardRef(function NavigationMenuRoot(
     },
   });
 
+  useOpenChangeComplete({
+    enabled: !actionsRef,
+    open,
+    ref: { current: viewportTargetElement },
+    onComplete() {
+      if (!open) {
+        handleUnmount();
+      }
+    },
+  });
+
   const contextValue: NavigationMenuRootContext = React.useMemo(
     () => ({
       open,
@@ -138,6 +164,8 @@ export const NavigationMenuRoot = React.forwardRef(function NavigationMenuRoot(
       setPopupElement,
       viewportElement,
       setViewportElement,
+      viewportTargetElement,
+      setViewportTargetElement,
       activationDirection,
       setActivationDirection,
       floatingRootContext,
@@ -153,6 +181,8 @@ export const NavigationMenuRoot = React.forwardRef(function NavigationMenuRoot(
       delay,
       closeDelay,
       orientation,
+      viewportInert,
+      setViewportInert,
     }),
     [
       open,
@@ -163,12 +193,14 @@ export const NavigationMenuRoot = React.forwardRef(function NavigationMenuRoot(
       positionerElement,
       popupElement,
       viewportElement,
+      viewportTargetElement,
       activationDirection,
       floatingRootContext,
       nested,
       delay,
       closeDelay,
       orientation,
+      viewportInert,
     ],
   );
 
diff --git a/packages/react/src/navigation-menu/root/NavigationMenuRootContext.ts b/packages/react/src/navigation-menu/root/NavigationMenuRootContext.ts
index d76e390a2c..ff0dc001d8 100644
--- a/packages/react/src/navigation-menu/root/NavigationMenuRootContext.ts
+++ b/packages/react/src/navigation-menu/root/NavigationMenuRootContext.ts
@@ -15,6 +15,8 @@ export interface NavigationMenuRootContext {
   setPositionerElement: React.Dispatch<React.SetStateAction<HTMLElement | null>>;
   viewportElement: HTMLElement | null;
   setViewportElement: React.Dispatch<React.SetStateAction<HTMLElement | null>>;
+  viewportTargetElement: HTMLElement | null;
+  setViewportTargetElement: React.Dispatch<React.SetStateAction<HTMLElement | null>>;
   activationDirection: 'left' | 'right' | 'up' | 'down' | null;
   setActivationDirection: React.Dispatch<
     React.SetStateAction<'left' | 'right' | 'up' | 'down' | null>
@@ -32,6 +34,8 @@ export interface NavigationMenuRootContext {
   delay: number;
   closeDelay: number;
   orientation: 'horizontal' | 'vertical';
+  viewportInert: boolean;
+  setViewportInert: React.Dispatch<React.SetStateAction<boolean>>;
 }
 
 export const NavigationMenuRootContext = React.createContext<NavigationMenuRootContext | undefined>(
diff --git a/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.tsx b/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.tsx
index 5de1725a03..174655f62c 100644
--- a/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.tsx
+++ b/packages/react/src/navigation-menu/trigger/NavigationMenuTrigger.tsx
@@ -1,6 +1,7 @@
 'use client';
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
+import { isTabbable } from 'tabbable';
 import { useEventCallback } from '@base-ui-components/utils/useEventCallback';
 import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect';
 import { visuallyHidden } from '@base-ui-components/utils/visuallyHidden';
@@ -43,8 +44,11 @@ import { NavigationMenuPopupCssVars } from '../popup/NavigationMenuPopupCssVars'
 import { NavigationMenuPositionerCssVars } from '../positioner/NavigationMenuPositionerCssVars';
 import { CompositeItem } from '../../composite/item/CompositeItem';
 import { useButton } from '../../use-button';
+import { getCssDimensions } from '../../utils/getCssDimensions';
 
 const TRIGGER_IDENTIFIER = 'data-navigation-menu-trigger';
+const DEFAULT_SIZE = { width: 0, height: 0 };
+const DEFAULT_ABORT_CONTROLLER = new AbortController();
 
 /**
  * Opens the navigation menu popup when hovered or clicked, revealing the
@@ -68,6 +72,7 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri
     setActivationDirection,
     setFloatingRootContext,
     popupElement,
+    viewportElement,
     rootRef,
     beforeOutsideRef,
     afterOutsideRef,
@@ -77,6 +82,7 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri
     delay,
     closeDelay,
     orientation,
+    setViewportInert,
   } = useNavigationMenuRootContext();
   const itemValue = useNavigationMenuItemContext();
   const nodeId = useNavigationMenuTreeContext();
@@ -91,10 +97,28 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri
   const [stickIfOpen, setStickIfOpen] = React.useState(true);
   const [pointerType, setPointerType] = React.useState<'mouse' | 'touch' | 'pen' | ''>('');
 
+  const allowFocusRef = React.useRef(false);
+  const prevSizeRef = React.useRef(DEFAULT_SIZE);
+  const animationAbortControllerRef = React.useRef(DEFAULT_ABORT_CONTROLLER);
+
   const isActiveItem = open && value === itemValue;
   const isActiveItemRef = useLatestRef(isActiveItem);
+  const interactionsEnabled = positionerElement ? true : !value;
 
-  const allowFocusRef = React.useRef(false);
+  const runOnceAnimationsFinish = useAnimationsFinished(popupElement);
+
+  React.useEffect(() => {
+    animationAbortControllerRef.current.abort();
+  }, [isActiveItem]);
+
+  const setAutoSizes = useEventCallback(() => {
+    if (!popupElement) {
+      return;
+    }
+
+    popupElement.style.setProperty(NavigationMenuPopupCssVars.popupWidth, 'auto');
+    popupElement.style.setProperty(NavigationMenuPopupCssVars.popupHeight, 'auto');
+  });
 
   const handleValueChange = useEventCallback((currentWidth: number, currentHeight: number) => {
     if (!popupElement || !positionerElement) {
@@ -106,99 +130,84 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri
     positionerElement.style.removeProperty(NavigationMenuPositionerCssVars.positionerWidth);
     positionerElement.style.removeProperty(NavigationMenuPositionerCssVars.positionerHeight);
 
-    const nextWidth = popupElement.offsetWidth;
-    const nextHeight = popupElement.offsetHeight;
+    const { width, height } = getCssDimensions(popupElement);
 
     if (currentHeight === 0 || currentWidth === 0) {
-      currentWidth = nextWidth;
-      currentHeight = nextHeight;
+      currentWidth = width;
+      currentHeight = height;
     }
 
     popupElement.style.setProperty(NavigationMenuPopupCssVars.popupWidth, `${currentWidth}px`);
     popupElement.style.setProperty(NavigationMenuPopupCssVars.popupHeight, `${currentHeight}px`);
     positionerElement.style.setProperty(
       NavigationMenuPositionerCssVars.positionerWidth,
-      `${nextWidth}px`,
+      `${width}px`,
     );
     positionerElement.style.setProperty(
       NavigationMenuPositionerCssVars.positionerHeight,
-      `${nextHeight}px`,
+      `${height}px`,
     );
 
     sizeFrame1.request(() => {
       sizeFrame2.request(() => {
-        popupElement.style.setProperty(NavigationMenuPopupCssVars.popupWidth, `${nextWidth}px`);
-        popupElement.style.setProperty(NavigationMenuPopupCssVars.popupHeight, `${nextHeight}px`);
+        popupElement.style.setProperty(NavigationMenuPopupCssVars.popupWidth, `${width}px`);
+        popupElement.style.setProperty(NavigationMenuPopupCssVars.popupHeight, `${height}px`);
+
+        animationAbortControllerRef.current = new AbortController();
+        runOnceAnimationsFinish(setAutoSizes, animationAbortControllerRef.current.signal);
       });
     });
   });
 
-  const setAutoSizes = useEventCallback(() => {
-    if (!popupElement) {
-      return;
+  React.useEffect(() => {
+    if (!open) {
+      stickIfOpenTimeout.clear();
+      sizeFrame1.cancel();
+      sizeFrame2.cancel();
+      prevSizeRef.current = DEFAULT_SIZE;
     }
+  }, [stickIfOpenTimeout, open, sizeFrame1, sizeFrame2]);
 
-    popupElement.style.setProperty(NavigationMenuPopupCssVars.popupWidth, 'auto');
-    popupElement.style.setProperty(NavigationMenuPopupCssVars.popupHeight, 'auto');
-  });
-
-  const runOnceAnimationsFinish = useAnimationsFinished({ current: popupElement }, value);
-
-  useIsoLayoutEffect(() => {
-    if (!positionerElement || !popupElement || !open) {
+  React.useEffect(() => {
+    if (!popupElement || typeof ResizeObserver !== 'function') {
       return undefined;
     }
 
-    sizeFrame1.request(() => {
-      sizeFrame2.request(setAutoSizes);
+    const resizeObserver = new ResizeObserver(() => {
+      // Using `getCssDimensions` here causes issues due to fractional values.
+      prevSizeRef.current = {
+        width: popupElement.offsetWidth,
+        height: popupElement.offsetHeight,
+      };
     });
+
+    resizeObserver.observe(popupElement);
+
     return () => {
-      sizeFrame1.cancel();
-      sizeFrame2.cancel();
+      resizeObserver.disconnect();
     };
-  }, [open, popupElement, positionerElement, sizeFrame1, sizeFrame2, setAutoSizes]);
+  }, [popupElement]);
 
-  useIsoLayoutEffect(() => {
-    if (!positionerElement || !popupElement || !value) {
+  React.useEffect(() => {
+    if (!popupElement || !isActiveItem || typeof MutationObserver !== 'function') {
       return undefined;
     }
 
-    const ac = new AbortController();
-    sizeFrame1.request(() => {
-      sizeFrame2.request(() => {
-        runOnceAnimationsFinish(setAutoSizes, ac.signal);
-      });
+    const mutationObserver = new MutationObserver(() => {
+      animationAbortControllerRef.current.abort();
+      handleValueChange(prevSizeRef.current.width, prevSizeRef.current.height);
+    });
+
+    mutationObserver.observe(popupElement, {
+      childList: true,
+      subtree: true,
+      characterData: true,
     });
 
     return () => {
-      sizeFrame1.cancel();
-      sizeFrame2.cancel();
-      ac.abort();
+      mutationObserver.disconnect();
     };
-  }, [
-    value,
-    popupElement,
-    positionerElement,
-    runOnceAnimationsFinish,
-    sizeFrame1,
-    sizeFrame2,
-    setAutoSizes,
-  ]);
-
-  React.useEffect(() => {
-    if (!open) {
-      setPointerType('');
-      stickIfOpenTimeout.clear();
-      sizeFrame1.cancel();
-      sizeFrame2.cancel();
-    }
-  }, [stickIfOpenTimeout, open, sizeFrame1, sizeFrame2]);
-
-  useIsoLayoutEffect(() => {
-    if (isActiveItemRef.current && open && popupElement) {
-      handleValueChange(0, 0);
-    }
-  }, [isActiveItemRef, open, popupElement, handleValueChange]);
+  }, [popupElement, positionerElement, isActiveItem, handleValueChange, setAutoSizes]);
 
   React.useEffect(() => {
     if (isActiveItem && open && popupElement && allowFocusRef.current) {
@@ -213,6 +222,12 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri
     };
   }, [beforeOutsideRef, focusFrame, handleValueChange, isActiveItem, open, popupElement]);
 
+  useIsoLayoutEffect(() => {
+    if (isActiveItemRef.current && open && popupElement) {
+      handleValueChange(0, 0);
+    }
+  }, [isActiveItemRef, open, popupElement, handleValueChange]);
+
   function handleOpenChange(
     nextOpen: boolean,
     event: Event | undefined,
@@ -220,31 +235,16 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri
   ) {
     const isHover = reason === 'trigger-hover';
 
-    if (pointerType === 'touch' && isHover) {
+    if (!interactionsEnabled) {
       return;
     }
 
-    if (!nextOpen && value !== itemValue) {
+    if (pointerType === 'touch' && isHover) {
       return;
     }
 
-    if (!nextOpen && popupElement && positionerElement) {
-      popupElement.style.setProperty(
-        NavigationMenuPopupCssVars.popupWidth,
-        `${popupElement.offsetWidth}px`,
-      );
-      popupElement.style.setProperty(
-        NavigationMenuPopupCssVars.popupHeight,
-        `${popupElement.offsetHeight}px`,
-      );
-      positionerElement.style.setProperty(
-        NavigationMenuPositionerCssVars.positionerWidth,
-        `${positionerElement.offsetWidth}px`,
-      );
-      positionerElement.style.setProperty(
-        NavigationMenuPositionerCssVars.positionerHeight,
-        `${positionerElement.offsetHeight}px`,
-      );
+    if (!nextOpen && value !== itemValue) {
+      return;
     }
 
     function changeState() {
@@ -262,6 +262,7 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri
         setValue(itemValue, event, reason);
       } else {
         setValue(null, event, reason);
+        setPointerType('');
       }
     }
 
@@ -279,22 +280,23 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri
     },
     elements: {
       reference: triggerElement,
-      floating: positionerElement,
+      floating: positionerElement || viewportElement,
     },
   });
 
   const hover = useHover(context, {
     move: false,
     handleClose: safePolygon({ blockPointerEvents: pointerType !== 'touch' }),
-    restMs: mounted ? 0 : delay,
+    restMs: mounted && positionerElement ? 0 : delay,
     delay: { close: closeDelay },
   });
   const click = useClick(context, {
+    enabled: interactionsEnabled,
     stickIfOpen,
     toggle: isActiveItem,
   });
   const dismiss = useDismiss(context, {
-    enabled: isActiveItem,
+    enabled: isActiveItem && interactionsEnabled,
     outsidePress(event) {
       // When pressing a new trigger with touch input, prevent closing the popup.
       const target = getTarget(event) as HTMLElement | null;
@@ -312,14 +314,7 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri
 
   const { getReferenceProps } = useInteractions([hover, click, dismiss]);
 
-  const handleOpenEvent = useEventCallback((event: React.MouseEvent | React.KeyboardEvent) => {
-    if (!popupElement || !positionerElement) {
-      return;
-    }
-
-    const currentWidth = popupElement.offsetWidth;
-    const currentHeight = popupElement.offsetHeight;
-
+  function handleActivation(event: React.MouseEvent | React.KeyboardEvent) {
     ReactDOM.flushSync(() => {
       const prevTriggerRect = prevTriggerElementRef.current?.getBoundingClientRect();
 
@@ -354,8 +349,20 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri
         );
       }
     });
+  }
+
+  const handleOpenEvent = useEventCallback((event: React.MouseEvent | React.KeyboardEvent) => {
+    // For nested scenarios without positioner/popup, we can still open the menu
+    // but we can't do size calculations
+    if (!popupElement || !positionerElement) {
+      handleActivation(event);
+      return;
+    }
 
-    handleValueChange(currentWidth, currentHeight);
+    const { width, height } = getCssDimensions(popupElement);
+
+    handleActivation(event);
+    handleValueChange(width, height);
   });
 
   const state: NavigationMenuTrigger.State = React.useMemo(
@@ -378,6 +385,12 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri
     'aria-expanded': isActiveItem,
     'aria-controls': isActiveItem ? popupElement?.id : undefined,
     [TRIGGER_IDENTIFIER as string]: '',
+    onFocus() {
+      if (!isActiveItem) {
+        return;
+      }
+      setViewportInert(false);
+    },
     onMouseMove() {
       allowFocusRef.current = false;
     },
@@ -394,7 +407,8 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri
     },
     onBlur(event) {
       if (
-        event.relatedTarget &&
+        positionerElement &&
+        popupElement &&
         isOutsideMenuEvent(
           {
             currentTarget: event.currentTarget,
@@ -414,6 +428,8 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri
     native: nativeButton,
   });
 
+  const referenceElement = positionerElement || viewportElement;
+
   return (
     <React.Fragment>
       <CompositeItem
@@ -430,7 +446,7 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri
           <FocusGuard
             ref={beforeOutsideRef}
             onFocus={(event) => {
-              if (positionerElement && isOutsideEvent(event, positionerElement)) {
+              if (referenceElement && isOutsideEvent(event, referenceElement)) {
                 beforeInsideRef.current?.focus();
               } else {
                 const prevTabbable = getPreviousTabbable(triggerElement);
@@ -438,12 +454,16 @@ export const NavigationMenuTrigger = React.forwardRef(function NavigationMenuTri
               }
             }}
           />
-          <span aria-owns={popupElement?.id} style={visuallyHidden} />
+          <span aria-owns={viewportElement?.id} style={visuallyHidden} />
           <FocusGuard
             ref={afterOutsideRef}
             onFocus={(event) => {
-              if (positionerElement && isOutsideEvent(event, positionerElement)) {
-                afterInsideRef.current?.focus();
+              if (referenceElement && isOutsideEvent(event, referenceElement)) {
+                const elementToFocus =
+                  afterInsideRef.current && isTabbable(afterInsideRef.current)
+                    ? afterInsideRef.current
+                    : triggerElement;
+                elementToFocus?.focus();
               } else {
                 const nextTabbable = getNextTabbable(triggerElement);
                 nextTabbable?.focus();
diff --git a/packages/react/src/navigation-menu/utils/isOutsideMenuEvent.ts b/packages/react/src/navigation-menu/utils/isOutsideMenuEvent.ts
index ae684ced86..47ebb9182b 100644
--- a/packages/react/src/navigation-menu/utils/isOutsideMenuEvent.ts
+++ b/packages/react/src/navigation-menu/utils/isOutsideMenuEvent.ts
@@ -8,13 +8,14 @@ interface Targets {
 
 interface Params {
   popupElement: HTMLElement | null;
+  viewportElement?: HTMLElement | null;
   rootRef: React.RefObject<HTMLDivElement | null>;
   tree: FloatingTreeType | null;
   nodeId: string | undefined;
 }
 
 export function isOutsideMenuEvent({ currentTarget, relatedTarget }: Targets, params: Params) {
-  const { popupElement, rootRef, tree, nodeId } = params;
+  const { popupElement, viewportElement, rootRef, tree, nodeId } = params;
 
   const nodeChildrenContains = tree
     ? getNodeChildren(tree.nodesRef.current, nodeId).some((node) =>
@@ -22,13 +23,22 @@ export function isOutsideMenuEvent({ currentTarget, relatedTarget }: Targets, pa
       )
     : [];
 
+  // For nested scenarios without popupElement, we need to be more lenient
+  // and only close if we're definitely outside the root
+  if (!popupElement) {
+    return !contains(rootRef.current, relatedTarget) && !nodeChildrenContains;
+  }
+
+  // Use popupElement as the primary floating element, but fall back to viewportElement if needed
+  const floatingElement = popupElement || viewportElement;
+
   return (
-    !contains(popupElement, currentTarget) &&
-    !contains(popupElement, relatedTarget) &&
+    !contains(floatingElement, currentTarget) &&
+    !contains(floatingElement, relatedTarget) &&
     !contains(rootRef.current, relatedTarget) &&
     !nodeChildrenContains &&
     !(
-      contains(popupElement, relatedTarget) &&
+      contains(floatingElement, relatedTarget) &&
       relatedTarget?.hasAttribute('data-base-ui-focus-guard')
     )
   );
diff --git a/packages/react/src/navigation-menu/utils/setFixedSize.ts b/packages/react/src/navigation-menu/utils/setFixedSize.ts
new file mode 100644
index 0000000000..b86e9b2d5e
--- /dev/null
+++ b/packages/react/src/navigation-menu/utils/setFixedSize.ts
@@ -0,0 +1,19 @@
+import { getCssDimensions } from '../../utils/getCssDimensions';
+import { NavigationMenuPopupCssVars } from '../popup/NavigationMenuPopupCssVars';
+import { NavigationMenuPositionerCssVars } from '../positioner/NavigationMenuPositionerCssVars';
+
+export function setFixedSize(element: HTMLElement, type: 'popup' | 'positioner') {
+  const { width, height } = getCssDimensions(element);
+  element.style.setProperty(
+    type === 'popup'
+      ? NavigationMenuPopupCssVars.popupWidth
+      : NavigationMenuPositionerCssVars.positionerWidth,
+    `${width}px`,
+  );
+  element.style.setProperty(
+    type === 'popup'
+      ? NavigationMenuPopupCssVars.popupHeight
+      : NavigationMenuPositionerCssVars.positionerHeight,
+    `${height}px`,
+  );
+}
diff --git a/packages/react/src/navigation-menu/viewport/NavigationMenuViewport.tsx b/packages/react/src/navigation-menu/viewport/NavigationMenuViewport.tsx
index e6897d06f8..ad10648c33 100644
--- a/packages/react/src/navigation-menu/viewport/NavigationMenuViewport.tsx
+++ b/packages/react/src/navigation-menu/viewport/NavigationMenuViewport.tsx
@@ -1,8 +1,64 @@
 'use client';
 import * as React from 'react';
+import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect';
+import { useId } from '@base-ui-components/utils/useId';
+import { inertValue } from '@base-ui-components/utils/inertValue';
 import type { BaseUIComponentProps } from '../../utils/types';
 import { useRenderElement } from '../../utils/useRenderElement';
 import { useNavigationMenuRootContext } from '../root/NavigationMenuRootContext';
+import { FocusGuard } from '../../utils/FocusGuard';
+import {
+  getNextTabbable,
+  getPreviousTabbable,
+  isOutsideEvent,
+  contains,
+} from '../../floating-ui-react/utils';
+import { useNavigationMenuPositionerContext } from '../positioner/NavigationMenuPositionerContext';
+
+function Guards({ children }: { children: React.ReactNode }) {
+  const {
+    beforeInsideRef,
+    beforeOutsideRef,
+    afterInsideRef,
+    afterOutsideRef,
+    positionerElement,
+    viewportElement,
+    floatingRootContext,
+  } = useNavigationMenuRootContext();
+  const hasPositioner = Boolean(useNavigationMenuPositionerContext(true));
+
+  const referenceElement = positionerElement || viewportElement;
+
+  if (!floatingRootContext && !hasPositioner) {
+    return children;
+  }
+
+  return (
+    <React.Fragment>
+      <FocusGuard
+        ref={beforeInsideRef}
+        onFocus={(event) => {
+          if (referenceElement && isOutsideEvent(event, referenceElement)) {
+            getNextTabbable(referenceElement)?.focus();
+          } else {
+            beforeOutsideRef.current?.focus();
+          }
+        }}
+      />
+      {children}
+      <FocusGuard
+        ref={afterInsideRef}
+        onFocus={(event) => {
+          if (referenceElement && isOutsideEvent(event, referenceElement)) {
+            getPreviousTabbable(referenceElement)?.focus();
+          } else {
+            afterOutsideRef.current?.focus();
+          }
+        }}
+      />
+    </React.Fragment>
+  );
+}
 
 /**
  * The clipping viewport of the navigation menu's current content.
@@ -14,16 +70,61 @@ export const NavigationMenuViewport = React.forwardRef(function NavigationMenuVi
   componentProps: NavigationMenuViewport.Props,
   forwardedRef: React.ForwardedRef<HTMLDivElement>,
 ) {
-  const { className, render, ...elementProps } = componentProps;
+  const { className, render, children, id: idProp, ...elementProps } = componentProps;
 
-  const { setViewportElement } = useNavigationMenuRootContext();
+  const id = useId(idProp);
+
+  const {
+    setViewportElement,
+    setViewportTargetElement,
+    floatingRootContext,
+    prevTriggerElementRef,
+    viewportInert,
+    setViewportInert,
+  } = useNavigationMenuRootContext();
+
+  const hasPositioner = Boolean(useNavigationMenuPositionerContext(true));
+  const domReference = floatingRootContext?.elements.domReference;
+
+  useIsoLayoutEffect(() => {
+    if (domReference) {
+      prevTriggerElementRef.current = domReference;
+    }
+  }, [domReference, prevTriggerElementRef]);
 
   const element = useRenderElement('div', componentProps, {
     ref: [forwardedRef, setViewportElement],
-    props: elementProps,
+    props: [
+      {
+        id,
+        onBlur(event) {
+          const relatedTarget = event.relatedTarget as Element | null;
+          const currentTarget = event.currentTarget as Element;
+
+          // If focus is leaving the viewport and not going to the trigger, make it inert
+          // to prevent a focus loop.
+          if (
+            relatedTarget &&
+            !contains(currentTarget, relatedTarget) &&
+            relatedTarget !== domReference
+          ) {
+            setViewportInert(true);
+          }
+        },
+        ...(!hasPositioner && viewportInert && { inert: inertValue(true) }),
+        children: hasPositioner ? (
+          children
+        ) : (
+          <Guards>
+            <div ref={setViewportTargetElement}>{children}</div>
+          </Guards>
+        ),
+      },
+      elementProps,
+    ],
   });
 
-  return element;
+  return hasPositioner ? <Guards>{element}</Guards> : element;
 });
 
 export namespace NavigationMenuViewport {
diff --git a/packages/react/src/utils/getCssDimensions.ts b/packages/react/src/utils/getCssDimensions.ts
new file mode 100644
index 0000000000..d37de089a5
--- /dev/null
+++ b/packages/react/src/utils/getCssDimensions.ts
@@ -0,0 +1,24 @@
+import { type Dimensions, round } from '@floating-ui/utils';
+import { getComputedStyle, isHTMLElement } from '@floating-ui/utils/dom';
+
+export function getCssDimensions(element: Element): Dimensions {
+  const css = getComputedStyle(element);
+  // In testing environments, the `width` and `height` properties are empty
+  // strings for SVG elements, returning NaN. Fallback to `0` in this case.
+  let width = parseFloat(css.width) || 0;
+  let height = parseFloat(css.height) || 0;
+  const hasOffset = isHTMLElement(element);
+  const offsetWidth = hasOffset ? element.offsetWidth : width;
+  const offsetHeight = hasOffset ? element.offsetHeight : height;
+  const shouldFallback = round(width) !== offsetWidth || round(height) !== offsetHeight;
+
+  if (shouldFallback) {
+    width = offsetWidth;
+    height = offsetHeight;
+  }
+
+  return {
+    width,
+    height,
+  };
+}
diff --git a/packages/react/src/utils/useAnchorPositioning.ts b/packages/react/src/utils/useAnchorPositioning.ts
index d0f67e7af9..4d00e1d498 100644
--- a/packages/react/src/utils/useAnchorPositioning.ts
+++ b/packages/react/src/utils/useAnchorPositioning.ts
@@ -2,16 +2,15 @@
 import * as React from 'react';
 import { getSide, getAlignment, type Rect, getSideAxis } from '@floating-ui/utils';
 import { ownerDocument } from '@base-ui-components/utils/owner';
-import { useEventCallback } from '@base-ui-components/utils/useEventCallback';
-import { useLatestRef } from '@base-ui-components/utils/useLatestRef';
 import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect';
+import { useLatestRef } from '@base-ui-components/utils/useLatestRef';
+import { useEventCallback } from '@base-ui-components/utils/useEventCallback';
 import {
   autoUpdate,
   flip,
   limitShift,
   offset,
   shift,
-  arrow,
   useFloating,
   size,
   hide,
@@ -27,6 +26,7 @@ import {
   type Middleware,
 } from '../floating-ui-react/index';
 import { useDirection } from '../direction-provider/DirectionContext';
+import { arrow } from '../floating-ui-react/middleware/arrow';
 
 function getLogicalSide(sideParam: Side, renderedSide: PhysicalSide, isRtl: boolean): Side {
   const isLogicalSideParam = sideParam === 'inline-start' || sideParam === 'inline-end';
@@ -268,6 +268,7 @@ export function useAnchorPositioning(
         // we'll create a fake element.
         element: arrowRef.current || document.createElement('div'),
         padding: arrowPadding,
+        offsetParent: 'floating',
       }),
       [arrowPadding],
     ),
diff --git a/packages/react/src/utils/useAnimationsFinished.ts b/packages/react/src/utils/useAnimationsFinished.ts
index fa5526feff..b4ce618d7b 100644
--- a/packages/react/src/utils/useAnimationsFinished.ts
+++ b/packages/react/src/utils/useAnimationsFinished.ts
@@ -6,11 +6,11 @@ import { useEventCallback } from '@base-ui-components/utils/useEventCallback';
 
 /**
  * Executes a function once all animations have finished on the provided element.
- * @param ref - The element to watch for animations.
+ * @param elementOrRef - The element to watch for animations.
  * @param waitForNextTick - Whether to wait for the next tick before checking for animations.
  */
 export function useAnimationsFinished(
-  ref: React.RefObject<HTMLElement | null>,
+  elementOrRef: React.RefObject<HTMLElement | null> | HTMLElement | null,
   waitForNextTick = false,
 ) {
   const frame = useAnimationFrame();
@@ -30,12 +30,21 @@ export function useAnimationsFinished(
     ) => {
       frame.cancel();
 
-      const element = ref.current;
-
-      if (!element) {
+      if (elementOrRef == null) {
         return;
       }
 
+      let element: HTMLElement;
+      if ('current' in elementOrRef) {
+        if (elementOrRef.current == null) {
+          return;
+        }
+
+        element = elementOrRef.current;
+      } else {
+        element = elementOrRef;
+      }
+
       if (typeof element.getAnimations !== 'function' || globalThis.BASE_UI_ANIMATIONS_DISABLED) {
         fnToExecute();
       } else {


Test patch:
diff --git a/packages/react/src/navigation-menu/root/NavigationMenuRoot.test.tsx b/packages/react/src/navigation-menu/root/NavigationMenuRoot.test.tsx
index d6c3cf32a8..a6525d4397 100644
--- a/packages/react/src/navigation-menu/root/NavigationMenuRoot.test.tsx
+++ b/packages/react/src/navigation-menu/root/NavigationMenuRoot.test.tsx
@@ -89,6 +89,59 @@ function TestNestedNavigationMenu() {
   );
 }
 
+function TestInlineNestedNavigationMenu() {
+  return (
+    <NavigationMenu.Root>
+      <NavigationMenu.List>
+        <NavigationMenu.Item value="item-1">
+          <NavigationMenu.Trigger data-testid="trigger-1">Item 1</NavigationMenu.Trigger>
+
+          <NavigationMenu.Content data-testid="popup-1">
+            <NavigationMenu.Link href="#link-1">Link 1</NavigationMenu.Link>
+            <NavigationMenu.Root defaultValue="nested-item-1">
+              <NavigationMenu.List>
+                <NavigationMenu.Item value="nested-item-1">
+                  <NavigationMenu.Trigger data-testid="nested-trigger-1">
+                    Nested Item 1
+                  </NavigationMenu.Trigger>
+                  <NavigationMenu.Content data-testid="nested-popup-1">
+                    <NavigationMenu.Link href="#nested-link-1">Nested Link 1</NavigationMenu.Link>
+                  </NavigationMenu.Content>
+                </NavigationMenu.Item>
+                <NavigationMenu.Item value="nested-item-2">
+                  <NavigationMenu.Trigger data-testid="nested-trigger-2">
+                    Nested Item 2
+                  </NavigationMenu.Trigger>
+                  <NavigationMenu.Content data-testid="nested-popup-2">
+                    <NavigationMenu.Link href="#nested-link-2">Nested Link 2</NavigationMenu.Link>
+                  </NavigationMenu.Content>
+                </NavigationMenu.Item>
+              </NavigationMenu.List>
+
+              <NavigationMenu.Viewport />
+            </NavigationMenu.Root>
+          </NavigationMenu.Content>
+        </NavigationMenu.Item>
+
+        <NavigationMenu.Item value="item-2">
+          <NavigationMenu.Trigger data-testid="trigger-2">Item 2</NavigationMenu.Trigger>
+          <NavigationMenu.Content data-testid="popup-2">
+            <NavigationMenu.Link href="#link-3">Link 3</NavigationMenu.Link>
+          </NavigationMenu.Content>
+        </NavigationMenu.Item>
+      </NavigationMenu.List>
+
+      <NavigationMenu.Portal>
+        <NavigationMenu.Positioner>
+          <NavigationMenu.Popup>
+            <NavigationMenu.Viewport />
+          </NavigationMenu.Popup>
+        </NavigationMenu.Positioner>
+      </NavigationMenu.Portal>
+    </NavigationMenu.Root>
+  );
+}
+
 describe('<NavigationMenu.Root />', () => {
   const { render, clock } = createRenderer({
     clockOptions: {
@@ -472,7 +525,6 @@ describe('<NavigationMenu.Root />', () => {
       await render(<TestNestedNavigationMenu />);
       const trigger1 = screen.getByTestId('trigger-1');
 
-      // Open the first level menu
       fireEvent.mouseEnter(trigger1);
       fireEvent.mouseMove(trigger1);
       clock.tick(OPEN_DELAY);
@@ -482,10 +534,8 @@ describe('<NavigationMenu.Root />', () => {
       expect(popup1).not.to.equal(null);
       expect(trigger1).to.have.attribute('aria-expanded', 'true');
 
-      // Search for the nested trigger *within* the first popup
       const nestedTrigger1 = within(popup1).getByTestId('nested-trigger-1');
 
-      // Hover over the nested trigger to open the second level menu
       fireEvent.mouseEnter(nestedTrigger1);
       fireEvent.mouseMove(nestedTrigger1);
       clock.tick(OPEN_DELAY);
@@ -495,64 +545,210 @@ describe('<NavigationMenu.Root />', () => {
       expect(nestedPopup1).not.to.equal(null);
       expect(nestedTrigger1).to.have.attribute('aria-expanded', 'true');
 
-      // Hover over the nested popup
       fireEvent.mouseEnter(nestedPopup1);
       fireEvent.mouseMove(nestedPopup1);
       await flushMicrotasks();
 
-      // Both menus should remain open
       expect(screen.queryByTestId('popup-1')).not.to.equal(null);
       expect(screen.queryByTestId('nested-popup-1')).not.to.equal(null);
       expect(trigger1).to.have.attribute('aria-expanded', 'true');
       expect(nestedTrigger1).to.have.attribute('aria-expanded', 'true');
     });
 
-    describe('tabbing', () => {
-      it('moves focus through a nested menu correctly', async () => {
-        const { user } = await render(
-          <div>
-            <button data-testid="first" />
-            <TestNestedNavigationMenu />
-            <button data-testid="last" />
-          </div>,
-        );
+    it('handles inline nested menu without positioner/popup correctly', async () => {
+      await render(<TestInlineNestedNavigationMenu />);
+      const trigger1 = screen.getByTestId('trigger-1');
 
-        const trigger1 = screen.getByTestId('trigger-1');
+      fireEvent.mouseEnter(trigger1);
+      fireEvent.mouseMove(trigger1);
+      clock.tick(OPEN_DELAY);
+      await flushMicrotasks();
+
+      const popup1 = screen.getByTestId('popup-1');
+      expect(popup1).not.to.equal(null);
+      expect(trigger1).to.have.attribute('aria-expanded', 'true');
 
-        await act(async () => trigger1.focus());
+      const nestedTrigger1 = within(popup1).getByTestId('nested-trigger-1');
 
-        fireEvent.click(trigger1);
+      fireEvent.mouseEnter(nestedTrigger1);
+      fireEvent.mouseMove(nestedTrigger1);
+      clock.tick(OPEN_DELAY);
+      await flushMicrotasks();
+
+      const nestedPopup1 = screen.getByTestId('nested-popup-1');
+      expect(nestedPopup1).not.to.equal(null);
+      expect(nestedTrigger1).to.have.attribute('aria-expanded', 'true');
+
+      fireEvent.mouseEnter(nestedPopup1);
+      fireEvent.mouseMove(nestedPopup1);
+      await flushMicrotasks();
+
+      expect(screen.queryByTestId('popup-1')).not.to.equal(null);
+      expect(screen.queryByTestId('nested-popup-1')).not.to.equal(null);
+      expect(trigger1).to.have.attribute('aria-expanded', 'true');
+      expect(nestedTrigger1).to.have.attribute('aria-expanded', 'true');
+
+      const nestedTrigger2 = within(popup1).getByTestId('nested-trigger-2');
+      fireEvent.mouseEnter(nestedTrigger2);
+      fireEvent.mouseMove(nestedTrigger2);
+      clock.tick(OPEN_DELAY);
+      await flushMicrotasks();
+
+      const nestedPopup2 = screen.getByTestId('nested-popup-2');
+      expect(nestedPopup2).not.to.equal(null);
+      expect(nestedTrigger2).to.have.attribute('aria-expanded', 'true');
+      expect(nestedTrigger1).to.have.attribute('aria-expanded', 'false');
+    });
+
+    describe('inline nested viewport', () => {
+      it('renders viewport content correctly for inline nested menu', async () => {
+        await render(<TestInlineNestedNavigationMenu />);
+        const trigger1 = screen.getByTestId('trigger-1');
+
+        fireEvent.mouseEnter(trigger1);
+        fireEvent.mouseMove(trigger1);
+        clock.tick(OPEN_DELAY);
         await flushMicrotasks();
 
         const popup1 = screen.getByTestId('popup-1');
         expect(popup1).not.to.equal(null);
-        expect(trigger1).toHaveFocus();
 
-        await user.tab();
-        expect(screen.getByText('Link 1')).toHaveFocus();
+        const nestedTrigger1 = within(popup1).getByTestId('nested-trigger-1');
+        expect(nestedTrigger1).to.have.attribute('aria-expanded', 'true');
+
+        const nestedPopup1 = screen.getByTestId('nested-popup-1');
+        expect(nestedPopup1).not.to.equal(null);
+        expect(screen.getByText('Nested Link 1')).not.to.equal(null);
+      });
+
+      it('switches content in viewport when hovering different nested triggers', async () => {
+        await render(<TestInlineNestedNavigationMenu />);
+        const trigger1 = screen.getByTestId('trigger-1');
+
+        fireEvent.mouseEnter(trigger1);
+        fireEvent.mouseMove(trigger1);
+        clock.tick(OPEN_DELAY);
+        await flushMicrotasks();
 
-        await user.tab();
+        const popup1 = screen.getByTestId('popup-1');
         const nestedTrigger1 = within(popup1).getByTestId('nested-trigger-1');
-        expect(nestedTrigger1).toHaveFocus();
+        const nestedTrigger2 = within(popup1).getByTestId('nested-trigger-2');
+
+        expect(nestedTrigger1).to.have.attribute('aria-expanded', 'true');
+        expect(screen.getByTestId('nested-popup-1')).not.to.equal(null);
+
+        fireEvent.mouseEnter(nestedTrigger2);
+        fireEvent.mouseMove(nestedTrigger2);
+        clock.tick(OPEN_DELAY);
+        await flushMicrotasks();
+
+        expect(nestedTrigger2).to.have.attribute('aria-expanded', 'true');
+        expect(nestedTrigger1).to.have.attribute('aria-expanded', 'false');
+        expect(screen.getByTestId('nested-popup-2')).not.to.equal(null);
+        expect(screen.queryByTestId('nested-popup-1')).to.equal(null);
 
-        fireEvent.click(nestedTrigger1);
+        fireEvent.mouseEnter(nestedTrigger1);
+        fireEvent.mouseMove(nestedTrigger1);
+        clock.tick(OPEN_DELAY);
         await flushMicrotasks();
 
+        expect(nestedTrigger1).to.have.attribute('aria-expanded', 'true');
+        expect(nestedTrigger2).to.have.attribute('aria-expanded', 'false');
+        expect(screen.getByTestId('nested-popup-1')).not.to.equal(null);
+        expect(screen.queryByTestId('nested-popup-2')).to.equal(null);
+      });
+
+      it('closes inline nested viewport when parent menu closes', async () => {
+        await render(<TestInlineNestedNavigationMenu />);
+        const trigger1 = screen.getByTestId('trigger-1');
+
+        fireEvent.mouseEnter(trigger1);
+        fireEvent.mouseMove(trigger1);
+        clock.tick(OPEN_DELAY);
+        await flushMicrotasks();
+
+        const popup1 = screen.getByTestId('popup-1');
+        expect(popup1).not.to.equal(null);
+
+        const nestedTrigger1 = within(popup1).getByTestId('nested-trigger-1');
+        expect(nestedTrigger1).to.have.attribute('aria-expanded', 'true');
         expect(screen.getByTestId('nested-popup-1')).not.to.equal(null);
 
-        expect(nestedTrigger1).toHaveFocus();
+        fireEvent.mouseLeave(trigger1);
+        fireEvent.mouseLeave(popup1);
+        clock.tick(50); // closeDelay
+        await flushMicrotasks();
+
+        expect(screen.queryByTestId('popup-1')).to.equal(null);
+        expect(screen.queryByTestId('nested-popup-1')).to.equal(null);
+        expect(trigger1).to.have.attribute('aria-expanded', 'false');
+      });
+
+      it('maintains inline viewport state when hovering between triggers and content', async () => {
+        await render(<TestInlineNestedNavigationMenu />);
+        const trigger1 = screen.getByTestId('trigger-1');
+
+        fireEvent.mouseEnter(trigger1);
+        fireEvent.mouseMove(trigger1);
+        clock.tick(OPEN_DELAY);
+        await flushMicrotasks();
+
+        const popup1 = screen.getByTestId('popup-1');
+
+        const nestedTrigger2 = within(popup1).getByTestId('nested-trigger-2');
+        fireEvent.mouseEnter(nestedTrigger2);
+        fireEvent.mouseMove(nestedTrigger2);
+        clock.tick(OPEN_DELAY);
+        await flushMicrotasks();
+
+        expect(nestedTrigger2).to.have.attribute('aria-expanded', 'true');
+        const nestedPopup2 = screen.getByTestId('nested-popup-2');
+        expect(nestedPopup2).not.to.equal(null);
+
+        fireEvent.mouseEnter(nestedPopup2);
+        fireEvent.mouseMove(nestedPopup2);
+        await flushMicrotasks();
+
+        expect(nestedTrigger2).to.have.attribute('aria-expanded', 'true');
+        expect(screen.getByTestId('nested-popup-2')).not.to.equal(null);
 
-        await user.tab();
-        expect(screen.getByText('Nested Link 1')).toHaveFocus();
+        fireEvent.mouseEnter(nestedTrigger2);
+        fireEvent.mouseMove(nestedTrigger2);
+        await flushMicrotasks();
 
-        await user.tab({ shift: true });
-        expect(nestedTrigger1).toHaveFocus();
+        expect(nestedTrigger2).to.have.attribute('aria-expanded', 'true');
+        expect(screen.getByTestId('nested-popup-2')).not.to.equal(null);
+      });
+
+      it('handles click interactions on inline nested menu triggers', async () => {
+        await render(<TestInlineNestedNavigationMenu />);
+        const trigger1 = screen.getByTestId('trigger-1');
 
-        await user.tab({ shift: true });
-        expect(screen.getByText('Link 1')).toHaveFocus();
+        fireEvent.click(trigger1);
+        await flushMicrotasks();
+
+        const popup1 = screen.getByTestId('popup-1');
+        expect(popup1).not.to.equal(null);
+
+        const nestedTrigger1 = within(popup1).getByTestId('nested-trigger-1');
+        expect(nestedTrigger1).to.have.attribute('aria-expanded', 'true');
+
+        const nestedTrigger2 = within(popup1).getByTestId('nested-trigger-2');
+        fireEvent.click(nestedTrigger2);
+        await flushMicrotasks();
+
+        expect(nestedTrigger2).to.have.attribute('aria-expanded', 'true');
+        expect(nestedTrigger1).to.have.attribute('aria-expanded', 'false');
+        expect(screen.getByTestId('nested-popup-2')).not.to.equal(null);
+        expect(screen.queryByTestId('nested-popup-1')).to.equal(null);
+
+        fireEvent.click(nestedTrigger2);
+        await flushMicrotasks();
 
-        await user.tab({ shift: true });
-        expect(trigger1).toHaveFocus();
+        expect(nestedTrigger2).to.have.attribute('aria-expanded', 'true');
+        expect(nestedTrigger1).to.have.attribute('aria-expanded', 'false');
+        expect(screen.queryByTestId('nested-popup-2')).not.to.equal(null);
+        expect(screen.queryByTestId('nested-popup-1')).to.equal(null);
       });
     });
   });