# Menu

Navigation with submenus, search, and a drawer for small screens.

URL: https://webrad.atentor.com/docs/menu
Updated: 2026-11-07

WebMenu · Navigation · Visual · 9 events

## Quick start

Add **Menu1** in the Designer. Use the example in a page method.

```ts
Menu1.items = [
  { id: 'home', label: 'Home', icon: 'house' },
  { id: 'records', label: 'Records', icon: 'folder', children: [
    { id: 'customers', label: 'Customers', icon: 'users' },
  ] },
];
Menu1.selectedItemId = 'home';
Menu1.expandItem('records');
Menu1.updateItem('customers', { badge: '3' });
```

Use stable item IDs. page takes the project page ID; href takes a link. In OnItemClick, call preventDefault() before any await.

- Open the configurator from the Menu in the Designer. Arrange items, submenus, icons, badges, brand, and user.
- Choose vertical or horizontal orientation. The menu floats; anchors and margins define its position.
- Below mobileBreakpoint, the menu uses a drawer. Configure drawerWidth, drawerSide, and closeOnSelect.
- page navigates to a page ID; href opens a link. Items with children and no destination expand their submenu.
- OnItemClick receives item, itemId, and path. OnItemToggle receives itemId and expanded; use it for submenus without an action.
- searchable enables search. searchMode = 'filter' filters items; 'event' retains items and emits OnSearch with detail.text.
- collapsed controls the compact version; open(), close(), and toggle() control opening. themeToggle displays the theme control.

## More examples

Handle an action by item ID

```ts
import type { WebMenu, WebMenuItemEvent } from '@webrad/runtime';
// Link this method to OnItemClick in Interaction.
class Example {
  public async Menu1ItemClick(
    sender: WebMenu,
    event: CustomEvent<WebMenuItemEvent>,
  ): Promise<void> {
    if (event.detail.itemId !== 'sign-out') return;
    event.preventDefault(); // Cancel navigation before await.
    const result = await RestGO.logout();
    if (result.error) { Message(result.error.message); return; }
    Navigate('PageLogin');
  }
}
```

Search and responsive configuration

```ts
Menu1.configuration = {
  ...Menu1.configuration,
  searchable: true,
  searchMode: 'event',
  mobileBreakpoint: 768,
  drawerWidth: 288,
  drawerSide: 'left',
  closeOnSelect: true,
};
// OnSearch receives event.detail.text; update data in your logic.
Menu1.brand = { ...Menu1.brand, caption: 'My application' };
Menu1.user = { ...Menu1.user, visible: true, name: 'Ana', status: 'online' };
Menu1.themeToggle = true;
```

## Inspector properties

Component options and defaults. Fields without runtime access are configured in the Inspector.

| Property / source | Type and options | Default | Runtime access |
| --- | --- | --- | --- |
| `name` — Name · Identity | `string` | `—` | name |
| `x` — Left · Size & position | `number` | `0` | x |
| `y` — Top · Size & position | `number` | `0` | y |
| `width` — Width · Size & position | `number` | `288` | width |
| `height` — Height · Size & position | `number` | `640` | height |
| `itemWidth` — Responsive width · Responsive size — Fit content grows the item to its widest rendered child and preserves child intrinsic widths; minimum and maximum width remain limits. | `enum` — `fixed` `stretch` `fit-content` | `"fixed"` | Inspector / configuration |
| `itemHeight` — Responsive height · Responsive size — Fit content grows the item to its lowest rendered child and preserves child intrinsic heights; minimum and maximum height remain limits. | `enum` — `fixed` `stretch` `fit-content` | `"fixed"` | Inspector / configuration |
| `itemMinWidth` — Minimum width · Responsive size | `number` | `0` | Inspector / configuration |
| `itemMaxWidth` — Maximum width · Responsive size | `number` | `null` | Inspector / configuration |
| `itemMinHeight` — Minimum height · Responsive size | `number` | `0` | Inspector / configuration |
| `itemMaxHeight` — Maximum height · Responsive size | `number` | `null` | Inspector / configuration |
| `itemGrow` — Grow · Responsive size | `number` | `0` | Inspector / configuration |
| `itemShrink` — Shrink · Responsive size — Allows fixed and stretch flow items to yield space. Fit-content keeps its intrinsic size so its children are not compressed. | `boolean` | `true` | Inspector / configuration |
| `itemAlignSelf` — Item alignment · Responsive size — Cross-axis alignment for this item in a row or column. Auto inherits the parent alignment; responsive cross-axis stretch retains its sizing behavior. | `enum` — `auto` `start` `center` `end` `stretch` | `"auto"` | Inspector / configuration |
| `itemHorizontalAnchor` — Horizontal anchor · Anchors & offsets | `enum` — `start` `center` `end` `stretch` | `"start"` | Inspector / configuration |
| `itemVerticalAnchor` — Vertical anchor · Anchors & offsets | `enum` — `start` `center` `end` `stretch` | `"start"` | Inspector / configuration |
| `itemLeft` — Left inset · Anchors & offsets | `number` | `0` | Inspector / configuration |
| `itemRight` — Right inset · Anchors & offsets | `number` | `0` | Inspector / configuration |
| `itemTop` — Top inset · Anchors & offsets | `number` | `0` | Inspector / configuration |
| `itemBottom` — Bottom inset · Anchors & offsets | `number` | `0` | Inspector / configuration |
| `margin` — Margin · Spacing — Outer spacing on all four sides, in pixels. Row and column layouts include this spacing between components. | `number` | `0` | margin |
| `marginTop` — Margin top · Spacing — Outer spacing on this side, in pixels. Overrides the shared Margin value. | `number` | `0` — Derived from saved properties | marginTop |
| `marginRight` — Margin right · Spacing — Outer spacing on this side, in pixels. Overrides the shared Margin value. | `number` | `0` — Derived from saved properties | marginRight |
| `marginBottom` — Margin bottom · Spacing — Outer spacing on this side, in pixels. Overrides the shared Margin value. | `number` | `0` — Derived from saved properties | marginBottom |
| `marginLeft` — Margin left · Spacing — Outer spacing on this side, in pixels. Overrides the shared Margin value. | `number` | `0` — Derived from saved properties | marginLeft |
| `visible` — Visible · Behavior | `boolean` | `true` | visible |
| `collapseWhenHidden` — Collapse when hidden · Behavior — Removes the component from the layout while it is not visible. | `boolean` | `true` | collapseWhenHidden |
| `enabled` — Enabled · Behavior | `boolean` | `true` | enabled |
| `menuDefinitionId` — Menu definition · Menu — Shared configuration and TypeScript; runtime state remains independent for each placement. | `menu-definition` | `—` | Inspector / configuration |
| `editMenuDefinition` — Edit shared menu · Menu | `designer-action` — Editor: menu-definition-edit | `—` | Inspector / configuration |
| `configuration` — Menu configuration · Menu — Edit orientation, menu items, brand, user, appearance and the optional Light/Dark theme switch. Responsive placement is automatic. | `menu-config` — Editor: menu-configurator | `{"schemaVersion":1,"orientation":"vertical","accessibleLabel":"Main navigation","collapsible":true,"collapsed":false,"accordion":false,"searchable":false,"themeToggle":true,"searchMode":"filter","searchPlaceholder":"Search menu…","emptyText":"No items found","footerText":"","selectedItemId":"","mobileBreakpoint":768,"mobileIconPosition":"left","drawerWidth":288,"drawerSide":"left","closeOnSelect":true,"brand":{"visible":true,"caption":"Workspace","subtitle":"Your application","logo":"","logoSize":36,"page":"","href":""},"user":{"visible":false,"bindToCurrentUser":false,"settings":false,"settingsLanguage":"en","emailConfirmationUrl":"","passwordResetUrl":"","name":"","description":"","avatar":"","status":"none"},"appearance":{"background":"var(--wr-theme-surface)","foreground":"var(--wr-theme-heading)","muted":"var(--wr-theme-text-muted)","border":"var(--wr-theme-border)","hoverBackground":"var(--wr-theme-disabled-surface)","activeBackground":"var(--wr-theme-surface-muted)","activeForeground":"var(--wr-theme-primary)","accent":"var(--wr-theme-primary)","badgeBackground":"var(--wr-theme-surface-muted)","badgeForeground":"var(--wr-theme-primary)","itemHeight":44,"itemRadius":10,"itemGap":4,"iconSize":20,"fontSize":14,"padding":14,"indent":18,"collapsedWidth":72},"items":[{"id":"home","label":"Home","icon":"house"},{"id":"workspace","label":"Workspace","icon":"layers","expanded":true,"children":[{"id":"projects","label":"Projects","icon":"folder"},{"id":"team","label":"Team","icon":"users"}]},{"id":"settings","label":"Settings","icon":"settings"}]}` | configuration |
| `placement` — Placement · | `enum` — `in-flow` `floating` | `"floating"` | placement — Read-only in the Inspector |
| `floatingHorizontalAnchor` — Horizontal side · Menu placement | `enum` — `left` `center` `right` | `"left"` | floatingHorizontalAnchor |
| `floatingVerticalAnchor` — Vertical side · Menu placement | `enum` — `top` `center` `bottom` | `"top"` | floatingVerticalAnchor |
| `floatingZIndex` — Layer · | `number` | `1000` | floatingZIndex — Read-only in the Inspector |
| `currentRowBindings` — Current row · Data — Bind writable visual values to fields of this repeated row. | `current-row-bindings` — Editor: current-row-bindings | `[]` | currentRowBindings |

## API in code

| Member | Signature | Access |
| --- | --- | --- |
| `floatingZIndex` | `get floatingZIndex(): number;` | Read-only |
| `margin` | `get margin(): number;` | Read and write |
| `marginTop` | `get marginTop(): number;` | Read and write |
| `marginRight` | `get marginRight(): number;` | Read and write |
| `marginBottom` | `get marginBottom(): number;` | Read and write |
| `marginLeft` | `get marginLeft(): number;` | Read and write |
| `placement` | `get placement(): 'floating';` | Read-only |
| `floatingHorizontalAnchor` | `get floatingHorizontalAnchor(): 'left' \| 'center' \| 'right';` | Read and write |
| `floatingVerticalAnchor` | `get floatingVerticalAnchor(): 'top' \| 'center' \| 'bottom';` | Read and write |
| `configuration` | `get configuration(): WebMenuConfiguration;` | Read and write |
| `enabled` | `get enabled(): boolean;` | Read and write |
| `items` | `get items(): readonly WebMenuItem[];` | Read and write |
| `orientation` | `get orientation(): WebMenuConfiguration['orientation'];` | Read and write |
| `brand` | `get brand(): WebMenuConfiguration['brand'];` | Read and write |
| `user` | `get user(): WebMenuConfiguration['user'];` | Read and write |
| `appearance` | `get appearance(): WebMenuConfiguration['appearance'];` | Read and write |
| `selectedItemId` | `get selectedItemId(): string;` | Read and write |
| `collapsed` | `get collapsed(): boolean;` | Read and write |
| `isOpen` | `get isOpen(): boolean;` | Read-only |
| `isMobile` | `get isMobile(): boolean;` | Read-only |
| `searchMode` | `get searchMode(): WebMenuConfiguration['searchMode'];` | Read and write |
| `themeToggle` | `get themeToggle(): boolean;` | Read and write |
| `searchText` | `get searchText(): string;` | Read and write |

### `createDefinition`

method

```ts
static createDefinition(element: HTMLElement, factory: (element: HTMLElement) => WebMenu, initialize: (menu: WebMenu) => void): WebMenu;
```

Apply the saved placement before the authored subclass constructor runs.

Example

```ts
Menu1.createDefinition();
```

### `fromElement`

method

```ts
static fromElement<T extends WebMenu>(element: HTMLElement, menuClass: new (element: HTMLElement) => T): T;
```

Resolve the concrete authored Menu already mounted for this element.

Example

```ts
Menu1.fromElement();
```

### `emit`

method

```ts
emit(eventName: string, detail?: unknown): boolean;
```

Example

```ts
Menu1.emit('OnClick');
```

### `findItem`

method

```ts
findItem(id: string): WebMenuItem | undefined;
```

Example

```ts
Menu1.findItem('home');
```

### `updateItem`

method

```ts
updateItem(id: string, changes: Partial<Omit<WebMenuItem, 'id'>>): void;
```

Example

```ts
Menu1.updateItem('home', { badge: '3' });
```

### `expandItem`

method

```ts
expandItem(id: string): void;
```

Example

```ts
Menu1.expandItem('records');
```

### `collapseItem`

method

```ts
collapseItem(id: string): void;
```

Example

```ts
Menu1.collapseItem('records');
```

### `toggleItem`

method

```ts
toggleItem(id: string): void;
```

Example

```ts
Menu1.toggleItem('records');
```

### `focusItem`

method

```ts
focusItem(id: string): void;
```

Example

```ts
Menu1.focusItem('home');
```

### `activateItem`

method

```ts
activateItem(id: string): void;
```

Example

```ts
Menu1.activateItem('home');
```

### `open`

method

```ts
open(): void;
```

Example

```ts
Menu1.open();
```

### `close`

method

```ts
close(): void;
```

Example

```ts
Menu1.close();
```

### `toggle`

method

```ts
toggle(): void;
```

Example

```ts
Menu1.toggle();
```

### `openUserSettings`

method

```ts
openUserSettings(): void;
```

Open the account editor when the saved Menu user settings option is enabled.

Example

```ts
Menu1.openUserSettings();
```

### `closeUserSettings`

method

```ts
closeUserSettings(): void;
```

Example

```ts
Menu1.closeUserSettings();
```

### `dispose`

method

```ts
dispose(): void;
```

Example

```ts
// Call only when disposing of an instance whose lifecycle you control.
Menu1.dispose();
```

Inherited members (19)

| Member | Signature | Access |
| --- | --- | --- |
| `currentRowBindings` | `currentRowBindings: readonly WebCurrentRowBinding[];` | Read and write |
| `element` | `readonly element: TElement;` | Read-only |
| `id` | `readonly id: string;` | Read-only |
| `name` | `readonly name: string;` | Read-only |
| `visible` | `visible: boolean;` | Read and write |
| `collapseWhenHidden` | `collapseWhenHidden: boolean;` | Read and write |
| `x` | `x: number;` | Read and write |
| `y` | `y: number;` | Read and write |
| `width` | `width: number;` | Read and write |
| `height` | `height: number;` | Read and write |
| `alignSelf` | `alignSelf: RuntimeLayoutAlignSelf;` | Read and write |
| `tabIndex` | `tabIndex: number;` | Read and write |
| `backgroundColor` | `backgroundColor: string;` | Read and write |
| `color` | `color: string;` | Read and write |
| `borderColor` | `borderColor: string;` | Read and write |

### `focus`

method

```ts
focus(options?: FocusOptions): void;
```

### `show`

method

```ts
show(): void;
```

### `hide`

method

```ts
hide(): void;
```

### `bindEvent`

method

```ts
bindEvent(eventName: string, handler: WebComponentEventHandler<this> | null): () => void;
```

## Events

Link methods in **Interaction**. [How to use events](https://webrad.atentor.com/docs/interactions).

### OnSearch

[WebMenuSearchEvent](https://webrad.atentor.com/docs/types#webmenusearchevent) in `event.detail` · `text`

Emitted when the user presses Enter in event search mode. Typing and clearing do not emit; menu items remain unchanged.

Signature and example

```ts
import type { WebMenu, WebMenuSearchEvent } from '@webrad/runtime';
// Copy this method into the page class and link it in Interaction.
class Example {
  public async Menu1Search(
    sender: WebMenu,
    event: CustomEvent<WebMenuSearchEvent>,
  ): Promise<void> {
    console.log(event.detail);
    const { text } = event.detail;
    console.log(text);
  }
}
```

### OnItemClick

[WebMenuItemEvent](https://webrad.atentor.com/docs/types#webmenuitemevent) in `event.detail` · `item`, `itemId`, `path`

Call event.preventDefault() synchronously to cancel automatic selection and navigation.

Signature and example

```ts
import type { WebMenu, WebMenuItemEvent } from '@webrad/runtime';
// Copy this method into the page class and link it in Interaction.
class Example {
  public async Menu1ItemClick(
    sender: WebMenu,
    event: CustomEvent<WebMenuItemEvent>,
  ): Promise<void> {
    console.log(event.detail);
    const { item, itemId, path } = event.detail;
    console.log(item, itemId, path);
  }
}
```

### OnSelectionChange

[WebMenuItemEvent](https://webrad.atentor.com/docs/types#webmenuitemevent) in `event.detail` · `item`, `itemId`

Signature and example

```ts
import type { WebMenu, WebMenuItemEvent } from '@webrad/runtime';
// Copy this method into the page class and link it in Interaction.
class Example {
  public async Menu1SelectionChange(
    sender: WebMenu,
    event: CustomEvent<WebMenuItemEvent>,
  ): Promise<void> {
    console.log(event.detail);
    const { item, itemId } = event.detail;
    console.log(item, itemId);
  }
}
```

### OnItemToggle

[WebMenuItemToggleEvent](https://webrad.atentor.com/docs/types#webmenuitemtoggleevent) in `event.detail` · `itemId`, `expanded`

Signature and example

```ts
import type { WebMenu, WebMenuItemToggleEvent } from '@webrad/runtime';
// Copy this method into the page class and link it in Interaction.
class Example {
  public async Menu1ItemToggle(
    sender: WebMenu,
    event: CustomEvent<WebMenuItemToggleEvent>,
  ): Promise<void> {
    console.log(event.detail);
    const { itemId, expanded } = event.detail;
    console.log(itemId, expanded);
  }
}
```

### OnCollapseChange

[WebMenuStateEvent](https://webrad.atentor.com/docs/types#webmenustateevent) in `event.detail` · `collapsed`

Signature and example

```ts
import type { WebMenu, WebMenuStateEvent } from '@webrad/runtime';
// Copy this method into the page class and link it in Interaction.
class Example {
  public async Menu1CollapseChange(
    sender: WebMenu,
    event: CustomEvent<WebMenuStateEvent>,
  ): Promise<void> {
    console.log(event.detail);
    const { collapsed } = event.detail;
    console.log(collapsed);
  }
}
```

### OnOpen

[WebMenuStateEvent](https://webrad.atentor.com/docs/types#webmenustateevent) in `event.detail`

Signature and example

```ts
import type { WebMenu, WebMenuStateEvent } from '@webrad/runtime';
// Copy this method into the page class and link it in Interaction.
class Example {
  public async Menu1Open(
    sender: WebMenu,
    event: CustomEvent<WebMenuStateEvent>,
  ): Promise<void> {
    console.log(event.detail);
  }
}
```

### OnClose

[WebMenuStateEvent](https://webrad.atentor.com/docs/types#webmenustateevent) in `event.detail`

Signature and example

```ts
import type { WebMenu, WebMenuStateEvent } from '@webrad/runtime';
// Copy this method into the page class and link it in Interaction.
class Example {
  public async Menu1Close(
    sender: WebMenu,
    event: CustomEvent<WebMenuStateEvent>,
  ): Promise<void> {
    console.log(event.detail);
  }
}
```

### OnBrandClick

[WebMenuBrand](https://webrad.atentor.com/docs/types#webmenubrand) in `event.detail`

Signature and example

```ts
import type { WebMenu, WebMenuBrand } from '@webrad/runtime';
// Copy this method into the page class and link it in Interaction.
class Example {
  public async Menu1BrandClick(
    sender: WebMenu,
    event: CustomEvent<WebMenuBrand>,
  ): Promise<void> {
    console.log(event.detail);
  }
}
```

### OnUserClick

[WebMenuUser](https://webrad.atentor.com/docs/types#webmenuuser) in `event.detail`

Signature and example

```ts
import type { WebMenu, WebMenuUser } from '@webrad/runtime';
// Copy this method into the page class and link it in Interaction.
class Example {
  public async Menu1UserClick(
    sender: WebMenu,
    event: CustomEvent<WebMenuUser>,
  ): Promise<void> {
    console.log(event.detail);
  }
}
```
