Skip to main content

MobileNav

Implemented

Mobile drawer — full-height slide-in from left. Logo + nav links + user section. Backdrop closes drawer. Focus trap.

Categoryorganisms
Sourcecomponents/organisms/MobileNav/MobileNav.tsx
AI HintControlled mobile drawer for small-viewport site navigation. Pair with the TopNav/Header hamburger trigger: pass the trigger's ref as triggerRef (focus returns on close) and wire aria-expanded/aria-controls via the id prop. Escape, backdrop click, focus trap and body-scroll lock are built in (shared useOverlayDialog hook).

Mobile drawer — full-height slide-in from left for small-viewport site navigation.

MobileNav

organisms

Controlled mobile drawer: portalled to document.body, slides in from the left, with a logo header, nav links, an optional children slot and a signed-in user footer. Escape, backdrop click, focus trap, focus restore and body-scroll lock come from the shared useOverlayDialog hook.

The drawer portals to document.body and slides in from the left. Close with Escape, the backdrop, the ✕ button, or any link — focus returns to the hamburger.

Usage

MobileNav is a controlled component — pair open with local state and wire onClose so Escape, the backdrop, the ✕ button and every link dismiss it. The trigger (hamburger) lives in the consumer, next to TopNav / Header.

import { MobileNav } from '@epasslive/design-system'
import { useState } from 'react'

export function SiteChrome() {
  const [open, setOpen] = useState(false)
  return (
    <>
      <button
        type="button"
        aria-expanded={open}
        aria-haspopup="dialog"
        aria-controls="site-mobile-nav"
        onClick={() => setOpen(true)}
      >
        Menu
      </button>
      <MobileNav
        id="site-mobile-nav"
        open={open}
        onClose={() => setOpen(false)}
        links={[
          { label: 'Discover', href: '/discover', active: true },
          { label: 'Venues', href: '/venues' },
          { label: 'Help', href: '/help' },
        ]}
      />
    </>
  )
}

Responsive usage

  • MobileNav is the standalone mobile drawer opened by the TopNav / Header hamburger trigger on small viewports — the trigger lives in the consumer (the component is controlled), MobileNav owns the drawer.
  • Pass the hamburger button's ref as triggerRef so focus returns to it on close.
  • Use the id prop plus aria-controls /aria-expanded on the trigger to wire it accessibly.
  • For sidebar-style app navigation (dashboard/docs), use Sidebar instead — it ships its own integrated mobile drawer below a configurable breakpoint.

Accessibility

  • Renders role="dialog" with aria-modal="true", portalled to document.body; named via ariaLabel (default "Mobile navigation").
  • Escape closes; backdrop clicks (clicks outside the panel) close; every nav link closes on click. Initial focus lands on the ✕ close button; a focus trap cycles Tab/Shift+Tab while open; focus returns to the triggerRef element on close; body scroll is locked while open.
  • The active link exposes aria-current="page"; the open/close slide transition is disabled under motion-reduce.

Props

Prop
Type
Default
Description
open*
boolean
—

Controlled open state; the drawer renders nothing when false.

onClose*
() => void
—

Fired on Escape, backdrop click, ✕ button, and every link click.

links*
MobileNavLink[]
—

Nav links — each is { label: string; href: string; active?: boolean }; mark the current route active.

user
MobileNavUser | null
—

Signed-in footer — { name: string; initials: string; href: string }; omit when anonymous.

ariaLabel
string
'Mobile navigation'

Accessible name for the nav landmark and the dialog panel.

closeLabel
string
'Close menu'

Accessible name for the ✕ close button — i18n catalog key 'mobileNav.close'; pass a localized value.

className
string
—

Additional CSS classes appended to the overlay wrapper.

id
string
—

Stable id for the drawer element; pass the same value to the trigger's aria-controls.

children
React.ReactNode
—

Rendered below the links and above the user footer — slot for a locale switcher, theme toggle, or CTA.

triggerRef
React.RefObject<HTMLElement | null>
—

Ref of the hamburger trigger; focus returns to it on close.

Props

Preview

MobileNav

Prop
Type
Required
Default
Description
open
boolean
Required
—

Controlled open state; the drawer renders nothing when false.

onClose
() => void
Required
—

Fired on Escape, backdrop click, ✕ button, and every link click.

links
MobileNavLink[]
Required
—

Nav links — each is { label: string; href: string; active?: boolean }. 2-6 links; mark the current route active.

user
MobileNavUser | null
Optional
—

Signed-in footer — { name: string; initials: string; href: string }; omit when anonymous.

ariaLabel
string
Optional
'Mobile navigation'

Accessible name for the nav landmark and the dialog panel.

closeLabel
string
Optional
'Close menu'

Accessible name for the ✕ close button — matches the i18n catalog key `mobileNav.close`; pass a localized value (e.g. "Fermer le menu") for non-English locales.

homeHref
string
Optional
'/'

Href of the drawer logo link; pass the locale-prefixed home (e.g. "/fr") so the logo routes within the current locale (F-5 / DSD-3 — the English "/" was previously hardcoded).

accountLabel
string
Optional
'Account: {name}'

Accessible name for the signed-in user link; defaults to the English template `Account: {name}` — pass the localized, already-interpolated value (e.g. "Compte : Alex") for non-English locales (F-5 / DSD-3).

className
string
Optional
—

Additional CSS classes appended to the overlay wrapper.

id
string
Optional
—

Stable id for the drawer element; pass the same value to the trigger's aria-controls.

children
React.ReactNode
Optional
—

Rendered below the links and above the user footer — slot for a locale switcher, theme toggle, or CTA.

triggerRef
React.RefObject<HTMLElement | null>
Optional
—

Ref of the hamburger trigger; focus returns to it on close.

Quick use:<MobileNav />

Accessibility

ARIA roledialog

Type

1 rule

NASA Rule 5

open and onClose required (controlled)

ARIA

1 rule

WAI-ARIA Dialog Pattern

role='dialog' aria-modal='true'; Escape closes; focus trap

Usage

Import

tsxSnippet
import { MobileNav } from "@epasslive/design-system"

Example 1

tsxSnippet
<MobileNav open={open} onClose={() => setOpen(false)} links={[{ label: "Home", href: "/" }]} />

Anti-patterns

— Do not render it always-open or uncontrolled — open/onClose are required and owned by the consumer

— Do not use for sidebar-style app navigation — the Sidebar molecule ships its own integrated mobile drawer below a breakpoint

— Do not inline a second focus trap or scroll lock — the shared useOverlayDialog hook already provides both

Design System v4.1.0 | ePassLive