Docs

Documentation versions (currently viewingVaadin 24)

Grid

Column alignment, freezing (i.e. fixed positioning), grouping, headers and footers, visibility, and width can be configured. Users can be allowed to resize and reorder columns.

Column Alignment

Three different column alignments are supported: left, which is the default; center; and right.

Right alignment is useful when comparing numeric values, as it helps with readability and scannability. Notice how the Amount column in the example here aligns the euro values to the right. Those values would be difficult to scan visually if they were left aligned or centered. Tabular numbers — if the font offers them — or a monospace font could be used to further improve digit alignment.

Open in a
new tab
grid.addColumn(GridColumnAlignment::generateRandomAmountText)
        .setHeader("Amount").setTextAlign(ColumnTextAlign.END);

Column Freezing

Columns and column groups can be frozen — made sticky — to exclude them from horizontally scrolling a grid. This can be useful for keeping the most important columns always visible in a grid that contains so many columns they might otherwise scroll out of view. Freezing columns at the end of the grid is useful, for example, for keeping row actions always visible.

In the example here, try scrolling the data to the right and back left. Notice that the name of each person is stationary so that you can see easily which data relates to which person as you scroll. The Edit button at the end also remains in place so that it’s available at any point while scrolling.

Open in a
new tab
grid.addColumn(createPersonRenderer()).setHeader("Name").setFrozen(true)
        .setAutoWidth(true).setFlexGrow(0);
grid.addColumn(createActionRenderer()).setFrozenToEnd(true)
        .setAutoWidth(true).setFlexGrow(0);

Although it’s technically possible to freeze any column, it should be used primarily to freeze columns at the start or end of the grid, leaving the remaining columns unfrozen.

Column Grouping

It’s possible to group columns. They share a common header and footer. Use this feature to better visualize and organize related or hierarchical data.

In the example below, the first and last names are grouped under Name. Whereas, the street address, the city, and so forth are grouped under Address.

Open in a
new tab
Grid<Person> grid = new Grid<>(Person.class, false);
Grid.Column<Person> firstNameColumn = grid
        .addColumn(Person::getFirstName).setHeader("First name");
Grid.Column<Person> lastNameColumn = grid.addColumn(Person::getLastName)
        .setHeader("Last name");
Grid.Column<Person> streetColumn = grid
        .addColumn(person -> person.getAddress().getStreet())
        .setHeader("Street");
Grid.Column<Person> cityColumn = grid
        .addColumn(person -> person.getAddress().getCity())
        .setHeader("City");
Grid.Column<Person> zipColumn = grid
        .addColumn(person -> person.getAddress().getZip())
        .setHeader("Zip");
Grid.Column<Person> stateColumn = grid
        .addColumn(person -> person.getAddress().getState())
        .setHeader("State");

HeaderRow headerRow = grid.prependHeaderRow();
headerRow.join(firstNameColumn, lastNameColumn).setText("Name");
headerRow.join(streetColumn, cityColumn, zipColumn, stateColumn)
        .setText("Address");

Column Headers & Footers

Each column has a customizable header and footer. A basic column header shows the name in plain text. Footers are empty by default and therefore hidden. However, if you add content for the footer, it becomes visible. Incidentally, both the header and footer can contain rich content and components — notice the header and footer in the example here.

Open in a
new tab
List<Person> people = DataService.getPeople();

Grid<Person> grid = new Grid<>(Person.class, false);
grid.addColumn(Person::getFullName).setHeader("Name")
        .setFooter(String.format("%s total members", people.size()));
grid.addColumn(person -> person.isSubscriber() ? "Yes" : "No")
        .setHeader(createSubscriberHeader())
        .setFooter(createSubscriberFooterText(people));
grid.addColumn(Person::getMembership)
        .setHeader(createMembershipHeader())
        .setFooter(createMembershipFooterText(people));

...

private static Component createSubscriberHeader() {
    Span span = new Span("Subscriber");
    Icon icon = VaadinIcon.INFO_CIRCLE.create();
    icon.getElement().setAttribute("title",
            "Subscribers are paying customers");
    icon.getStyle().set("height", "var(--lumo-font-size-m)").set("color",
            "var(--lumo-contrast-70pct)");

    HorizontalLayout layout = new HorizontalLayout(span, icon);
    layout.setAlignItems(FlexComponent.Alignment.CENTER);
    layout.setSpacing(false);

    return layout;
}

private static String createSubscriberFooterText(List<Person> people) {
    long subscriberCount = people.stream().filter(Person::isSubscriber)
            .count();

    return String.format("%s subscribers", subscriberCount);
}

private static Component createMembershipHeader() {
    Span span = new Span("Membership");
    Icon icon = VaadinIcon.INFO_CIRCLE.create();
    icon.getElement().setAttribute("title",
            "Membership levels determines which features a client has access to");
    icon.getStyle().set("height", "var(--lumo-font-size-m)").set("color",
            "var(--lumo-contrast-70pct)");

    HorizontalLayout layout = new HorizontalLayout(span, icon);
    layout.setAlignItems(FlexComponent.Alignment.CENTER);
    layout.setSpacing(false);

    return layout;
}

private static String createMembershipFooterText(List<Person> people) {
    long regularCount = people.stream()
            .filter(person -> "Regular".equals(person.getMembership()))
            .count();
    long premiumCount = people.stream()
            .filter(person -> "Premium".equals(person.getMembership()))
            .count();
    long vipCount = people.stream()
            .filter(person -> "VIP".equals(person.getMembership())).count();

    return String.format("%s regular, %s premium, %s VIP", regularCount,
            premiumCount, vipCount);
}

Column Visibility

When you want, columns and column groups can be hidden. You can provide the user with a menu for toggling column visibilities, for example, using Menu Bar. Allowing the user to hide columns is useful when only a subset of the columns is relevant to their task, and if there are plenty of columns.

In the example here, notice that the email addresses and telephone numbers are not fully visible because the data doesn’t wrap and won’t fit in the width provided. Now click on the Show/Hide Columns button to see a menu of choices to reduce the number of columns. Notice that all columns are checked. De-select the First Name and then the Profession column. That should allow the email addresses and telephone numbers to be fully visible. Incidentally, if you don’t deselect any columns, you can still right-click on an email address to copy it: You’ll still get the whole address, even if it’s not visible.

Open in a
new tab
Grid<Person> grid = new Grid<>(Person.class, false);
Grid.Column<Person> firstNameColumn = grid
        .addColumn(Person::getFirstName).setHeader("First name");
Grid.Column<Person> lastNameColumn = grid.addColumn(Person::getLastName)
        .setHeader("Last name");
Grid.Column<Person> emailColumn = grid.addColumn(Person::getEmail)
        .setHeader("Email");
Grid.Column<Person> phoneColumn = grid
        .addColumn(person -> person.getAddress().getPhone())
        .setHeader("Phone");
Grid.Column<Person> professionColumn = grid
        .addColumn(Person::getProfession).setHeader("Profession");

Button menuButton = new Button("Show/Hide Columns");
menuButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
ColumnToggleContextMenu columnToggleContextMenu = new ColumnToggleContextMenu(
        menuButton);
columnToggleContextMenu.addColumnToggleItem("First name",
        firstNameColumn);
columnToggleContextMenu.addColumnToggleItem("Last name",
        lastNameColumn);
columnToggleContextMenu.addColumnToggleItem("Email", emailColumn);
columnToggleContextMenu.addColumnToggleItem("Phone", phoneColumn);
columnToggleContextMenu.addColumnToggleItem("Profession",
        professionColumn);

...

private static class ColumnToggleContextMenu extends ContextMenu {
    public ColumnToggleContextMenu(Component target) {
        super(target);
        setOpenOnClick(true);
    }

    void addColumnToggleItem(String label, Grid.Column<Person> column) {
        MenuItem menuItem = this.addItem(label, e -> {
            column.setVisible(e.getSource().isChecked());
        });
        menuItem.setCheckable(true);
        menuItem.setChecked(column.isVisible());
        menuItem.setKeepOpen(true);
    }
}

Column Reordering & Resizing

Enabling the user to reorder columns is useful when they want to compare data that isn’t adjacent by default. Grouped columns can only be reordered within their group. Resizing is helpful when a column’s content doesn’t fit and is cut off or varies in length.

Instead of hiding columns as in the earlier example, in the example here the user can resize a truncated column to be able to read it, fully. Try doing that. Hover your mouse pointer over the space on the header row between the Street and City columns, until the mouse pointer changes to a resizing tool. Then, while holding the left mouse button, pull on the right edge of the Street column until you can see all of the street addresses.

Open in a
new tab
Grid<Person> grid = new Grid<>(Person.class, false);
grid.setColumnReorderingAllowed(true);
Grid.Column<Person> firstNameColumn = grid
        .addColumn(Person::getFirstName).setHeader("First name")
        .setResizable(true);
Grid.Column<Person> lastNameColumn = grid.addColumn(Person::getLastName)
        .setHeader("Last name").setResizable(true);
Grid.Column<Person> streetColumn = grid
        .addColumn(person -> person.getAddress().getStreet())
        .setHeader("Street").setResizable(true);
Grid.Column<Person> cityColumn = grid
        .addColumn(person -> person.getAddress().getCity())
        .setHeader("City").setResizable(true);
Grid.Column<Person> zipColumn = grid
        .addColumn(person -> person.getAddress().getZip())
        .setHeader("Zip").setResizable(true);
Grid.Column<Person> stateColumn = grid
        .addColumn(person -> person.getAddress().getState())
        .setHeader("State").setResizable(true);

HeaderRow headerRow = grid.prependHeaderRow();
headerRow.join(firstNameColumn, lastNameColumn).setText("Name");
headerRow.join(streetColumn, cityColumn, zipColumn, stateColumn)
        .setText("Address");

Column Width

Unless specified, all columns are the same width. You can set a specific width for any column, though, or allow the Grid to set the width based on the content. Column widths can be fixed or non-fixed, which is the default. Fixed-width columns don’t grow or shrink as the available space changes, while non-fixed-width columns do.

Note
The em length unit is a relative setting for font sizes. Don’t use it for column widths. If the font sizes of the header, body or footer cells differ, may cause column widths to be inconsistent, leading to misaligned content. When length relative to the font size is needed, use instead the rem length unit — or make sure that the font size is consistent across the cells.

In the following example, the first and last columns have fixed widths. The second column’s width is set to be based on the content, while the third consumes the remaining space.

Open in a
new tab
Grid<Person> grid = new Grid<>(Person.class, false);
grid.setSelectionMode(Grid.SelectionMode.MULTI);
grid.addColumn(Person::getFirstName).setHeader("First name")
        .setWidth("7rem").setFlexGrow(0);
grid.addColumn(Person::getProfession).setHeader("Profession")
        .setAutoWidth(true).setFlexGrow(0);
grid.addColumn(Person::getEmail).setHeader("Email");
grid.addColumn(person -> person.isSubscriber() ? "Yes" : "No")
        .setHeader("Has Sub").setWidth("6rem").setFlexGrow(0);