

# MobileNav

Slide-out mobile navigation drawer built on React Aria Modal — focus trap, Escape, and backdrop dismissal included. Control open state with `isOpen` / `onOpenChange`, or share state through `MobileNavProvider` + `MobileNav.Toggle`.

## Examples

### Default

Controlled open state from a button.

```tsx
import { MobileNav } from '@var-ui/react';

<MobileNav isOpen={open} onOpenChange={setOpen} header="Menu">
  <SideNav.Item label="Home" href="/" />
</MobileNav>
```

```astro
---
import { MobileNav, MobileNavToggle, SideNavItem } from '@var-ui/astro';
---
<div data-var-ui-mobile-nav-provider>
  <MobileNavToggle />
  <MobileNav header="Menu">
    <SideNavItem label="Home" href="/" />
  </MobileNav>
</div>
```

```html
<div data-var-ui-mobile-nav>…</div>
```

### Provider and toggle

Wrap the hamburger and drawer in `MobileNavProvider` so `MobileNav.Toggle` and `MobileNav` share open state without prop drilling. `AppShell` already provides this at the mobile breakpoint.

```tsx
import { MobileNav, MobileNavProvider, SideNav } from '@var-ui/react';

<MobileNavProvider defaultIsOpen>
  <MobileNav.Toggle />
  <MobileNav header="Menu">
    <SideNav.Section title="Browse">
      <SideNav.Item label="Home" href="/" isSelected />
      <SideNav.Item label="Docs" href="/docs" />
    </SideNav.Section>
  </MobileNav>
</MobileNavProvider>
```

```astro
<!-- No Astro binding yet — use @var-ui/react -->
```

```html
<!-- No HTML demo yet — use @var-ui/react -->
```

## Props

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| children | ReactNode | no |  | Drawer content — typically a `SideNav.Section`/`SideNav.Item` tree. |
| className | string \| undefined | no |  |  |
| closeLabel | string \| undefined | no | 'Close navigation' | Label for the close button. |
| header | ReactNode | no |  | Rendered in the sticky header row, left of the close button. |
| isOpen | boolean \| undefined | no |  | Controlled open state — falls back to a `MobileNavProvider` ancestor when omitted. |
| label | string \| undefined | no | 'Navigation' | Accessible name for the dialog. |
| onOpenChange | ((open: boolean) => void) \| undefined | no |  | Falls back to a `MobileNavProvider` ancestor when omitted. |
| portalContainer | Element \| undefined | no |  | Element the modal portals into instead of `document.body`. Needed when a subtree renders under a different theme than the page ambient (the theme's CSS custom properties only cascade to descendants of the themed element). |
| side | "auto" \| "end" \| "start" \| undefined | no | 'auto' | Edge the panel slides in from. `'auto'` resolves to `'start'`. |
| width | number \| undefined | no | 320 | Panel width in px; the recipe caps it at 85vw. |

## Accessibility

The panel is a dialog (`label` defaults to `"Navigation"`) with a labeled close control. `MobileNav.Toggle` sets `aria-expanded` from provider or explicit `isOpen`, and defaults to `aria-label="Open navigation"`. Prefer the provider pattern when the toggle lives in top chrome and the drawer is a sibling (as in `AppShell`).
