MobileMainLayout
Experimental.
MobileMainLayoutis new and marked@Deprecatedon purpose: its API may still evolve, even in patch releases. It is ready to use and to give feedback on — just pin your version if you depend on the details.
MobileMainLayout is a MainLayout tuned for phones, in the
spirit of native iOS/Android apps:
- the primary navigation is an icon (optionally + caption) bottom bar instead of a side drawer, and
- the view title is a large heading at the top of the content that scrolls away as the user scrolls down.
It is adaptive, not phone-only: the same instance keeps the familiar side drawer on wide (desktop) viewports and shows the bottom bar on phone-sized ones (decided on viewport width, so it also appears in a narrow desktop window — handy for testing). An app whose primary device is a phone still looks right on a laptop.
Because it extends MainLayout, the whole menu model carries over
unchanged: @MenuItem/@Menu icons and ordering, grouping, checkAccess
access control and getMenuText i18n all work exactly as
documented for MainLayout.
Basic usage
Extend the layout, implement the drawer header, and annotate the views as usual:
public class MobileLayout extends MobileMainLayout {
@Override
protected Object getDrawerHeader() {
return "My App";
}
}
@Route(value = "", layout = MobileLayout.class)
@MenuItem(title = "Home", icon = VaadinIcon.HOME, order = 0)
public class HomeView extends VerticalLayout { ... }
@Route(value = "profile", layout = MobileLayout.class)
@MenuItem(title = "Profile", icon = VaadinIcon.USER)
public class ProfileView extends VerticalLayout { ... }
Each top-level view becomes a bottom-bar item — a tappable icon that highlights when its route is active.
The “More” overflow and two-level groups
A bottom bar only fits a handful of icons, so at most
getMaxBottomNavItems() (five by default) are shown. When there are more
top-level views — or any grouped items, which a flat bar cannot display — the
last slot becomes a “More” action.
By default “More” opens the full drawer. A group item (a view with children
via @MenuItem(parent = ...)) instead opens a small popover above the bar
listing its children, so a two-level hierarchy stays reachable without the
drawer.
Override the overflow behaviour if you like:
@Override
protected void openOverflow() {
// e.g. open a custom dialog instead of the drawer
}
@Override
protected String getOverflowText() {
return "Lisää"; // localise the "More" label
}
Full-bleed views (edge to edge)
A view that should run under the floating bar — a map, a photo, a video — opts
out of the bottom-bar clearance by adding the EDGE_TO_EDGE style class:
public class MapView extends Div {
public MapView() {
setSizeFull();
addClassName(MobileMainLayout.EDGE_TO_EDGE);
}
}
The content then fills the screen and the translucent bar simply overlays it.
Per-view navbar actions
The addNavbarHelper(Component) pattern works here too. On a wide
screen the helper sits in the navbar; on a phone it appears in a pinned (sticky)
top app-bar that stays reachable while the content scrolls under it — the mobile
equivalent of the wide layout's top-right actions (e.g. an options “…” button
opening a popover). This works even when the scroll-away title is turned off, so a
view's actions are always available without having to enable the title — and in
that case the view title is shown implicitly, at a compact size, on the lead edge
of the same row (so it reads as a real app-bar rather than a lone floating button)
with the helpers on the trailing edge.
@Override
protected void onAttach(AttachEvent e) {
super.onAttach(e);
Button options = new Button(VaadinIcon.ELLIPSIS_DOTS_H.create());
Popover popover = new Popover();
popover.setTarget(options);
popover.add(/* the view's options controls */);
add(popover);
MainLayout.getCurrent().addNavbarHelper(options);
}
Configuration
All options have sensible defaults; set them in the layout's constructor.
| Method | Default | Effect |
|---|---|---|
setBottomNavLabelsVisible(boolean) |
false |
Show a small text caption under each bottom-bar icon. |
setViewTitleVisible(boolean) |
false |
Show the large, scroll-away view title in the content. A wide desktop always shows the title next to the drawer toggle regardless of this setting. |
setMaxBottomNavItems(int) |
5 |
How many icons before the last slot becomes “More”. |
setBottomNavHideOnScroll(boolean) |
true |
Slide the bar out of the way when scrolling down, iOS-style. |
setBodyScrolling(boolean) |
true |
Scroll the whole page (lets the mobile browser's address bar collapse and content run edge-to-edge under the status bar) instead of just the content area. |
public class MobileLayout extends MobileMainLayout {
public MobileLayout() {
setBottomNavLabelsVisible(true);
setViewTitleVisible(true);
}
@Override
protected Object getDrawerHeader() {
return "My App";
}
}
Theming
The layout is theme-agnostic — it styles itself with the shared --vaadin-*
base properties, so it looks right under both Lumo and Aura:
- Lumo renders a solid bottom bar separated from the content by a divider.
- Aura renders an iOS-style translucent “liquid glass” floating pill, with the active item marked by a soft lozenge. A subtle background tint keeps the icons legible over busy content (e.g. a map), in both light and dark mode.
Safe-area insets are respected throughout, so the bar and the scroll-away title
clear the notch / home indicator when the app's viewport uses
viewport-fit=cover.
How the adaptive switch works
Under the hood the bottom bar is Vaadin AppLayout's touch-optimized navbar.
MobileMainLayout shows it whenever the viewport is phone-sized
(max-width: 800px) and otherwise lets AppLayout show the persistent drawer —
the drawer toggle remains available in the content header on wide screens. You
generally do not need to think about this; it is mentioned only because it
explains why narrowing a desktop browser window flips the layout to the bottom
bar.
