MobileNav
ImplementedMobile drawer — full-height slide-in from left. Logo + nav links + user section. Backdrop closes drawer. Focus trap.
components/organisms/MobileNav/MobileNav.tsxMobile 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/Headerhamburger 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
triggerRefso focus returns to it on close. - Use the
idprop plusaria-controls/aria-expandedon the trigger to wire it accessibly. - For sidebar-style app navigation (dashboard/docs), use
Sidebarinstead — it ships its own integrated mobile drawer below a configurable breakpoint.
Accessibility
- Renders
role="dialog"witharia-modal="true", portalled todocument.body; named viaariaLabel(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
triggerRefelement on close; body scroll is locked while open. - The active link exposes
aria-current="page"; the open/close slide transition is disabled undermotion-reduce.
Props
open*booleanControlled open state; the drawer renders nothing when false.
onClose*() => voidFired 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.
userMobileNavUser | nullSigned-in footer — { name: string; initials: string; href: string }; omit when anonymous.
ariaLabelstring'Mobile navigation'Accessible name for the nav landmark and the dialog panel.
closeLabelstring'Close menu'Accessible name for the ✕ close button — i18n catalog key 'mobileNav.close'; pass a localized value.
classNamestringAdditional CSS classes appended to the overlay wrapper.
idstringStable id for the drawer element; pass the same value to the trigger's aria-controls.
childrenReact.ReactNodeRendered below the links and above the user footer — slot for a locale switcher, theme toggle, or CTA.
triggerRefReact.RefObject<HTMLElement | null>Ref of the hamburger trigger; focus returns to it on close.
Props
openboolean—Controlled open state; the drawer renders nothing when false.
onClose() => void—Fired on Escape, backdrop click, ✕ button, and every link click.
linksMobileNavLink[]—Nav links — each is { label: string; href: string; active?: boolean }. 2-6 links; mark the current route active.
userMobileNavUser | null—Signed-in footer — { name: string; initials: string; href: string }; omit when anonymous.
ariaLabelstring'Mobile navigation'Accessible name for the nav landmark and the dialog panel.
closeLabelstring'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.
homeHrefstring'/'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).
accountLabelstring'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).
classNamestring—Additional CSS classes appended to the overlay wrapper.
idstring—Stable id for the drawer element; pass the same value to the trigger's aria-controls.
childrenReact.ReactNode—Rendered below the links and above the user footer — slot for a locale switcher, theme toggle, or CTA.
triggerRefReact.RefObject<HTMLElement | null>—Ref of the hamburger trigger; focus returns to it on close.
<MobileNav />Accessibility
Type
1 ruleARIA
1 ruleUsage
Import
Example 1
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