Vertical Menu
Side navigation with submenus, for deeper hierarchies than a top bar can carry.


Reach for it when you need:
- Area → line → equipment navigation down the side of a screen
Component name: VerticalMenu · Category: Navigation
In the palette: Side navigation with submenus
Properties
Any property can be bound to a tag, another component's property, or a query.
| Property | Type | Default | Description |
|---|---|---|---|
menu | array | [{"label":"Dashboard","icon":"LayoutDashboard","link":{"path… (truncated) | — |
↳ menu.label | string | New Item | — |
↳ menu.key | string | "" | Stable id for this item, available to the Menu item click event as {{item.key}}. |
↳ menu.icon | icon | Circle | Lucide icon name |
↳ menu.description | string | "" | — |
↳ menu.visible | boolean | true | Off = the item is removed entirely and leaves no space. 1.0.8 |
↳ menu.disabled | boolean | false | Grey the item out and block clicks (and, for a group, opening it). Applies to its sub-items too. 1.0.8 |
↳ menu.position | number | — | Sort order within this level (lower first). Empty = its place in the list. 1.0.8 |
↳ menu.pin | enum | start | start = with the rest; end = parked at the far end (bottom of a vertical menu, right of a horizontal one). Options: start · end. |
↳ menu.link | object | {} | — 1.0.8 |
↳ menu.link.path | string | "" | — 1.0.8 |
↳ menu.link.linkType | enum | auto | auto = detect from the path; internal = a project route (SPA navigation); external = a URL outside the app. Options: auto · internal · external. 1.0.8 |
↳ menu.link.openIn | enum | auto | auto = same tab for an internal route, new tab for an external URL. Options: auto · same · new. 1.0.8 |
↳ menu.normal | object | {} | Resting text and background colour. 1.0.8 |
↳ menu.normal.color | color | "" | — 1.0.8 |
↳ menu.normal.backgroundColor | color | transparent | — 1.0.8 |
↳ menu.hover | object | {} | — 1.0.8 |
↳ menu.hover.color | color | hsl(var(--foreground)) | — 1.0.8 |
↳ menu.hover.backgroundColor | color | rgba(161, 161, 170, 0.1) | — 1.0.8 |
↳ menu.active | object | {} | Colours while the item is the current page. 1.0.8 |
↳ menu.active.color | color | hsl(var(--foreground)) | — 1.0.8 |
↳ menu.active.backgroundColor | color | rgba(161, 161, 170, 0.15) | — 1.0.8 |
↳ menu.font | object | {} | — |
↳ menu.font.fontSize | number | — | — |
↳ menu.font.fontWeight | enum | — | Options: normal · bold · semibold · medium · light. |
↳ menu.font.fontStyle | enum | — | Options: normal · italic. |
↳ menu.font.textAlign | enum | — | Options: left · center · right. |
↳ menu.font.letterSpacing | number | — | — |
↳ menu.font.lineHeight | number | — | — |
↳ menu.font.textDecoration | enum | — | Options: none · underline · line-through. |
↳ menu.font.textTransform | enum | — | Options: none · uppercase · lowercase · capitalize. |
↳ menu.badge | object | {"value":"","fontColor":"","fontSize":10,"backgroundColor":"… (truncated) | — |
↳ menu.badge.value | string | "" | — |
↳ menu.badge.fontColor | color | "" | — |
↳ menu.badge.fontSize | number | 10 | — |
↳ menu.badge.backgroundColor | color | "" | — |
↳ menu.separator | object | {} | A line above and/or below this item. 1.0.8 |
↳ menu.separator.position | enum | none | Which side(s) of the item draw a separator line. Options: none · top · bottom · both. 1.0.8 |
↳ menu.separator.spacing | number | 4 | Space in px between the line and the item. 1.0.8 |
↳ menu.separator.insetLeft | number | 0 | Space in px from the left edge. 0 (with insetRight 0) = full width. 1.0.8 |
↳ menu.separator.insetRight | number | 0 | Space in px from the right edge. 0 (with insetLeft 0) = full width. 1.0.8 |
↳ menu.separator.color | color | "" | Empty = the theme border colour. 1.0.8 |
↳ menu.separator.thickness | number | 1 | — 1.0.8 |
↳ menu.separator.style | enum | solid | Options: solid · dashed · dotted. 1.0.8 |
↳ menu.gap | number | 4 | — |
↳ menu.security | object | {} | Optional role gating for this item. Leave roles empty = visible to everyone. |
↳ menu.security.requiredRoles | roles | [] | Pick the roles allowed to see this item (from the bound User Database). Empty = shown to everyone. |
↳ menu.security.roleMatch | enum | any | any = holds at least one role; all = holds every listed role. Options: any · all. |
↳ menu.security.denyBehavior | enum | hide | hide = remove the item; disable = grey it out and block clicks. Options: hide · disable. |
↳ menu.subMenu | array | [] | — |
↳ menu.subMenu.label | string | New Item | — |
↳ menu.subMenu.key | string | "" | Stable id for this item, available to the Menu item click event as {{item.key}}. |
↳ menu.subMenu.icon | icon | Circle | Lucide icon name |
↳ menu.subMenu.description | string | "" | — |
↳ menu.subMenu.visible | boolean | true | Off = the item is removed entirely and leaves no space. 1.0.8 |
↳ menu.subMenu.disabled | boolean | false | Grey the item out and block clicks (and, for a group, opening it). Applies to its sub-items too. 1.0.8 |
↳ menu.subMenu.position | number | — | Sort order within this level (lower first). Empty = its place in the list. 1.0.8 |
↳ menu.subMenu.link | object | {} | — 1.0.8 |
↳ menu.subMenu.link.path | string | "" | — 1.0.8 |
↳ menu.subMenu.link.linkType | enum | auto | auto = detect from the path; internal = a project route (SPA navigation); external = a URL outside the app. Options: auto · internal · external. 1.0.8 |
↳ menu.subMenu.link.openIn | enum | auto | auto = same tab for an internal route, new tab for an external URL. Options: auto · same · new. 1.0.8 |
↳ menu.subMenu.normal | object | {} | Resting text and background colour. 1.0.8 |
↳ menu.subMenu.normal.color | color | "" | — 1.0.8 |
↳ menu.subMenu.normal.backgroundColor | color | transparent | — 1.0.8 |
↳ menu.subMenu.hover | object | {} | — 1.0.8 |
↳ menu.subMenu.hover.color | color | hsl(var(--foreground)) | — 1.0.8 |
↳ menu.subMenu.hover.backgroundColor | color | rgba(161, 161, 170, 0.1) | — 1.0.8 |
↳ menu.subMenu.active | object | {} | Colours while the item is the current page. 1.0.8 |
↳ menu.subMenu.active.color | color | hsl(var(--foreground)) | — 1.0.8 |
↳ menu.subMenu.active.backgroundColor | color | rgba(161, 161, 170, 0.15) | — 1.0.8 |
↳ menu.subMenu.font | object | {} | — |
↳ menu.subMenu.font.fontSize | number | — | — |
↳ menu.subMenu.font.fontWeight | enum | — | Options: normal · bold · semibold · medium · light. |
↳ menu.subMenu.font.fontStyle | enum | — | Options: normal · italic. |
↳ menu.subMenu.font.textAlign | enum | — | Options: left · center · right. |
↳ menu.subMenu.font.letterSpacing | number | — | — |
↳ menu.subMenu.font.lineHeight | number | — | — |
↳ menu.subMenu.font.textDecoration | enum | — | Options: none · underline · line-through. |
↳ menu.subMenu.font.textTransform | enum | — | Options: none · uppercase · lowercase · capitalize. |
↳ menu.subMenu.badge | object | {"value":"","fontColor":"","fontSize":10,"backgroundColor":"… (truncated) | — |
↳ menu.subMenu.badge.value | string | "" | — |
↳ menu.subMenu.badge.fontColor | color | "" | — |
↳ menu.subMenu.badge.fontSize | number | 10 | — |
↳ menu.subMenu.badge.backgroundColor | color | "" | — |
↳ menu.subMenu.separator | object | {} | A line above and/or below this item. 1.0.8 |
↳ menu.subMenu.separator.position | enum | none | Which side(s) of the item draw a separator line. Options: none · top · bottom · both. 1.0.8 |
↳ menu.subMenu.separator.spacing | number | 4 | Space in px between the line and the item. 1.0.8 |
↳ menu.subMenu.separator.insetLeft | number | 0 | Space in px from the left edge. 0 (with insetRight 0) = full width. 1.0.8 |
↳ menu.subMenu.separator.insetRight | number | 0 | Space in px from the right edge. 0 (with insetLeft 0) = full width. 1.0.8 |
↳ menu.subMenu.separator.color | color | "" | Empty = the theme border colour. 1.0.8 |
↳ menu.subMenu.separator.thickness | number | 1 | — 1.0.8 |
↳ menu.subMenu.separator.style | enum | solid | Options: solid · dashed · dotted. 1.0.8 |
↳ menu.subMenu.gap | number | 4 | — |
↳ menu.subMenu.security | object | {} | Optional role gating for this item. Leave roles empty = visible to everyone. |
↳ menu.subMenu.security.requiredRoles | roles | [] | Pick the roles allowed to see this item (from the bound User Database). Empty = shown to everyone. |
↳ menu.subMenu.security.roleMatch | enum | any | any = holds at least one role; all = holds every listed role. Options: any · all. |
↳ menu.subMenu.security.denyBehavior | enum | hide | hide = remove the item; disable = grey it out and block clicks. Options: hide · disable. |
↳ menu.subMenu.subMenu | array | [] | — |
↳ menu.subMenu.subMenu.label | string | New Item | — |
↳ menu.subMenu.subMenu.key | string | "" | Stable id for this item, available to the Menu item click event as {{item.key}}. |
↳ menu.subMenu.subMenu.icon | icon | Circle | Lucide icon name |
↳ menu.subMenu.subMenu.description | string | "" | — |
↳ menu.subMenu.subMenu.visible | boolean | true | Off = the item is removed entirely and leaves no space. 1.0.8 |
↳ menu.subMenu.subMenu.disabled | boolean | false | Grey the item out and block clicks (and, for a group, opening it). Applies to its sub-items too. 1.0.8 |
↳ menu.subMenu.subMenu.position | number | — | Sort order within this level (lower first). Empty = its place in the list. 1.0.8 |
↳ menu.subMenu.subMenu.link | object | {} | — 1.0.8 |
↳ menu.subMenu.subMenu.link.path | string | "" | — 1.0.8 |
↳ menu.subMenu.subMenu.link.linkType | enum | auto | auto = detect from the path; internal = a project route (SPA navigation); external = a URL outside the app. Options: auto · internal · external. 1.0.8 |
↳ menu.subMenu.subMenu.link.openIn | enum | auto | auto = same tab for an internal route, new tab for an external URL. Options: auto · same · new. 1.0.8 |
↳ menu.subMenu.subMenu.normal | object | {} | Resting text and background colour. 1.0.8 |
↳ menu.subMenu.subMenu.normal.color | color | "" | — 1.0.8 |
↳ menu.subMenu.subMenu.normal.backgroundColor | color | transparent | — 1.0.8 |
↳ menu.subMenu.subMenu.hover | object | {} | — 1.0.8 |
↳ menu.subMenu.subMenu.hover.color | color | hsl(var(--foreground)) | — 1.0.8 |
↳ menu.subMenu.subMenu.hover.backgroundColor | color | rgba(161, 161, 170, 0.1) | — 1.0.8 |
↳ menu.subMenu.subMenu.active | object | {} | Colours while the item is the current page. 1.0.8 |
↳ menu.subMenu.subMenu.active.color | color | hsl(var(--foreground)) | — 1.0.8 |
↳ menu.subMenu.subMenu.active.backgroundColor | color | rgba(161, 161, 170, 0.15) | — 1.0.8 |
↳ menu.subMenu.subMenu.font | object | {} | — |
↳ menu.subMenu.subMenu.font.fontSize | number | — | — |
↳ menu.subMenu.subMenu.font.fontWeight | enum | — | Options: normal · bold · semibold · medium · light. |
↳ menu.subMenu.subMenu.font.fontStyle | enum | — | Options: normal · italic. |
↳ menu.subMenu.subMenu.font.textAlign | enum | — | Options: left · center · right. |
↳ menu.subMenu.subMenu.font.letterSpacing | number | — | — |
↳ menu.subMenu.subMenu.font.lineHeight | number | — | — |
↳ menu.subMenu.subMenu.font.textDecoration | enum | — | Options: none · underline · line-through. |
↳ menu.subMenu.subMenu.font.textTransform | enum | — | Options: none · uppercase · lowercase · capitalize. |
↳ menu.subMenu.subMenu.badge | object | {"value":"","fontColor":"","fontSize":10,"backgroundColor":"… (truncated) | — |
↳ menu.subMenu.subMenu.badge.value | string | "" | — |
↳ menu.subMenu.subMenu.badge.fontColor | color | "" | — |
↳ menu.subMenu.subMenu.badge.fontSize | number | 10 | — |
↳ menu.subMenu.subMenu.badge.backgroundColor | color | "" | — |
↳ menu.subMenu.subMenu.separator | object | {} | A line above and/or below this item. 1.0.8 |
↳ menu.subMenu.subMenu.separator.position | enum | none | Which side(s) of the item draw a separator line. Options: none · top · bottom · both. 1.0.8 |
↳ menu.subMenu.subMenu.separator.spacing | number | 4 | Space in px between the line and the item. 1.0.8 |
↳ menu.subMenu.subMenu.separator.insetLeft | number | 0 | Space in px from the left edge. 0 (with insetRight 0) = full width. 1.0.8 |
↳ menu.subMenu.subMenu.separator.insetRight | number | 0 | Space in px from the right edge. 0 (with insetLeft 0) = full width. 1.0.8 |
↳ menu.subMenu.subMenu.separator.color | color | "" | Empty = the theme border colour. 1.0.8 |
↳ menu.subMenu.subMenu.separator.thickness | number | 1 | — 1.0.8 |
↳ menu.subMenu.subMenu.separator.style | enum | solid | Options: solid · dashed · dotted. 1.0.8 |
↳ menu.subMenu.subMenu.gap | number | 4 | — |
↳ menu.subMenu.subMenu.security | object | {} | Optional role gating for this item. Leave roles empty = visible to everyone. |
↳ menu.subMenu.subMenu.security.requiredRoles | roles | [] | Pick the roles allowed to see this item (from the bound User Database). Empty = shown to everyone. |
↳ menu.subMenu.subMenu.security.roleMatch | enum | any | any = holds at least one role; all = holds every listed role. Options: any · all. |
↳ menu.subMenu.subMenu.security.denyBehavior | enum | hide | hide = remove the item; disable = grey it out and block clicks. Options: hide · disable. |
↳ menu.subMenu.subMenu.subMenu | array | [] | — |
↳ menu.subMenu.subMenu.subMenu.label | string | New Item | — |
↳ menu.subMenu.subMenu.subMenu.key | string | "" | Stable id for this item, available to the Menu item click event as {{item.key}}. |
↳ menu.subMenu.subMenu.subMenu.icon | icon | Circle | Lucide icon name |
↳ menu.subMenu.subMenu.subMenu.description | string | "" | — |
↳ menu.subMenu.subMenu.subMenu.visible | boolean | true | Off = the item is removed entirely and leaves no space. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.disabled | boolean | false | Grey the item out and block clicks (and, for a group, opening it). Applies to its sub-items too. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.position | number | — | Sort order within this level (lower first). Empty = its place in the list. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.link | object | {} | — 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.link.path | string | "" | — 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.link.linkType | enum | auto | auto = detect from the path; internal = a project route (SPA navigation); external = a URL outside the app. Options: auto · internal · external. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.link.openIn | enum | auto | auto = same tab for an internal route, new tab for an external URL. Options: auto · same · new. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.normal | object | {} | Resting text and background colour. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.normal.color | color | "" | — 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.normal.backgroundColor | color | transparent | — 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.hover | object | {} | — 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.hover.color | color | hsl(var(--foreground)) | — 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.hover.backgroundColor | color | rgba(161, 161, 170, 0.1) | — 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.active | object | {} | Colours while the item is the current page. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.active.color | color | hsl(var(--foreground)) | — 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.active.backgroundColor | color | rgba(161, 161, 170, 0.15) | — 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.font | object | {} | — |
↳ menu.subMenu.subMenu.subMenu.font.fontSize | number | — | — |
↳ menu.subMenu.subMenu.subMenu.font.fontWeight | enum | — | Options: normal · bold · semibold · medium · light. |
↳ menu.subMenu.subMenu.subMenu.font.fontStyle | enum | — | Options: normal · italic. |
↳ menu.subMenu.subMenu.subMenu.font.textAlign | enum | — | Options: left · center · right. |
↳ menu.subMenu.subMenu.subMenu.font.letterSpacing | number | — | — |
↳ menu.subMenu.subMenu.subMenu.font.lineHeight | number | — | — |
↳ menu.subMenu.subMenu.subMenu.font.textDecoration | enum | — | Options: none · underline · line-through. |
↳ menu.subMenu.subMenu.subMenu.font.textTransform | enum | — | Options: none · uppercase · lowercase · capitalize. |
↳ menu.subMenu.subMenu.subMenu.badge | object | {"value":"","fontColor":"","fontSize":10,"backgroundColor":"… (truncated) | — |
↳ menu.subMenu.subMenu.subMenu.badge.value | string | "" | — |
↳ menu.subMenu.subMenu.subMenu.badge.fontColor | color | "" | — |
↳ menu.subMenu.subMenu.subMenu.badge.fontSize | number | 10 | — |
↳ menu.subMenu.subMenu.subMenu.badge.backgroundColor | color | "" | — |
↳ menu.subMenu.subMenu.subMenu.separator | object | {} | A line above and/or below this item. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.separator.position | enum | none | Which side(s) of the item draw a separator line. Options: none · top · bottom · both. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.separator.spacing | number | 4 | Space in px between the line and the item. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.separator.insetLeft | number | 0 | Space in px from the left edge. 0 (with insetRight 0) = full width. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.separator.insetRight | number | 0 | Space in px from the right edge. 0 (with insetLeft 0) = full width. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.separator.color | color | "" | Empty = the theme border colour. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.separator.thickness | number | 1 | — 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.separator.style | enum | solid | Options: solid · dashed · dotted. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.gap | number | 4 | — |
↳ menu.subMenu.subMenu.subMenu.security | object | {} | Optional role gating for this item. Leave roles empty = visible to everyone. |
↳ menu.subMenu.subMenu.subMenu.security.requiredRoles | roles | [] | Pick the roles allowed to see this item (from the bound User Database). Empty = shown to everyone. |
↳ menu.subMenu.subMenu.subMenu.security.roleMatch | enum | any | any = holds at least one role; all = holds every listed role. Options: any · all. |
↳ menu.subMenu.subMenu.subMenu.security.denyBehavior | enum | hide | hide = remove the item; disable = grey it out and block clicks. Options: hide · disable. |
↳ menu.subMenu.subMenu.subMenu.subMenu | array | [] | — |
↳ menu.subMenu.subMenu.subMenu.subMenu.label | string | New Item | — |
↳ menu.subMenu.subMenu.subMenu.subMenu.key | string | "" | Stable id for this item, available to the Menu item click event as {{item.key}}. |
↳ menu.subMenu.subMenu.subMenu.subMenu.icon | icon | Circle | Lucide icon name |
↳ menu.subMenu.subMenu.subMenu.subMenu.description | string | "" | — |
↳ menu.subMenu.subMenu.subMenu.subMenu.visible | boolean | true | Off = the item is removed entirely and leaves no space. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.disabled | boolean | false | Grey the item out and block clicks (and, for a group, opening it). Applies to its sub-items too. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.position | number | — | Sort order within this level (lower first). Empty = its place in the list. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.link | object | {} | — 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.link.path | string | "" | — 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.link.linkType | enum | auto | auto = detect from the path; internal = a project route (SPA navigation); external = a URL outside the app. Options: auto · internal · external. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.link.openIn | enum | auto | auto = same tab for an internal route, new tab for an external URL. Options: auto · same · new. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.normal | object | {} | Resting text and background colour. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.normal.color | color | "" | — 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.normal.backgroundColor | color | transparent | — 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.hover | object | {} | — 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.hover.color | color | hsl(var(--foreground)) | — 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.hover.backgroundColor | color | rgba(161, 161, 170, 0.1) | — 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.active | object | {} | Colours while the item is the current page. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.active.color | color | hsl(var(--foreground)) | — 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.active.backgroundColor | color | rgba(161, 161, 170, 0.15) | — 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.font | object | {} | — |
↳ menu.subMenu.subMenu.subMenu.subMenu.font.fontSize | number | — | — |
↳ menu.subMenu.subMenu.subMenu.subMenu.font.fontWeight | enum | — | Options: normal · bold · semibold · medium · light. |
↳ menu.subMenu.subMenu.subMenu.subMenu.font.fontStyle | enum | — | Options: normal · italic. |
↳ menu.subMenu.subMenu.subMenu.subMenu.font.textAlign | enum | — | Options: left · center · right. |
↳ menu.subMenu.subMenu.subMenu.subMenu.font.letterSpacing | number | — | — |
↳ menu.subMenu.subMenu.subMenu.subMenu.font.lineHeight | number | — | — |
↳ menu.subMenu.subMenu.subMenu.subMenu.font.textDecoration | enum | — | Options: none · underline · line-through. |
↳ menu.subMenu.subMenu.subMenu.subMenu.font.textTransform | enum | — | Options: none · uppercase · lowercase · capitalize. |
↳ menu.subMenu.subMenu.subMenu.subMenu.badge | object | {"value":"","fontColor":"","fontSize":10,"backgroundColor":"… (truncated) | — |
↳ menu.subMenu.subMenu.subMenu.subMenu.badge.value | string | "" | — |
↳ menu.subMenu.subMenu.subMenu.subMenu.badge.fontColor | color | "" | — |
↳ menu.subMenu.subMenu.subMenu.subMenu.badge.fontSize | number | 10 | — |
↳ menu.subMenu.subMenu.subMenu.subMenu.badge.backgroundColor | color | "" | — |
↳ menu.subMenu.subMenu.subMenu.subMenu.separator | object | {} | A line above and/or below this item. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.separator.position | enum | none | Which side(s) of the item draw a separator line. Options: none · top · bottom · both. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.separator.spacing | number | 4 | Space in px between the line and the item. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.separator.insetLeft | number | 0 | Space in px from the left edge. 0 (with insetRight 0) = full width. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.separator.insetRight | number | 0 | Space in px from the right edge. 0 (with insetLeft 0) = full width. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.separator.color | color | "" | Empty = the theme border colour. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.separator.thickness | number | 1 | — 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.separator.style | enum | solid | Options: solid · dashed · dotted. 1.0.8 |
↳ menu.subMenu.subMenu.subMenu.subMenu.gap | number | 4 | — |
↳ menu.subMenu.subMenu.subMenu.subMenu.security | object | {} | Optional role gating for this item. Leave roles empty = visible to everyone. |
↳ menu.subMenu.subMenu.subMenu.subMenu.security.requiredRoles | roles | [] | Pick the roles allowed to see this item (from the bound User Database). Empty = shown to everyone. |
↳ menu.subMenu.subMenu.subMenu.subMenu.security.roleMatch | enum | any | any = holds at least one role; all = holds every listed role. Options: any · all. |
↳ menu.subMenu.subMenu.subMenu.subMenu.security.denyBehavior | enum | hide | hide = remove the item; disable = grey it out and block clicks. Options: hide · disable. |
gap | number | 4 | — |
size | enum | inherit | Row height, text and icon size of every item. Project default = the project's Control size (Project settings → Theme). An item's own font size still wins for that item. Options: inherit · xs · sm · md · lg · xl. 1.0.8 |
align | enum | top | Where the items sit along the column when the menu is taller than them. Options: top · center · bottom. |
showExternalIcon | boolean | true | Mark items that leave the app with a small arrow glyph. |
disabled | boolean | false | Disable the whole menu: every item greyed out and inert. 1.0.8 |
rail | object | {} | The icon-only rail: collapse to icons and back. 1.0.8 |
↳ rail.collapsible | boolean | false | Offer the icon-only rail (and its collapse toggle). 1.0.8 |
↳ rail.collapsed | boolean | false | Start collapsed to icons. Bind it to drive the rail from a tag or variable. 1.0.8 |
↳ rail.collapseMode | enum | tooltip | tooltip = stay a rail and show the label beside the icon; expand = widen back over the page while hovered. Options: tooltip · expand. 1.0.8 |
↳ rail.collapsedWidth | number | 56 | — 1.0.8 |
↳ rail.expandedWidth | number | 240 | Width when expanded. Independent of the widget's own box, so a narrow rail can still open wide. 1.0.8 |
↳ rail.showToggle | boolean | true | Render the collapse/expand button at the top of the menu. 1.0.8 |
style | style | {} | Custom CSS properties |
Events
Attach event actions to these in the Event Manager.
| Event | Label | Group | Payload |
|---|---|---|---|
click | Click | Mouse | The DOM event. |
dblclick | Double click | Mouse | The DOM event. |
contextmenu | Right click | Mouse | The DOM event. |
mouseenter | Mouse enter | Mouse | The DOM event. |
mouseleave | Mouse leave | Mouse | The DOM event. |
itemClick | Menu item click | Menu | The clicked item, under item: { key, label, path, href, external, newTab, level, index, hasSubMenu }. |
contextmenu fires your configured actions at runtime only — in LiveView and in preview. On the Designer canvas the right-click is captured to open the Event Manager, so testing it there will not run your actions. Test right-click in preview.
Notes
A menu saved by 1.0.7 opens with its items' destinations and colours gone, and its rail settings back at their defaults: the items render, but they no longer navigate and draw in the theme colours. Re-enter them under the new names:
| 1.0.7 (on each item) | 1.0.8 |
|---|---|
path, linkType, openIn | link.path, link.linkType, link.openIn |
color, backgroundColor | normal.color, normal.backgroundColor |
hoverColor, hoverBackgroundColor | hover.color, hover.backgroundColor |
activeColor, activeBgColor | active.color, active.backgroundColor |
font.fontColor | removed — it duplicated normal.color |
collapsible, collapsed, collapseMode, collapsedWidth, expandedWidth, showToggle (on the widget) | the same names under rail |
A script or binding that wrote the old keys into menu needs the same change.
Internal and external items
Each item's destination lives in its link group — path, linkType and openIn — and declares what its path means:
- internal — a route inside the project. It is prefixed with the project slug and navigated in-app with no page reload, so a path that happens to look like a hostname (
reports.2024/summary) still resolves as a route. - external — an address outside the app. A bare host is given
https://so the browser does not resolve it against the current page, it opens in a new tab by default withrel="noopener noreferrer", and a small arrow marks it so an operator knows the click leaves the HMI. - auto (the default) — work it out from the path. Fine for obvious cases; state it explicitly when the path is ambiguous.
Use link.openIn to override the tab in either direction: auto follows the kind, same keeps the operator on the current screen, new opens a second tab.
Making an item do more than navigate
An item does not have to go anywhere. Leave its link.path empty, give it a key, and handle Menu item click in the Event Manager. The chain receives the clicked item, so ONE handler on the menu covers every entry — each action guarded by the item it belongs to.
Every action is available (popover, panel, notification, sign in, sign out, set tag, set variable, send message, run script) because this is the same action list every other widget uses.
Worked example: Help opens a popover, Sign out signs out
1. Build the popover content. Create a view — say HelpCard — with whatever the popover should show, and give it a size (300 x 200 is a reasonable card). It needs no routing path: a popover hosts it in place.
2. Key the items. Select the menu and open the menu property. On the Help item set key to help; on the Sign out item set it to logout. Leave both link.path fields empty — these items run actions rather than navigating. (linkType and openIn disappear once a path is empty, which is the schema telling you the same thing.)
3. Open the handler. Right-click the menu on the canvas, choose Menu item click.
4. Add the popover. Add action → Popover. Set View to HelpCard, Anchor to This widget, and leave Side on Auto so it places itself wherever there is room.
5. Guard it to the Help item. On that action tick Only run this action when…, then:
| Field | Value |
|---|---|
| source | {{item.key}} — use the picker, under Event data |
| operator | is |
| value | help |
Without the guard the popover would open for every item in the menu, because the handler belongs to the whole widget rather than to one entry.
6. Add the second action. Add action → Sign out, guarded the same way with {{item.key}} is logout. Guarded actions are skipped cleanly — a skipped action records no result and raises no error, so the two never interfere.
7. Test in preview. Help opens the popover, Sign out signs out, and every other item navigates exactly as before.
Passing the clicked item into the view
Once the popover view declares params, the action shows a View Param table. Any payload field can go straight in:
| Param | Value |
|---|---|
topic | {{item.key}} |
title | {{item.label}} |
The full payload is {{item.key}}, {{item.label}}, {{item.path}}, {{item.href}}, {{item.external}}, {{item.newTab}}, {{item.level}}, {{item.index}} and {{item.hasSubMenu}}.
Renaming "Sign out" to "Log out" would silently unwire a guard that tested the label. The key is yours and changes only when you change it.
The event fires for submenu entries too, and it fires before any navigation — so an item can both run actions and follow its path.
Showing, hiding, ordering and disabling items New in 1.0.8
Every item — top level or inside a submenu — has three state fields, all bindable:
visible— off removes the item entirely: no element, no gap. This is different from the widget's own Visible, which hides the box but keeps its space. An item hidden this way is hidden in the editor too; select the menu and edit it in themenuproperty to bring it back. A group with no path of its own disappears when all of its children are hidden.position— a sort key within the item's own level. Lower comes first; an item without one sorts by its place in the list (0 for the first item, 1 for the second…), and equal positions keep the list order. Each submenu is ordered on its own, and pinned items are ordered among themselves. Bind it to reorder the menu from a tag.disabled— greys the item out, takes it out of the tab order and blocks clicks. A disabled group does not open, and its children are disabled with it. The widget-leveldisableddisables the whole menu at once.
Colours per state Changed in 1.0.8
Colours are grouped by state: normal, hover and active each hold a color and a backgroundColor. active applies while the item is the current page and is hidden on an external link, which never can be. Typography is in font.
Size New in 1.0.8
size sets the row height, text and icon of every item — xs 28 · sm 32 · md 36 · lg 44 · xl 52 px rows. Project default (inherit, the default) follows the project's Control size, and the flyouts of a submenu follow their menu. An item with its own font.fontSize keeps it.
Before 1.0.8 these widgets had one size, what is now md (a 44 px field). The project's Control size defaults to sm (36 px), sized for desktop dashboards, and every widget whose size is Project default follows it — which is every widget in an upgraded project. On a touch panel, set Project settings → Theme → Control size to md or larger: 44 px is the touch-target minimum.
Parking one item at the far end
Set an item's pin to end and it renders in its own group at the far end of the menu — the bottom of a vertical menu, the right of a horizontal one — while everything else stays together at the start. This is per item, which is the point: Sign in, Sign out and Settings need to sit apart from the navigation, and a whole-menu alignment cannot say that.
Pin as many as you like; their order within the pinned group is still yours. pin applies to top-level items only — a submenu opens as a flyout, so it has no far end of its own.
linkType and openIn only show once an item has a link.path, and no destination field shows on an item that has children — a group expands, it never navigates. The active colours are hidden on an external item, which can never be the page you are on.
Where the items sit
Set align to top, center or bottom to park the items along the column when the menu is taller than they are.
Separator lines New in 1.0.8
Each item (in a submenu too) has a separator group that draws a line above, below or on both sides of it:
| Field | What it does |
|---|---|
position | none (default), top, bottom or both. |
spacing | Space in px between the line and the item's text. Default 4. |
insetLeft / insetRight | Space in px from each edge. Both 0 (the default) = the line spans the full width. |
color | Empty = the theme border colour. |
thickness / style | Line weight in px, and solid, dashed or dotted. |
The other fields appear once position is not none. Separators are a Vertical Menu feature; the Horizontal Menu does not have them.
An icon-only sidebar, step by step
This is the shape most projects want: a sidebar down the left of every screen that shrinks to a strip of icons and back. It is built from two halves — the shell reserves the space, the menu fills it — so both have to be set up, and each has one field that does the real work.
Step 1 — build the menu view
Create a view containing just the Vertical Menu, and make the menu fill it. Size the view to the width you want the sidebar to have when it is open — 240 is a good starting point. Add the items you need, and pin Sign out (or Settings) to end so it sits at the bottom.
Step 2 — give the zone the view
Open Layout Configuration and select the Left zone.
- Enabled — on.
- View — the menu view from step 1.
- Behaviour —
Push, so the screen content shrinks beside the sidebar rather than being covered by it. - Width — 240, matching the view.
Step 3 — let the zone collapse to a rail
- Collapsible — on.
- Collapsed width —
56.
Step 3.2 is the one that matters. Left at 0 the zone collapses to nothing: it disappears and takes the menu with it, which is why an icon sidebar seems impossible until you find this field. Any other value leaves a rail of that width and keeps rendering the view inside it — which is exactly the strip the icons need.
- Start collapsed — on, if the screen should open with the rail.
Step 4 — decide what hovering the rail does
Back on the menu, set rail.collapseMode:
tooltip(default) — the rail stays a rail and the label appears beside the icon. Groups open their children as a flyout to the side. Nothing ever covers the screen.expand— hovering widens the rail back into the full menu, floating over the page, and it collapses again when the pointer leaves.
Submenus on a rail
A group cannot expand inside a 56px rail, so it opens a flyout to the side instead — on hover, with a short grace period so the diagonal trip into it does not close it. The flyout is titled with the name of the group it belongs to, points back at the icon it came from, and caps to of the screen and scrolls, so a group with twenty children stays reachable.
A group gets no tooltip — the flyout already names it, and stacking a tooltip over the children it just revealed is the one thing you do not want. Tooltips are for leaf items, whose label has nowhere else to go.
Step 5 — pick which toggle the operator sees
You now have two: the zone's edge tab, and the menu's own button. Keep whichever suits the design and turn the other off — the zone's Toggle button field set to None, or the menu's rail.showToggle set to false. Both collapse the same zone, so either works.
Inside a collapsible zone you do not set rail.collapsible, rail.collapsed or rail.collapsedWidth on the widget. The zone owns the width and the collapsed state; the menu reads them, draws itself at the rail width it was given, and its toggle collapses the zone so the layout reflows. That keeps one collapsed state instead of two that can disagree — which is what made the widget and the zone fight each other if you configured both.
Every zone field is per breakpoint, so set Collapsed width to 0 on mobile to have the sidebar get out of the way entirely there.
Collapsing a menu that is not in a zone
A Vertical Menu placed directly on a view can collapse on its own. Every rail setting lives in the rail group. Turn on rail.collapsible, then set rail.collapsedWidth (the rail, default 56) and rail.expandedWidth (default 240). The expanded width belongs to the menu, not to the widget box, which is what lets a narrow rail open out to a full panel. rail.collapsed sets the starting state and is bindable — bind it to a client variable to remember the operator's choice across screens.
A collapsible menu declares both of its widths, so a sibling in a flex or grid container reflows when it closes. A hover peek is the exception and always draws OVER the page: reflowing a whole screen for a glance makes the content jump out from under the cursor and back a moment later.
The panel that floats out on hover has to be opaque, so it paints the background you set on the menu itself; failing that, the background of the view around it; and only failing both does it fall back to the theme surface. Set a background on the menu (or on its view) and the panel follows it instead of appearing as a pale slab over a dark screen.