MobileNavigationRoot
Full-screen fixed overlay that hosts the mobile navigation; hidden from lg up.
Carries modal-dialog semantics: focus is trapped inside the overlay while it is
mounted, cycles with Tab, and returns to the trigger once it unmounts. Pressing
Escape invokes onClose. The accessible name defaults to "Navigation menu" and
can be overridden via aria-label.
Renders an m.div, so consumers can drive enter/exit motion (e.g. a slide) by
passing initial/animate/exit/transition and wrapping in AnimatePresence.
With no motion props it renders statically. Horizontal overflow is clipped
(overflow-x-clip) so a nested drill-in view sliding off-screen never spawns a
horizontal scrollbar; vertical content still scrolls.
Usage
import { mobileNavigationAnatomy, MobileNavigationAppHeader, MobileNavigationAppHeaderSurface, MobileNavigationBrandLockup, MobileNavigationBrandTrigger, MobileNavigationCenteredPanel, MobileNavigationCenteredText, MobileNavigationContent, MobileNavigationDrawerContent, MobileNavigationDrawerProfile, MobileNavigationDrawerProfileProps, MobileNavigationDrawerSection, MobileNavigationDrawerStack, MobileNavigationFrame, MobileNavigationFullWidthAction, MobileNavigationHeader, MobileNavigationHeaderAction, MobileNavigationHeaderSearchSlot, MobileNavigationItem, MobileNavigationItemProps, MobileNavigationList, MobileNavigationProfileAction, MobileNavigationProfileActionProps, MobileNavigationProjectItem, MobileNavigationProjectItemProps, MobileNavigationRoot, MobileNavigationSeparator, MobileNavigationSlottedText } from "@instruments/materia-ui/compositions/mobile-navigation";Best practices
Wire
onCloseto your open-state setter - the overlay traps focus and fires it on Escape, but you own mounting/unmounting.It's
role="dialog"+aria-modaland hidden fromlgup, so pair it with a desktop nav rather than showing both at once.Override the default "Navigation menu" accessible name via
aria-labelwhen the drawer's purpose is more specific.
Props
MobileNavigationAppHeader
| Prop | Type | Default | Description |
|---|---|---|---|
| hideFrom | "lg" | "md" | "lg" | Breakpoint from which the bar is hidden. |
| surface | MobileNavigationAppHeaderSurface | "muted" | The page surface the bar matches, so scrolling content disappears under
it without a seam: |
MobileNavigationBrandLockup
| Prop | Type | Default | Description |
|---|---|---|---|
| align | LayoutResponsiveValue<FlexAlign> | "stretch" | Cross-axis alignment. Accepts a per-breakpoint responsive object. |
| as | "div" | "article" | "aside" | "figcaption" | "figure" | "footer" | "header" | "li" | "main" | "nav" | "ol" | "section" | "span" | "ul" | - | Render as a different host element from the closed layout vocabulary
({@link LayoutElement}). The set is deliberately closed: |
| direction | LayoutResponsiveValue<FlexDirection> | "row" | Main-axis direction. Accepts a per-breakpoint responsive object. |
| gap | LayoutResponsiveValue<"base" | "none" | "2xs" | "xs" | "sm" | "lg" | "xl" | "2xl" | "3xl" | "3xs"> | - | Gap between items, from the spacing scale. Accepts a per-breakpoint responsive object. |
| guidelines | boolean | false | Show dashed outline guidelines during development. |
| inline | boolean | false | Render as inline-flex instead of flex. |
| justify | LayoutResponsiveValue<FlexJustify> | "flex-start" | Main-axis distribution. Accepts a per-breakpoint responsive object. |
| responsiveMode | ResponsiveMode | "screen" | Whether responsive values resolve against the viewport ("screen") or nearest container ("container"). |
| wrap | LayoutResponsiveValue<FlexWrap> | "nowrap" | Wrap behaviour. Accepts a per-breakpoint responsive object. |
| ref | LayoutRef | undefined | - | Ref to the rendered host, whichever element |
MobileNavigationBrandTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
| render | useRender.RenderProp<Record<string, unknown>> | undefined | - | Replace the rendered |
| tone | "neutral" | "brand" | "danger" | null | undefined | - | |
| radius | "rounded" | "square" | "full" | null | undefined | - | |
| size | ButtonSizeProp | "base" | Size token; |
| dot | string | undefined | - | CSS colour for a status dot; empty/undefined renders no dot. |
| loading | boolean | undefined | false | Show a spinner and block presses. The button stays focusable (it is
|
| square | boolean | null | undefined | - | |
| emphasis | "link" | "outline" | "solid" | "ghost" | "soft" | "transparent" | null | undefined | - | |
| asInput | boolean | null | undefined | - | |
| align | "center" | "end" | "start" | null | undefined | - | |
| pressed | boolean | null | undefined | - | |
| testId | string | - | |
| suffixIcon | IconComponent | undefined | - | Trailing icon (Lucide or SVG component) rendered at the end. |
| expandable | boolean | undefined | false | Icon-only at rest; expands on hover/focus-visible to reveal the label
to the right of the icon. Collapsed geometry equals the matching icon-*
size, expanded equals the text button of that size - so pass a text
size ( |
| focused | boolean | undefined | - | Force the focus ring on - for documentation/testing only. |
| hovered | boolean | undefined | - | Force the hover state on - for documentation/testing only. |
| loadingLabel | string | undefined | - | Label shown in place of |
| iconClassName | string | undefined | - | Extra classes for the leading icon. |
| suffixIconClassName | string | undefined | - | Extra classes for the trailing icon. |
| dotPlacement | "end" | "start" | undefined | "start" | Dot position relative to content. |
MobileNavigationCenteredPanel
| Prop | Type | Default | Description |
|---|---|---|---|
| ref | LayoutRef | undefined | - | Ref to the rendered host, whichever element |
| gap | LayoutResponsiveValue<"base" | "none" | "2xs" | "xs" | "sm" | "lg" | "xl" | "2xl" | "3xl" | "3xs"> | "base" | Gap between items, from the spacing scale. Accepts a per-breakpoint responsive object. |
| as | "div" | "article" | "aside" | "figcaption" | "figure" | "footer" | "header" | "li" | "main" | "nav" | "ol" | "section" | "span" | "ul" | - | Render as a different host element from the closed layout vocabulary
({@link LayoutElement}). The set is deliberately closed: |
| align | LayoutResponsiveValue<FlexAlign> | "stretch" | Cross-axis alignment. Accepts a per-breakpoint responsive object. |
| responsiveMode | ResponsiveMode | "screen" | Whether responsive values resolve against the viewport ("screen") or nearest container ("container"). |
| justify | LayoutResponsiveValue<FlexJustify> | "flex-start" | Main-axis distribution. Accepts a per-breakpoint responsive object. |
| guidelines | boolean | false | Show dashed outline guidelines during development. |
MobileNavigationCenteredText
| Prop | Type | Default | Description |
|---|---|---|---|
| as | "dd" | "dt" | "figcaption" | "legend" | "li" | "p" | "span" | - | Render as a different host element from the closed text vocabulary
({@link TextElement}). The set is deliberately closed and text-only:
|
| size | ResponsiveValue<"caption" | "paragraph-sm" | "paragraph" | "paragraph-lg" | "subheading" | "subheading-lg" | "heading-sm"> | "paragraph-sm" | Type role - a single role or a responsive object like
|
| variant | "default" | "muted" | "accent" | - | Text colour variant |
| weight | "normal" | "medium" | "semibold" | - | Font weight |
MobileNavigationDrawerContent
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | Extra classes for the panel surface. |
| showHandle | boolean | true | Show the drag handle (bottom drawers). |
MobileNavigationDrawerProfile
| Prop | Type | Default | Description |
|---|---|---|---|
| displayName*(required) | string | - | |
| email*(required) | string | undefined | - | Secondary line under the name; hidden when absent. |
| userAvatarName*(required) | string | undefined | - | Name used to derive the avatar initials fallback. |
| userAvatarUrl*(required) | string | null | undefined | - | Avatar image URL; falls back to initials when absent. |
MobileNavigationDrawerSection
| Prop | Type | Default | Description |
|---|---|---|---|
| ref | LayoutRef | undefined | - | Ref to the rendered host, whichever element |
| gap | LayoutResponsiveValue<"base" | "none" | "2xs" | "xs" | "sm" | "lg" | "xl" | "2xl" | "3xl" | "3xs"> | "base" | Gap between items, from the spacing scale. Accepts a per-breakpoint responsive object. |
| as | "div" | "article" | "aside" | "figcaption" | "figure" | "footer" | "header" | "li" | "main" | "nav" | "ol" | "section" | "span" | "ul" | - | Render as a different host element from the closed layout vocabulary
({@link LayoutElement}). The set is deliberately closed: |
| align | LayoutResponsiveValue<FlexAlign> | "stretch" | Cross-axis alignment. Accepts a per-breakpoint responsive object. |
| responsiveMode | ResponsiveMode | "screen" | Whether responsive values resolve against the viewport ("screen") or nearest container ("container"). |
| justify | LayoutResponsiveValue<FlexJustify> | "flex-start" | Main-axis distribution. Accepts a per-breakpoint responsive object. |
| guidelines | boolean | false | Show dashed outline guidelines during development. |
MobileNavigationDrawerStack
| Prop | Type | Default | Description |
|---|---|---|---|
| ref | LayoutRef | undefined | - | Ref to the rendered host, whichever element |
| gap | LayoutResponsiveValue<"base" | "none" | "2xs" | "xs" | "sm" | "lg" | "xl" | "2xl" | "3xl" | "3xs"> | "base" | Gap between items, from the spacing scale. Accepts a per-breakpoint responsive object. |
| as | "div" | "article" | "aside" | "figcaption" | "figure" | "footer" | "header" | "li" | "main" | "nav" | "ol" | "section" | "span" | "ul" | - | Render as a different host element from the closed layout vocabulary
({@link LayoutElement}). The set is deliberately closed: |
| align | LayoutResponsiveValue<FlexAlign> | "stretch" | Cross-axis alignment. Accepts a per-breakpoint responsive object. |
| responsiveMode | ResponsiveMode | "screen" | Whether responsive values resolve against the viewport ("screen") or nearest container ("container"). |
| justify | LayoutResponsiveValue<FlexJustify> | "flex-start" | Main-axis distribution. Accepts a per-breakpoint responsive object. |
| guidelines | boolean | false | Show dashed outline guidelines during development. |
MobileNavigationFrame
| Prop | Type | Default | Description |
|---|---|---|---|
| ref | LayoutRef | undefined | - | Ref to the rendered host, whichever element |
| gap | LayoutResponsiveValue<"base" | "none" | "2xs" | "xs" | "sm" | "lg" | "xl" | "2xl" | "3xl" | "3xs"> | "base" | Gap between items, from the spacing scale. Accepts a per-breakpoint responsive object. |
| as | "div" | "article" | "aside" | "figcaption" | "figure" | "footer" | "header" | "li" | "main" | "nav" | "ol" | "section" | "span" | "ul" | - | Render as a different host element from the closed layout vocabulary
({@link LayoutElement}). The set is deliberately closed: |
| align | LayoutResponsiveValue<FlexAlign> | "stretch" | Cross-axis alignment. Accepts a per-breakpoint responsive object. |
| responsiveMode | ResponsiveMode | "screen" | Whether responsive values resolve against the viewport ("screen") or nearest container ("container"). |
| justify | LayoutResponsiveValue<FlexJustify> | "flex-start" | Main-axis distribution. Accepts a per-breakpoint responsive object. |
| guidelines | boolean | false | Show dashed outline guidelines during development. |
MobileNavigationFullWidthAction
| Prop | Type | Default | Description |
|---|---|---|---|
| tone | "neutral" | "brand" | "danger" | null | undefined | - | |
| radius | "rounded" | "square" | "full" | null | undefined | - | |
| square | boolean | null | undefined | - | |
| emphasis | "link" | "outline" | "solid" | "ghost" | "soft" | "transparent" | null | undefined | - | |
| asInput | boolean | null | undefined | - | |
| align | "center" | "end" | "start" | null | undefined | - | |
| pressed | boolean | null | undefined | - | |
| size | ButtonSizeProp | "base" | Size token; |
| render | useRender.RenderProp<Record<string, unknown>> | undefined | - | Replace the rendered |
| loading | boolean | undefined | false | Show a spinner and block presses. The button stays focusable (it is
|
| loadingLabel | string | undefined | - | Label shown in place of |
| icon | IconComponent | undefined | - | Leading icon (Lucide or SVG component); with no children it becomes an icon-only button. |
| iconClassName | string | undefined | - | Extra classes for the leading icon. |
| suffixIcon | IconComponent | undefined | - | Trailing icon (Lucide or SVG component) rendered at the end. |
| suffixIconClassName | string | undefined | - | Extra classes for the trailing icon. |
| dot | string | undefined | - | CSS colour for a status dot; empty/undefined renders no dot. |
| dotPlacement | "end" | "start" | undefined | "start" | Dot position relative to content. |
| expandable | boolean | undefined | false | Icon-only at rest; expands on hover/focus-visible to reveal the label
to the right of the icon. Collapsed geometry equals the matching icon-*
size, expanded equals the text button of that size - so pass a text
size ( |
| focused | boolean | undefined | - | Force the focus ring on - for documentation/testing only. |
| hovered | boolean | undefined | - | Force the hover state on - for documentation/testing only. |
| testId | string | - |
MobileNavigationHeader
| Prop | Type | Default | Description |
|---|---|---|---|
| ref | LayoutRef | undefined | - | Ref to the rendered host, whichever element |
| inline | boolean | false | Render as inline-flex instead of flex. |
| direction | LayoutResponsiveValue<FlexDirection> | "row" | Main-axis direction. Accepts a per-breakpoint responsive object. |
| gap | LayoutResponsiveValue<"base" | "none" | "2xs" | "xs" | "sm" | "lg" | "xl" | "2xl" | "3xl" | "3xs"> | - | Gap between items, from the spacing scale. Accepts a per-breakpoint responsive object. |
| as | "div" | "article" | "aside" | "figcaption" | "figure" | "footer" | "header" | "li" | "main" | "nav" | "ol" | "section" | "span" | "ul" | - | Render as a different host element from the closed layout vocabulary
({@link LayoutElement}). The set is deliberately closed: |
| align | LayoutResponsiveValue<FlexAlign> | "stretch" | Cross-axis alignment. Accepts a per-breakpoint responsive object. |
| responsiveMode | ResponsiveMode | "screen" | Whether responsive values resolve against the viewport ("screen") or nearest container ("container"). |
| justify | LayoutResponsiveValue<FlexJustify> | "flex-start" | Main-axis distribution. Accepts a per-breakpoint responsive object. |
| wrap | LayoutResponsiveValue<FlexWrap> | "nowrap" | Wrap behaviour. Accepts a per-breakpoint responsive object. |
| guidelines | boolean | false | Show dashed outline guidelines during development. |
| onClose*(required) | () => void | - |
MobileNavigationHeaderAction
| Prop | Type | Default | Description |
|---|---|---|---|
| tone | "neutral" | "brand" | "danger" | null | undefined | - | |
| radius | "rounded" | "square" | "full" | null | undefined | - | |
| square | boolean | null | undefined | - | |
| emphasis | "link" | "outline" | "solid" | "ghost" | "soft" | "transparent" | null | undefined | - | |
| asInput | boolean | null | undefined | - | |
| align | "center" | "end" | "start" | null | undefined | - | |
| pressed | boolean | null | undefined | - | |
| size | ButtonSizeProp | "base" | Size token; |
| render | useRender.RenderProp<Record<string, unknown>> | undefined | - | Replace the rendered |
| loading | boolean | undefined | false | Show a spinner and block presses. The button stays focusable (it is
|
| loadingLabel | string | undefined | - | Label shown in place of |
| icon | IconComponent | undefined | - | Leading icon (Lucide or SVG component); with no children it becomes an icon-only button. |
| iconClassName | string | undefined | - | Extra classes for the leading icon. |
| suffixIcon | IconComponent | undefined | - | Trailing icon (Lucide or SVG component) rendered at the end. |
| suffixIconClassName | string | undefined | - | Extra classes for the trailing icon. |
| dot | string | undefined | - | CSS colour for a status dot; empty/undefined renders no dot. |
| dotPlacement | "end" | "start" | undefined | "start" | Dot position relative to content. |
| expandable | boolean | undefined | false | Icon-only at rest; expands on hover/focus-visible to reveal the label
to the right of the icon. Collapsed geometry equals the matching icon-*
size, expanded equals the text button of that size - so pass a text
size ( |
| focused | boolean | undefined | - | Force the focus ring on - for documentation/testing only. |
| hovered | boolean | undefined | - | Force the hover state on - for documentation/testing only. |
| testId | string | - |
MobileNavigationItem
| Prop | Type | Default | Description |
|---|---|---|---|
| active | boolean | - | Mark the row as the current location (adds a filled background). |
| destructive | boolean | - | Render the label in the destructive colour. |
| dot | boolean | "neutral" | "brand" | "danger" | "warning" | "success" | "info" | - | Show a status dot in place of the leading icon. |
| icon | import("../../tokens/icon-component").IconComponent | - | Leading icon; omit to reserve leading space with no glyph. |
| label*(required) | React.ReactNode | - | Row text (required). |
| suffixIcon | import("../../tokens/icon-component").IconComponent | - | Trailing icon shown after the label. |
| render | useRender.RenderProp<Record<string, unknown>> | undefined | - | Replace the rendered |
| tone | "neutral" | "brand" | "danger" | null | undefined | - | |
| radius | "rounded" | "square" | "full" | null | undefined | - | |
| size | ButtonSizeProp | "base" | Size token; |
| loading | boolean | undefined | false | Show a spinner and block presses. The button stays focusable (it is
|
| square | boolean | null | undefined | - | |
| emphasis | "link" | "outline" | "solid" | "ghost" | "soft" | "transparent" | null | undefined | - | |
| asInput | boolean | null | undefined | - | |
| align | "center" | "end" | "start" | null | undefined | - | |
| pressed | boolean | null | undefined | - | |
| testId | string | - | |
| expandable | boolean | undefined | false | Icon-only at rest; expands on hover/focus-visible to reveal the label
to the right of the icon. Collapsed geometry equals the matching icon-*
size, expanded equals the text button of that size - so pass a text
size ( |
| focused | boolean | undefined | - | Force the focus ring on - for documentation/testing only. |
| hovered | boolean | undefined | - | Force the hover state on - for documentation/testing only. |
| loadingLabel | string | undefined | - | Label shown in place of |
| iconClassName | string | undefined | - | Extra classes for the leading icon. |
| suffixIconClassName | string | undefined | - | Extra classes for the trailing icon. |
| dotPlacement | "end" | "start" | undefined | "start" | Dot position relative to content. |
MobileNavigationList
| Prop | Type | Default | Description |
|---|---|---|---|
| ref | LayoutRef | undefined | - | Ref to the rendered host, whichever element |
| gap | LayoutResponsiveValue<"base" | "none" | "2xs" | "xs" | "sm" | "lg" | "xl" | "2xl" | "3xl" | "3xs"> | "base" | Gap between items, from the spacing scale. Accepts a per-breakpoint responsive object. |
| as | "div" | "article" | "aside" | "figcaption" | "figure" | "footer" | "header" | "li" | "main" | "nav" | "ol" | "section" | "span" | "ul" | - | Render as a different host element from the closed layout vocabulary
({@link LayoutElement}). The set is deliberately closed: |
| align | LayoutResponsiveValue<FlexAlign> | "stretch" | Cross-axis alignment. Accepts a per-breakpoint responsive object. |
| responsiveMode | ResponsiveMode | "screen" | Whether responsive values resolve against the viewport ("screen") or nearest container ("container"). |
| justify | LayoutResponsiveValue<FlexJustify> | "flex-start" | Main-axis distribution. Accepts a per-breakpoint responsive object. |
| guidelines | boolean | false | Show dashed outline guidelines during development. |
MobileNavigationProfileAction
| Prop | Type | Default | Description |
|---|---|---|---|
| avatarName*(required) | string | undefined | - | Name used to derive the avatar initials fallback. |
| displayName*(required) | string | - | User's display name shown as the button label. |
| loading | boolean | false | Show a loading label and disable the button. |
| userAvatarUrl*(required) | string | null | undefined | - | Avatar image URL; falls back to initials when absent. |
| render | useRender.RenderProp<Record<string, unknown>> | undefined | - | Replace the rendered |
| tone | "neutral" | "brand" | "danger" | null | undefined | - | |
| radius | "rounded" | "square" | "full" | null | undefined | - | |
| size | ButtonSizeProp | "base" | Size token; |
| dot | string | undefined | - | CSS colour for a status dot; empty/undefined renders no dot. |
| square | boolean | null | undefined | - | |
| icon | IconComponent | undefined | - | Leading icon (Lucide or SVG component); with no children it becomes an icon-only button. |
| emphasis | "link" | "outline" | "solid" | "ghost" | "soft" | "transparent" | null | undefined | - | |
| asInput | boolean | null | undefined | - | |
| align | "center" | "end" | "start" | null | undefined | - | |
| pressed | boolean | null | undefined | - | |
| testId | string | - | |
| suffixIcon | IconComponent | undefined | - | Trailing icon (Lucide or SVG component) rendered at the end. |
| expandable | boolean | undefined | false | Icon-only at rest; expands on hover/focus-visible to reveal the label
to the right of the icon. Collapsed geometry equals the matching icon-*
size, expanded equals the text button of that size - so pass a text
size ( |
| focused | boolean | undefined | - | Force the focus ring on - for documentation/testing only. |
| hovered | boolean | undefined | - | Force the hover state on - for documentation/testing only. |
| loadingLabel | string | undefined | - | Label shown in place of |
| iconClassName | string | undefined | - | Extra classes for the leading icon. |
| suffixIconClassName | string | undefined | - | Extra classes for the trailing icon. |
| dotPlacement | "end" | "start" | undefined | "start" | Dot position relative to content. |
MobileNavigationProjectItem
| Prop | Type | Default | Description |
|---|---|---|---|
| active | boolean | - | Mark the project as current (dot indicator + filled background). |
| archived | boolean | - | Dim the label to indicate an archived project. |
| label*(required) | React.ReactNode | - | |
| onAction*(required) | () => void | - | Invoked when the trailing action button is pressed. |
| actionIcon*(required) | import("../../tokens/icon-component").IconComponent | - | |
| actionLabel*(required) | string | - | Accessible label for the trailing action button. |
| render*(required) | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | - | The link element the row renders as, e.g. |
MobileNavigationRoot
| Prop | Type | Default | Description |
|---|---|---|---|
| children | React.ReactNode | - | |
| onClose | () => void | - |
MobileNavigationSeparator
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "strong" | "default" | "muted" | "accent" | "default" | Visual weight of the separator line. |
Also exports
- const
mobileNavigationAnatomy - type
MobileNavigationAppHeaderSurface - component
MobileNavigationContent - interface
MobileNavigationDrawerProfileProps - component
MobileNavigationHeaderSearchSlot - interface
MobileNavigationItemProps - interface
MobileNavigationProfileActionProps - interface
MobileNavigationProjectItemProps - component
MobileNavigationSlottedText
Anatomy
MobileNavigationRoot
mobile-navigation-rootRoot controlling open state for the mobile drawer
MobileNavigationFrame
mobile-navigation-frameFull-height frame that stacks header and content
MobileNavigationHeader
mobile-navigation-headerBranded top bar with a close action
MobileNavigationContent
mobile-navigation-contentScrollable region holding the navigation list
MobileNavigationItem
mobile-navigation-itemSingle navigation entry with icon and active state