Docs

Documentation versions (currently viewingVaadin 24)

Add a Router Layout

This guide teaches you how to create a router layout in Flow, apply it to views automatically and explicitly, and work with nested layouts. A hands-on mini-tutorial at the end will help you put these concepts into practice.

Router Layouts in Flow

In Flow, router layouts are UI components that implement the RouterLayout interface, which provides two key methods:

  • showRouterLayoutContent(HasElement) — shows the given view in the router layout.

  • removeRouterLayoutContent(HasElement) — removes the given view in the router layout.

When you navigate to a view, the router first determines which layout to use — if any. If you’re navigating from one view to another inside the same router layout, the existing router layout instance is reused. Otherwise, a new instance is created. The router then calls showRouterLayoutContent(), passing in the new view instance.

Automatic Layouts

To create an automatic layout, add the @Layout annotation to a router layout. This layout is automatically applied to all views unless explicitly disabled.

The following example applies MainLayout to all views:

@Layout
public class MainLayout extends AppLayout { 1
    ...
}
  1. AppLayout is a built-in router layout. See its documentation page for more details.

Opting Out

Sometimes, you may want to exclude specific views from the automatic layout. For example, displaying a login view within an application layout might not be appropriate.

To prevent a view from using the automatic layout, set the autoLayout attribute of the @Route annotation to false:

@Route(value = "login", autoLayout = false)
public class LoginView extends Main {
    ...
}
Note
@RouteAlias also has the autoLayout attribute. You can disable or enable the automatic layout for a single view depending on the route used to access it.

Scoping to a Path

You can restrict an automatic layout to views with routes that start with a specific path.

The following example applies AdminLayout only to views with routes starting with /admin:

@Layout("/admin")
public class AdminLayout extends AppLayout {
    ...
}

If a route matches multiple layouts, the layout with the longest matching path takes precedence. For example, given a main layout scoped to / (default) and an admin layout scoped to /admin, the layouts apply as follows:

  • / → rendered inside the main layout.

  • /customers → rendered inside the main layout.

  • /admin/users → rendered inside the admin layout

  • /admin/groups → rendered inside the admin layout.

Important
Defining multiple layouts with the exact same path will result in an exception.

Explicit Layouts

You can declare a view to use a specific router layout using the layout attribute of the @Route annotation:

@Route(layout = MyLayout.class)
public class DefinedLayoutView extends Main {
    ...
}

Declaring a layout explicitly also disables the automatic layout for the view.

Note
@RouteAlias also has the layout attribute. You can render the same view in different layouts depending on the route used to access it.

Nested Layouts

Automatic layouts do not apply to other layouts. By default, a router layout is rendered directly in the browser tab. To render a router layout inside another router layout, use the @ParentLayout annotation:

@ParentLayout(MainLayout.class)
public class NestedLayout extends Div implements RouterLayout {
    ...
}

Path Prefixes

By default, router layouts do not affect the routes of the views that they are applied to. You can change this with the @RoutePrefix annotation, which adds a prefix to all its routes.

In the following example, MyView receives the some prefix from its router layout, resulting in some/path being its actual path:

@Route(value = "path", layout = MyLayout.class)
public class MyView extends Main {
    ...
}

@RoutePrefix("some")
public class MyLayout extends Div implements RouterLayout {
    ...
}

Opting Out

A view can opt out from a route prefix by setting the absolute attribute of @Route to true.

In the following example, the path of MyView is path, ignoring the prefix coming from MyLayout:

@Route(value = "path", layout = MyLayout.class, absolute = true)
public class MyView extends Main {
    ...
}

@RoutePrefix("some")
public class MyLayout extends Div implements RouterLayout {
    ...
}
Note
@RouteAlias also has the absolute attribute.

Nested router layouts can also opt out from route prefixes.

In the following example, the path of MyView is in fact nested/path, as opposed to some/nested/path:

@Route(value = "path", layout = MyNestedLayout.class)
public class MyView extends Main {
    ...
}

@RoutePrefix(value = "nested", absolute = true)
@ParentLayout(MyLayout.class)
public class MyNestedLayout extends Div implements RouterLayout {
    ...
}

@RoutePrefix("some")
public class MyLayout extends Div implements RouterLayout {
    ...
}

Try It

In this mini-tutorial, you’ll explore router layouts using the Vaadin walking skeleton. You’ll then create a nested layout and experiment with different ways to apply it to views.

Set Up the Project

First, generate a walking skeleton with a Flow UI, open it in your IDE, and run it with hotswap enabled.

Explore the Main Layout

The skeleton already contains a main layout. Instead of implementing one from scratch, you’re going to have a look at it. Open [application package].base.ui.view.MainLayout in your IDE.

The main layout is based on App Layout:

@Layout
public final class MainLayout extends AppLayout {

    public MainLayout() {
        setPrimarySection(Section.DRAWER);
        addToDrawer(createHeader(), new Scroller(createSideNav()), createUserMenu());
    }
    ...
}

It has a drawer on the left side with the following elements: an application header, a navigation menu, and a user menu. All the elements are styled using Lumo Utility Classes.

The Header

The header is created by the createHeader() method. It contains the application’s name and logo:

private Div createHeader() {
    // TODO Replace with real application logo and name
    var appLogo = VaadinIcon.CUBES.create();
    appLogo.addClassNames(TextColor.PRIMARY, IconSize.LARGE);

    var appName = new Span("Walking Skeleton");
    appName.addClassNames(FontWeight.SEMIBOLD, FontSize.LARGE);

    var header = new Div(appLogo, appName);
    header.addClassNames(Display.FLEX, Padding.MEDIUM, Gap.MEDIUM, AlignItems.CENTER);
    return header;
}

Now, change the name and the logo. Use an icon from the default icons.

The Navigation Menu

The navigation menu is created by the createSideNav() method. It includes all views — both Flow and React — that have declared a menu item:

private SideNav createSideNav() {
    var nav = new SideNav();
    nav.addClassNames(Margin.Horizontal.MEDIUM);
    MenuConfiguration.getMenuEntries().forEach(entry -> 1
        nav.addItem(createSideNavItem(entry)));
    return nav;
}

private SideNavItem createSideNavItem(MenuEntry menuEntry) {
    if (menuEntry.icon() != null) { 2
        return new SideNavItem(menuEntry.title(), menuEntry.path(),
            new Icon(menuEntry.icon())); 3
    } else {
       return new SideNavItem(menuEntry.title(), menuEntry.path());
    }
}
  1. MenuConfiguration gives access to all registered view menu items.

  2. This navigation menu assumes that all menu items have a title, but only some may have an icon. If you know all your menu items have icons, you can simplify this method.

  3. This navigation menu assumes that the icon attribute contains the name of an Icon.

The User Menu

The user menu is created by the createUserMenu() method. It is the only part of the router layout that is a stub:

private Component createUserMenu() {
    // TODO Replace with real user information and actions
    var avatar = new Avatar("John Smith");
    avatar.addThemeVariants(AvatarVariant.LUMO_XSMALL);
    avatar.addClassNames(Margin.Right.SMALL);
    avatar.setColorIndex(5);

    var userMenu = new MenuBar();
    userMenu.addThemeVariants(MenuBarVariant.LUMO_TERTIARY_INLINE);
    userMenu.addClassNames(Margin.MEDIUM);

    var userMenuItem = userMenu.addItem(avatar);
    userMenuItem.add("John Smith");
    userMenuItem.getSubMenu().addItem("View Profile");
    userMenuItem.getSubMenu().addItem("Manage Settings");
    userMenuItem.getSubMenu().addItem("Logout");

    return userMenu;
}

The Security guides show you how to add real functionality to the user menu.

Create a Nested Layout

Create a new Java package [application package].tutorial.ui.view. Inside this package, create a new class called NestedLayout, like this:

import com.example.application.base.ui.view.MainLayout; 1
import com.vaadin.flow.component.HasElement;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.router.*;
import com.vaadin.flow.theme.lumo.LumoUtility;

@ParentLayout(MainLayout.class) 2
public class NestedLayout extends Div implements RouterLayout {

    private final Div content;

    public NestedLayout() {
        addClassNames(LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN,
                LumoUtility.Gap.SMALL, LumoUtility.Padding.MEDIUM,
                LumoUtility.BoxSizing.BORDER, LumoUtility.Height.FULL);
        content = new Div();
        content.addClassNames(LumoUtility.Border.ALL, LumoUtility.Background.BASE);
        content.setSizeFull();
        add(new Div("This is a layout: " + this), content); 3
    }

    @Override
    public void showRouterLayoutContent(HasElement content) { 4
        this.content.getElement().appendChild(content.getElement());
    }
}
  1. Replace with real package.

  2. This renders the nested layout inside the main layout.

  3. Prints this on the screen so that you can see when the layout instance changes.

  4. This renders views inside the content element.

You can’t see what your new layout looks like yet, because you don’t have any views that use it. You’ll fix that next.

Create Example Views

You’ll now create two views that contain links to each other and both use the new nested layout. Inside the [application package].tutorial.ui.view package, create two new classes; FirstView and SecondView:

import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.router.Menu;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.router.RouterLink;

@Route(layout = NestedLayout.class)
@Menu
public class FirstView extends Main {

    public FirstView() {
        add(new H2("First View"),
            new RouterLink("Second View", SecondView.class));
    }
}
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.router.Menu;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.router.RouterLink;

@Route(layout = NestedLayout.class)
@Menu
public class SecondView extends Main {

    public SecondView() {
        add(new H2("Second View"),
            new RouterLink("First View", FirstView.class));
    }
}
Test the Application

Restart the application. Open your browser and navigate to: http://localhost:8080/first

You should see the first view rendered inside the nested layout.

Navigate back and forth between the first view and the second view. You should see that the nested layout instance remains unchanged.

Now click on Task List in the navigation menu, then on FirstView. You should see that the nested layout instance has now changed.

Add a Route Prefix

Add a layout route prefix to the nested layout:

@ParentLayout(MainLayout.class)
@RoutePrefix("layout")
public class NestedLayout extends Div implements RouterLayout {
    ...
}

Restart the application. Notice that the paths of first view and second view have now changed to layout/first and layout/second, respectively.

Enable Automatic Layout

Up to this point, the nested layout has been explicitly applied to the first view and second view. You’ll now change this so that it is applied automatically, while keeping the application’s behavior unchanged.

Start by removing the reference to NestedLayout from first view and second view, and add the layout prefix to each route:

@Route("layout/first")
@Menu
public class FirstView extends Main {
    ...
}
@Route("layout/second")
@Menu
public class SecondView extends Main {
    ...
}

Next, change the NestedLayout to be automatically applied to all paths that start with /layout. Remove the @RoutePrefix annotation and add the @Layout annotation:

@ParentLayout(MainLayout.class)
@Layout("/layout")
public class NestedLayout extends Div implements RouterLayout {
    ...
}

Restart the application and click around. It should behave the same way as before.

Final Thoughts

You’ve now learned how to:

  • Create a custom router layout.

  • Apply layouts explicitly and automatically.

  • Use path prefixes in router layouts.

Now, try experimenting with route aliases and absolute routes!