Skip to content

Structure of an app

Every Mateu app has the same nested structure. Knowing it tells you where any piece of UI lives, which declaration controls it, and why it looks consistent on every renderer.

flowchart TD
Mount["Mount (type: UI · @UI)"] --> Shell["App shell (type: AppShell · @App)"]
Shell --> AH["App header"]
Shell --> Nav["Navigation (menu)"]
Shell --> CA["Content area"]
Shell --> AF["App-level floating buttons<br/>AI chat · command center · app FABs"]
CA --> Page["Page"]
Page --> PH["Page header"]
Page --> PB["Banners"]
Page --> Body["Body"]
Page --> PF["Footer buttons"]
Page --> FAB["Page FABs"]
Page -.-> OV["Overlays: dialog · drawer · popover · toast"]

This is the canonical arrangement on a wide web viewport with the menu on top. Other variants move the navigation (see shell variants), and the native renderers adapt it to their platform (a drawer on a phone, tool windows in IntelliJ), but the parts are the same.

┌──────────────────────────────────────────────────────────────────────────────────────┐
│ [1] Logo · Title [2] Products Orders ▾ Reports [3] Hotel ▾ Actions ▾ 🔔 ☾ │ ← app header
├──────────────────────────────────────────────────────────────────────────────────────┤
│ [4] Home › Orders │
│ [5] SALES [9] ‹ › [10] Edit Export … │
│ [6] Order SO-1042 [7] Open Priority │ ← page header
│ Placed by ACME Corp · Last updated 2 min ago [8] Total 1,240 € Lines 12 │
├──────────────────────────────────────────────────────────────────────────────────────┤
│ [11] ⚠ Payment pending: the customer has not paid the deposit yet. ✕ │ ← banners
├──────────────────────────────────────────────────────────────────────────────────────┤
│ [12] ┌ Customer ───────────────┐ ┌ Shipping ────────────────┐ │
│ │ Name ACME Corp │ │ Address … │ │ ← body
│ └─────────────────────────┘ └──────────────────────────┘ │
│ ┌ Lines ───────────────────────────────────────────────┐ │
│ │ Product Qty Price │ │
│ └──────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────────────────────────────┤
│ [13] Cancel Save │ ← footer buttons
│ [14] (+) (✦) │ ← FABs
└──────────────────────────────────────────────────────────────────────────────────────┘
[15] overlays open above all of this: dialog · drawer · popover · toast
#PartWhat it isDeclared with
1App identityTitle, subtitle, logo, favicontitle: / logo: on type: AppShell · @Title, @Logo, @FavIcon on the @UI class
2NavigationThe menu, drawn by the shell variantmenu: · @Menu fields, MenuSupplier
3Header widgetsContext pickers, header actions, notification bell, theme toggle, live valuessee the app header
4BreadcrumbsWhere the page sitsBreadcrumbsSupplier · breadcrumbs
5OverlineA quiet line above the title@Overline / OverlineSupplier
6TitleThe page title (and a placeholder while it is empty)title: · @Title / TitleSupplier, @TitlePlaceholder
7BadgesStatus chips beside the title@BadgeInHeader fields / BadgeSupplier
8Subtitle, timestamp, KPIsContext under the title; key/value factssubtitle: · @Subtitle, @Timestamp, @KPI fields
9Peer navigation, record switcherPrevious / next record; jump to any recordPeerNavigationSupplier, RecordSwitcherSupplier
10ToolbarThe page’s actions; secondary ones collapse into ”…”toolbar: · @Toolbar methods, ToolbarSupplier
11BannersMessages above the body: info, success, warning, danger@Banner / BannerSupplier, or returned by an action
12BodyThe components of the page: sections, zones, tabs, listings…content: · the view model’s fields, or a component tree
13Footer buttonsThe page’s closing actionsbuttons: · @Button methods, ButtonsSupplier
14FABsFloating action buttons, per page or for the whole app@Fab methods
15OverlaysDialogs, drawers, popovers, toastsreturned by actions; see overlays

The page-header parts 5–9 and the banners are page metadata, declared in code today; a YAML Form or Listing has title, subtitle, toolbar, header, buttons and footer. The header grammar itself (one primary action, secondary actions in ”…”, a leading EntityHeader hoisted into the header) is described in page templates.

A mount is an app served at a base path (/, /back-office). It names its home page and the route files that make it up. Several mounts can live in one project, or be federated into one console. A mount has no visual presence of its own: what you see is its shell. See mounts and routes.

The app shell is the chrome around every page of a mount: identity, header, navigation and the content area. It is not a page: its content area shows the current route’s page.

The variant decides how navigation is drawn:

VariantWhat it looks likeGood for
MENU_ON_TOPa horizontal bar; groups open as dropdownsa standard back office with two levels
HAMBURGER_MENUa button opening the whole menu in a drawermany top-level entries; small screens
HAMBURGER_SECTIONSthe hamburger holds the sections, a band under the header holds the current section’s entrieslarge suites organised in sections
MENU_ON_LEFTa persistent, collapsible sidebarkeeping the whole tree visible while working
RAILa compact icon rail on the leftfrequent switching between a few areas
TABSa tab bara few peer sections with no hierarchy
TILESflat top-level entries; an entry with children opens a hub of tilesdeep menus
AUTO (the default)chosen from the menu’s shape, see belownot deciding

How AUTO picks, in order:

  1. the menu federates a RemoteMenu → MENU_ON_TOP;
  2. a group contains another group (three levels or more) → TILES;
  3. the menu has groups and more than seven top-level entries → HAMBURGER_MENU;
  4. the menu has groups → MENU_ON_TOP;
  5. the menu has no groups at all, however long → TABS.

An explicit variant always wins. (MEDIATOR is internal: the chrome-less shell a CRUD uses inside the content area.)

Two shell flags change the chrome itself:

  • Command center (commandCenter: true · @App(commandCenter = true)): a floating button that opens a full-screen palette unifying navigation, entity search, recent screens and the AI hand-off. See command center.
  • Chromeless (chromeless: true · @App(chromeless = true)): drops the navigation chrome entirely; the command center becomes the way around. Implies the command center.

The content area can also be split in two panes (@App(layout = AppLayout.SPLIT)), and the accentColor/accentStrip attributes brand the shell (see accent colour and strip).

# specs/ui/app.yaml, bound to the mount root ("") in routes.yaml
type: AppShell
title: Catalogue
variant: AUTO
themeToggle: true
commandCenter: true
menu:
- {type: RouteLink, label: Products, route: products}
- type: Menu
label: Reports
submenu:
- {type: RouteLink, label: Sales, route: reports/sales}

More: App shell and menus (the concept), App shell as data (every YAML key), @App (every attribute) and navigation patterns (each variant in detail).

The right side of the app header hosts app-wide widgets. Each one appears only when the app declares it:

WidgetWhat it doesDeclared withPattern page
Context pickersFix a value for every screen: active hotel, company, fiscal year@AppContext field on the app classApp context
Header actionsButtons and dropdowns next to the pickersAppActionsSupplierApp context › header actions
Notification bellInbox with unread count and mark-as-readNotificationsSupplierNotification inbox
Global search⌘K palette searching the menu and your dataGlobalSearchSupplierGlobal search
Theme toggleLight / darkthemeToggle@App
Live header valuesValues pushed from the server (a counter, a clock)headerValues: · HeaderValuesSupplierLive header values
WidgetsAny component (a version tag, a status badge)widgets:App shell as data
Open on mobileA QR code that opens the current screen in the Mateu appmobileQrOpen on mobile

App-wide behaviours that are not widgets: app shortcuts and quick launch (@AppShortcut, shortcuts:), access keys (accessKeys), the idle-session warning and the embedded AI chat (@AI, a floating button at the bottom right).

A menu is a list of entries. Every leaf either routes somewhere or runs something:

EntryWhat a click does
RouteLinknavigates to a route of the mount (or a full URL)
RuleLinkruns an action instead of navigating
Menua group of entries, nestable
RemoteMenuthe menu of another Mateu service, fetched at runtime

Two presentations change how a group opens:

  • Card menus (@Menu(display = MenuDisplay.cards), or presentation: {display: cards}): the group opens as a panel of cards, each with a description and an image or icon. See card menus.
  • Remote menus: a shell composes the menus of independent services, so several teams’ UIs appear as one console. See shell and remote menus.

Entries the caller may not open are not sent at all (a RouteLink inherits its route’s access rules). The home page is the shell’s homeRoute, else the mount’s home:, else the first navigable entry. More: navigation and menus.

The content area shows the page of the current route. Navigating replaces it; the shell stays. Its width follows the page, not the shell: each page is fixed (capped at 1408 px and centred), full width (fluid with side gutters) or edge to edge (no margins). See page width.

Inside a page, parts of the screen can be independent islands: an embedded view or micro-frontend with its own state and actions, which re-renders on its own. See component communication.

A page has five regions. Most pages use some of them; a page type is largely a convention about which regions it fills and how.

  1. Header: breadcrumbs, overline, title, badges, subtitle, timestamp, KPIs, peer navigation, record switcher and toolbar. A page can also open with a welcome banner (@WelcomeBanner), a hero above the content. When a page’s content starts with an EntityHeader, the renderer hoists it into this header.
  2. Banners: page messages between the header and the body, static (@Banner, BannerSupplier) or returned by an action (PageBanner), optionally closeable or timed. For a message inside the body, use a notice.
  3. Body: the components. In code, fields group into @Section cards, which can sit side by side in @Zones, become @Tabs, sit side by side with @FoldedLayout, get a sticky index with @Toc, or place one panel aside with @Aside. A ContentLayout gives the main + aside + footer grammar to any page.
  4. Footer buttons: the closing actions (@Button methods, buttons: in YAML).
  5. FABs: floating action buttons (@Fab), scoped to the page or, on the app class, to the whole app. They stack above the AI and command-center buttons.

KPIs in the page header

Banners between the page header and the body

Two modifiers change the whole page: @Compact for dense operational screens, and @AutoSave for screens without a save button.

An overlay opens above the page without leaving it. An action returns it, and UICommand.closeModal() closes the topmost one (optionally emitting an event the page reacts to).

OverlayUse it forPattern page
Dialoga short, focused interaction that must be answeredDialog
ConfirmDialogconfirming a destructive action (@Action(confirmationRequired = true) builds one for you)Confirm dialog
Drawer (side)editing or reading more about something without losing the page; CRUD create/edit with editInDrawer()Drawer
Drawer (bottom)a docked, collapsible panel under a canvasBottom drawer
Popoverdetails on hover or clickHover details
ActionPanela categorised “I want to…” menu of actionsAction panel
Toastsmessages returned by actions, including undoable onesUndo

Overlays trap focus and give it back when they close; see accessibility.