Documentation versions (currently viewingVaadin 23)
New Acceleration Kits: Observability Kit, SSO Kit, and Swing Kit. Read the blog post.


Binding to Data

By default, Grid is bound to a List of items. You can use the setItems() method to set the items.

Example: Showing a list of beans in a Grid.

// Have some data
List<Person> people = Arrays.asList(
        new Person("Nicolaus Copernicus", 1543),
        new Person("Galileo Galilei", 1564),
        new Person("Johannes Kepler", 1571));

// Create a grid bound to the list
Grid<Person> grid = new Grid<>();
        .setHeader("Year of birth");


Behind the scenes, Grid uses the DataProvider interface to communicate with the backend. The setItems() method is a shorthand to create a ListDataProvider. For a large amount of data or other advanced use cases, you should probably use the DataProvider interface and lazy loading. See Data Providers for more.

To function, components such as the Grid and ComboBox require that the assigned items have identifiers that are stable and unique. This usually requires a proper implementation of the hashCode() and equals() methods as described in the section.

Handling Selection Changes

The Grid doesn’t implement the HasValue interface directly. Other selection components do typically implement this interface. For this reason, selection handling in the Grid is different from typical selection components. The Grid supports three selection options: single selection, multiple selection, and no selection. Each option is defined by a specific selection model.

For basic switching between selection models, you can use the setSelectionMode(SelectionMode) method. Possible options are SINGLE (default), MULTI, or NONE.

To access the selection API or to use Grid as an input field with Binder, you can use asSingleSelect() or asMultiSelect(), depending on the currently defined selection mode. Both the SingleSelect and MultiSelect interfaces implement the HasValue interface. In the MultiSelect interface, the value type is a Set of the item type.

Example: Using the HasValue interface with single and multi-select mode.

Grid<Person> grid = new Grid<>();

SingleSelect<Grid<Person>, Person> personSelect =
// personSelect can now be used with Binder or
// HasValue interface
personSelect.addValueChangeListener(e -> {
    Person selectedPerson = e.getValue();

MultiSelect<Grid<Person>, Person> multiSelect =
multiSelect.addValueChangeListener(e -> {
    Set<Person> selectedPersons = e.getValue();

Alternatively, you can use a grid-specific selection API. To get the selected value or values in any selection model, you can use a SelectionListener, with the provided generic SelectionEvent, to get the selected value or values.

Example: Using addSelectionListener() to get all selected items.

Grid<Person> grid = new Grid<>();

// switch to multiselect mode

grid.addSelectionListener(event -> {
   Set<Person> selected = event.getAllSelectedItems();
   message.setText(selected.size() + " items selected");
The listener is attached to the selection model and not the grid. It stops getting events when the selection mode is changed.

You can use the select(T) method to programmatically select values. In multi-selection mode, this adds the given item to the selection.

Example: Using the select(T) method.

// in single-select, only one item is selected;

// switch to multi select, clears selection
// Select items 2-4
people.subList(2, 3).forEach(grid::select);

You can get the current selection from the Grid using the getSelectedItems() method. The returned Set contains one item in single-selection mode, or several items in multi-selection mode.


If you change the grid’s selection mode, it clears the selection and fires a selection event. To keep the previous selection, reset the selection afterwards, using the select() method.


If you change the grid’s items with either setItems() or the used DataProvider, it clears the selection and fires a selection event. To maintain the previous selection, reset the selection afterwards, using the select() method.

Selection Models

You can access the used selection model using the getSelectionModel() method. The return type is the GridSelectionModel, which has a generic selection model API, but you can cast that to the specific selection model type, typically either SingleSelectionModel or MultiSelectionModel.

You can also get the selection model using the setSelectionMode(SelectionMode) method.

Example: Using the setSelectionMode(SelectionMode) method to get the selection model.

// the default selection model
GridSingleSelectionModel<Person> defaultModel =
    (GridSingleSelectionModel<Person>) grid

// Use multi-selection mode
GridMultiSelectionModel<Person> selectionModel =
    (GridMultiSelectionModel<Person>) grid

Single-Selection Model

Obtaining a reference to the SingleSelectionModel allows you access to a fine-grained API for the single-selection use case.

You can use the addSingleSelect(SingleSelectionListener) method to access SingleSelectionEvent, which includes additional convenience methods and API options.

In single-selection mode, it’s possible to control whether the empty (null) selection is allowed. This is enabled by default.

Example: Disallowing empty (null) selection using the setDeselectAllowed() method.

// preselect value;

GridSingleSelectionModel<Person> singleSelect =
    (GridSingleSelectionModel<Person>) grid

// disallow empty selection

Multi-Selection Model

In multi-selection mode, a user can select multiple items by selecting checkboxes in the left column.

Obtaining a reference to the MultiSelectionModel allows you access to a fine-grained API for the multi-selection use case.

You can use the addMultiSelectionListener(MultiSelectionListener) method to access MultiSelectionEvent, which includes additional convenience methods and API options.

Example: Using the addMultiSelectionListener() method to access selection changes.

// Grid in multi-selection mode
Grid<Person> grid = new Grid<>();
GridMultiSelectionModel<Person> selectionModel =
    (GridMultiSelectionModel<Person>) grid


selectionModel.addMultiSelectionListener(event -> {
            "%s items added, %s removed.",

    // Allow deleting only if there's any selected

Handling Item-Click Events

It’s possible to handle item-click or double-click events, in addition to handling selection events. These can be used with selection events or on their own.

Example: Disabling the selection mode using SelectionMode.NONE, but still getting item-click events.

grid.addItemClickListener(event -> System.out
        .println(("Clicked Item: " + event.getItem())));
  • The clicked item, together with other information about the click, is available via the event.

  • Selection events are no longer available, and no visual selection is displayed when a row is clicked.

It’s possible to get separate selection and click events.

Example: Using Grid in multi-selection mode with an added click (or double-click) listener.

grid.addItemDoubleClickListener(event ->
  • In the example code, the local copy() method is called with the currently selected items when the user double-clicks a row.

Configuring Columns

The addColumn() method allows you to add columns to the Grid.

The column configuration is defined in Grid.Column objects that are returned by the addColumn() method. The getColumns() method returns a list of currently configured columns.

The setter methods in Column have fluent-API functionality, making it easy to chain configuration calls for columns.

Example: Chaining column configuration calls.

Column<Person> nameColumn = grid

Column Keys

You can set an identifier key for a column using the setKey() method. This allows you to retrieve the column from the grid at any time.

Example: Using the setKey() method to set an identifier key for a column.


Automatically Adding Columns

You can configure Grid to automatically add columns for every property in a bean by passing the class of the bean type to the grid’s constructor. The property names are set as the column keys, and you can use them to further configure the columns.

Example: Automatically adding columns by passing the bean-type class to the constructor.

Grid<Person> grid = new Grid<>(Person.class);
  • This constructor only adds columns for the direct properties of the bean type

  • The values are displayed as strings.

You can add columns for nested properties by using the dot notation with the setColumn(String) method.

Example: Adding a column for postalCode. Assumes Person has a reference to an Address object that has a postalCode property.

  • The column’s key is "address.postalCode" and its header is "Postal Code".

  • To use these String properties in addColumn(), you need to use the Grid constructor, which takes a bean-class parameter.

Defining and Ordering Automatically Added Columns

You can define which columns display, and the order in which they display, in the grid, using the setColumns() method.

Example: Defining columns and their order using the setColumns() method.

Grid<Person> grid = new Grid<>(Person.class);
grid.setColumns("name", "age", "address.postalCode");
You can also use the setColumns() method to reorder the columns you already have.
When calling setColumns(), all columns that are currently present in the grid are removed, and only those passed as parameters are added.

To add custom columns before the auto-generated columns, use the addColumns() method instead. You can avoid creating the auto-generated columns using the Grid(Class, boolean) constructor.

Example: Adding custom columns.

Grid<Person> grid = new Grid<>(Person.class, false);
grid.addColumn(person -> person.getName().split(" ")[0])
    .setHeader("First name");
grid.addColumns("age", "address.postalCode");
An IllegalArgumentException is thrown if you try to add columns that are already present the grid.

Sortable Automatic Columns

By default, all property-based columns are sortable, if the property type implements Comparable.

Many data types, such as String, Number, primitive types and Date/LocalDate/LocalDateTime are Comparable, and therefore also sortable, by default.

To make the column of a non-comparable property type sortable, you need to define a custom Comparator. See Column Sorting for more.

You can disable sorting for a specific column using the setSortable() method.

Example: Disabling sorting on the address.postalCode column.


You can also define a list of columns as sortable using the setSortableColumns() method. This makes all other columns not sortable.

Example: Setting defined columns as sortable.

// All columns except "name" and "yearOfBirth"
// are not sortable
grid.setSortableColumns("name", "yearOfBirth");

Column Headers and Footers

By default, columns don’t have a header or footer. These need to be set explicitly using the setHeader() and setFooter() methods. Both methods have two overloads; one accepts a plain text string and the other a LitRenderer.

Examples: Setting headers and footers.

// Sets a simple text header
// Sets a header using Html component,
// in this case bolding the caption "Name"
nameColumn.setHeader(new Html("<b>Name</b>"));

// Similarly for the footer
nameColumn.setFooter(new Html("<b>Name</b>"));

See Using Lit Renderers for more.

Column Reordering

Column reordering isn’t enabled by default. You can use the setColumnReorderingAllowed() method to allow column reordering by dragging.

Example: Enabling column reordering.


Hiding Columns

Columns can be hidden by calling the setVisible() method in Column.

A hidden column still sends the data required to render it to the client side. Best practice is to remove (or not add) columns if the data isn’t needed on the client side. This reduces the amount of data sent and lessens the load on the client.

Removing Columns

You can remove a single column using the removeColumn(Column) and removeColumnByKey(String) methods. You can also remove all currently configured columns using the removeAllColumns() method.

Setting Column Widths

By default, columns don’t have a defined width. They resize automatically based on the data displayed.

You can set the column width:

  • relatively, using flex grow ratios, by using the setFlexGrow() method, or

  • explicitly, using a CSS string value with setWidth() (with flex grow set to 0).

You can also enable user column resizing using the setResizable() method. The column is resized by dragging the column divider.

Setting Frozen Columns

You can freeze columns using the setFrozen() method. This ensures that the set number of columns on the left remain static (and visible) when the user scrolls horizontally.

When columns are frozen, user reordering is limited to the frozen columns.

Example: Setting a column as frozen.


Grouping Columns

You can group multiple columns together by adding them in the HeaderRow of the grid.

When you retrieve the HeaderRow, using the prependHeaderRow() or appendHeaderRow() methods, you can then group the columns using the join() method. In addition, you can use the setText() and setComponent() methods on the join result to set the text or component for the joined columns.

Example: Grouping columns

// Create a header row
HeaderRow topRow = grid.prependHeaderRow();

// group two columns under the same label
topRow.join(nameColumn, ageColumn)
        .setComponent(new Label("Basic Information"));

// group the other two columns in the same header row
topRow.join(streetColumn, postalCodeColumn)
        .setComponent(new Label("Address Information"));

Using Renderers in Columns

You can configure columns to use a renderer to show the data in the cells.

Conceptually, there are three types of renderer:

  1. Basic renderer: Renders basic values, such as dates and numbers.

  2. Lit renderer: Renders content using HTML markup and Lit data-binding syntax.

  3. Component renderer: Renders content using arbitrary components.

Using Basic Renderers

You can use several basic renderers to configure grid columns.

Local Date Renderer

Use LocalDateRenderer to render LocalDate objects in the cells.

Example: Using LocalDateRenderer with the addColumn() method.

grid.addColumn(new LocalDateRenderer<>(
    .setHeader("Estimated delivery date");

LocalDateRenderer works with a DateTimeFormatter or a String format to render LocalDate objects.

Example: Using a String format to render the LocalDate object.

grid.addColumn(new LocalDateRenderer<>(
    .setHeader("Estimated delivery date");

Local Date Time Renderer

Use LocalDateTimeRenderer to render LocalDateTime objects in the cells.

Example: Using LocalDateTimeRenderer with the addColumn() method.

grid.addColumn(new LocalDateTimeRenderer<>(
    .setHeader("Purchase date and time");

LocalDateTimeRenderer also works with DateTimeFormatter (with separate styles for date and time) or a String format to render LocalDateTime objects.

Example: Using a String format to render the LocalDateTime object.

grid.addColumn(new LocalDateTimeRenderer<>(
        "dd/MM HH:mm:ss")
).setHeader("Purchase date and time");

Number Renderer

Use NumberRenderer to render any type of Number in the cells. It’s especially useful for rendering floating-point values.

Example: Using NumberRenderer with the addColumn() method.

grid.addColumn(new NumberRenderer<>(Item::getPrice,

It’s possible to set up the NumberRenderer with a String format, and an optional null representation.

Example: Using a String format to render a price.

grid.addColumn(new NumberRenderer<>(
        Item::getPrice, "$ %(,.2f",
        Locale.US, "$ 0.00")

Native Button Renderer

Use NativeButtonRenderer to create a clickable button in the cells. This creates a native <button> on the client side. Click and tap (for touch devices) events are handled on the server side.

Example: Using NativeButtonRenderer with the addColumn() method.

    new NativeButtonRenderer<>("Remove item",
       clickedItem -> {
           // remove the item

You can configure a custom label for each item.

Example: Configuring NativeButtonRenderer to use a custom label.

grid.addColumn(new NativeButtonRenderer<>(
        item -> "Remove " + item,
        clickedItem -> {
            // remove the item

Using Lit Renderers

Providing a LitRenderer for a column allows you to define the content of cells using HTML markup, and to use Lit notations for data binding and event handling.

Example: Using LitRenderer to embolden the names of the persons.

Grid<Person> grid = new Grid<>();

       .withProperty("name", Person::getName)
  • The template string is passed for the static LitRenderer.of() method.

  • Every property in the template needs to be defined in the withProperty() method.

  • ${} is the Lit syntax for interpolating properties into the template. See the Lit documentation for more.

  • When using a custom Web Component or a Vaadin element in a Lit renderer, remember to import the component. This can be done using @JsModule or @Uses, if the component has a server-side counterpart. This ensures that all StyleSheet, HtmlImport, and JavaScript dependencies for the component are loaded when the Grid is used.

Creating Custom Properties

You can use a LitRenderer to create and display new properties (that is, properties the item didn’t originally contain).

Example: Using LitRenderer to compute the approximate age of each person and add it in a new column. Age is the current year minus the birth year.

        .<Person>of("${item.age} years old")
                person ->
                        - person.getYearOfBirth())

Using Expressions

Lit templates can include any type of JavaScript expression, not limited to binding single property values.

Example: By evaluating the person’s age in the template expression, the age column could also be written as:

        .<Person>of("${new Date().getFullYear() - item.yearOfBirth} years old")
        .withProperty("yearOfBirth", Person::getYearOfBirth);

Binding Beans

If an object contains a bean property that has sub-properties, it’s only necessary to make the bean accessible by calling the withProperty() method. The sub-properties become accessible automatically.

All properties of the bean, even ones which aren’t used in the template, are sent to the client, so use this feature with caution.

Example: Using the withProperty() method to access multiple sub-properties. This assumes that Person has a field for the Address bean, which has street, number and postalCode fields with corresponding getter and setter methods.

        "<div>${item.address.street}, number " +
        "${item.address.number}<br>" +
        "<small>${item.address.postalCode}</small>" +
        .withProperty("address", Person::getAddress))

Handling Events

You can define event handlers for the elements in your template, and hook them to server-side code, by calling the withFunction() method on your LitRenderer. This is useful for editing items in the grid.

Example: Using the withFunction() method to map defined method names to server-side code. The snippet adds a new column with two buttons: one to edit a property of the item and one to remove the item. Both buttons define a method to call for click events.

     "<button @click=\"${handleUpdate}\">Update</button>" +
     "<button @click=\"${handleRemove}\">Remove</button>")
    .withFunction("handleUpdate", person -> {
        person.setName(person.getName() + " Updated");
    }).withFunction("handleRemove", person -> {
        ListDataProvider<Person> dataProvider =
            (ListDataProvider<Person>) grid
  • When the server-side data used by the grid is edited, the grid’s DataProvider is refreshed by calling the refreshItem() method. This ensures that the changes show up in the element.

  • When an item is removed, the refreshAll() method call ensures that all the data is updated.

  • You need to use Lit notation for event handlers. @click is Lit syntax for the native click.

  • LitRenderer has a fluent API, so you can chain the commands, like LitRenderer.of().withProperty().withProperty().withFunction()…​

The withFunction() handler can also receive more data in addition to the item. To pass additional data from client to the server-side handler, you need to invoke the function in the Lit template with the desired extra parameters. The additional data can be accessed via the second handler parameter (of type JsonArray).


     "<input .value=\"${item.profession}\" @change=\"${e => changed(}\">")
    .withFunction("changed", (person, args) -> {
        String profession = args.getString(0);
    }).withProperty("profession", Person::getProfession));
  • The functions defined by the withFunction() method can be called with any number of additional parameters.

  • The additional argument of type String (the updated profession) is obtained from the second handler parameter with args.getString(0), where the number is the index of the argument in the JsonArray.

Using Component Renderers

You can use any component in the grid cells by providing a ComponentRenderer for a column.

To define how the component is generated for each item, you need to pass a Function for the ComponentRenderer.

Example: Adding a column that contains a different icon depending on the person’s gender.

Grid<Person> grid = new Grid<>();

grid.addColumn(new ComponentRenderer<>(person -> {
    if (person.getGender() == Gender.MALE) {
        return VaadinIcon.MALE.create();
    } else {
        return VaadinIcon.FEMALE.create();

It’s also possible to provide a separate Supplier to create the component, and a Consumer to configure it for each item.

Example: Using ComponentRenderer with a Consumer.

SerializableBiConsumer<Div, Person> consumer =
        (div, person) -> div.setText(person.getName());
        new ComponentRenderer<>(Div::new, consumer))

If the component is the same for every item, you only need to provide the Supplier.

Example: Using ComponentRenderer with a Supplier.

    new ComponentRenderer<>(
             () -> VaadinIcon.ARROW_LEFT.create()));

You can create complex content for the grid cells by using the component APIs.

Example: Using ComponentRenderer to create complex content that listens for events and wraps multiple components in layouts.

grid.addColumn(new ComponentRenderer<>(person -> {

    // text field for entering a new name for the person
    TextField name = new TextField("Name");

    // button for saving the name to backend
    Button update = new Button("Update", event -> {

    // button that removes the item
    Button remove = new Button("Remove", event -> {
        ListDataProvider<Person> dataProvider =
            (ListDataProvider<Person>) grid

    // layouts for placing the text field on top
    // of the buttons
    HorizontalLayout buttons =
            new HorizontalLayout(update, remove);
    return new VerticalLayout(name, buttons);
addComponentColumn() is a shorthand for addColumn() with a ComponentRenderer.
  • Editing grid items requires refreshing the grid’s DataProvider. The reasoning is the same as for Handling Events mentioned earlier.

See Data Providers for more.

Enabling Expanding Rows

The Grid supports expanding rows that reveal more detail about the items. The additional information is hidden, unless the user chooses to reveal it, keeping the grid appearance clean and simple, while simultaneously allowing detailed explanations.

You can enable expanding rows using the setItemDetailsRenderer() method, which allows either a LitRenderer or a ComponentRenderer to define how the details are rendered.

Example: Using the setItemDetailsRenderer() method with a ComponentRenderer.

    new ComponentRenderer<>(person -> {
        VerticalLayout layout = new VerticalLayout();
        layout.add(new Label("Address: " +
                person.getAddress().getStreet() + " " +
        layout.add(new Label("Year of birth: " +
        return layout;

By default, the row’s detail opens by clicking the row. Clicking the row again, or clicking another row (to open its detail), automatically closes the first row’s detail. You can disable this behavior by calling the grid.setDetailsVisibleOnClick(false) method. You can show and hide item details programmatically using the setDetailsVisible() method, and test whether an item’s detail is visible using the isDetailsVisible() method.

By default, items are selected by clicking them. If you want the click action only to show the item details without selection, you need to use the grid.setSelectionMode(SelectionMode.NONE) method.

Column Sorting

By default, this is how column sorting in the grid works:

  • The first click on the column header sorts the column.

  • The second click reverses the sort order.

  • The third click resets the column to its unsorted state.

If multi-sorting is enabled, the user can sort by multiple columns. The first click sorts the first column. Subsequent clicks on second and more sortable column headers add secondary and more sort criteria.

Defining Column Sorting

The difference between in-memory and backend sorting is key to understanding the sorting mechanism:

  • In-memory sorting is sorting that’s applied by the framework to items fetched from the backend, before returning them to the client.

  • Backend sorting is applied by providing a list of QuerySortOrder objects to your DataProvider. These typically pass the sort hints to the backend code and, sometimes, all the way to database queries. See Data Providers for more.

The sorting mechanism is flexible; you can configure in-memory and backend sorting together or separately.

The sections that follow detail options you can use to set up sorting for your grid.

Using a Sort Property Name

By using a sort property, you can override or customize the property or multiple properties that are used for sorting the column. This option includes both in-memory and backend sorting. The property is defined at the time of column construction and uses a sort property name.

You can use the addColumn() method to set a sort property to be used for backend sorting when the column is added to the grid.

Example: Using the addColumn() method to set a column sort property.

grid.addColumn(Person::getAge, "age").setHeader("Age");
  • The Age column uses the values returned by the Person::getAge() method to do in-memory sorting.

  • The column uses the age string to build a QuerySortOrder that’s sent to the DataProvider to do the backend sorting.

You can also define multiple properties.

Example: Using the addColumn() method to set multiple column sort properties.

grid.addColumn(person -> person.getName() + " " +
        person.getLastName(), "name", "lastName"
  • With multiple properties, the QuerySortOrder objects are created in the order they are declared.

You can also use properties created for your LitRenderer.

Example: Using the addColumn() method with LitRenderer to set column sort properties.

        "<div>${}<br>" +
        .withProperty("name", Person::getName)
        .withProperty("email", Person::getEmail),
    "name", "email")
  • For in-memory sorting to work correctly, the values returned by the ValueProviders in the LitRenderer (Person::getName() and Person::getEmail() in this example) should implement Comparable.

  • The names of the sort properties must match the names of the properties in the template (set via withProperty()).

Using a Comparator

This option is for in-memory sorting only, and uses a custom comparator.

If you need custom logic to compare items for sorting, or if your underlying data isn’t Comparable, you can set a Comparator for your column.

Example: Using the setComparator() method to configure a comparator for a column.

    .setComparator((person1, person2) ->

Setting Backend Sort Properties

This option is for backend sorting only, and uses a sort property name. It’s similar to Using a Sort Property Name, but excludes in-memory sorting.

You can use the setSortProperty() method to set strings describing backend properties to be used when sorting the column.

Example: Using the setSortProperty() method to define sorting.

        .setSortProperty("name", "email")
  • Unlike using the sorting properties in the addColumn() method directly, calling setSortProperty() doesn’t configure any in-memory sorting.

  • A SortOrderProvider is created automatically when the sort properties are set.

Setting a SortOrderProvider

This option is for backend sorting and uses a SortOrderProvider.

If you need fine-grained control over how QuerySortOrder objects are created and sent to the DataProvider, you can define a SortOrderProvider.

Example: Defining a SortOrderProvider for backend sorting.

    .setSortOrderProvider(direction -> Arrays
        .asList(new QuerySortOrder("name", direction),
                new QuerySortOrder("email", direction))

Enabling and Disabling Column Sorting

When a column is sortable, it displays the sorter element in the column header.

You can use the setSortable() method to toggle the sorter element on and off.

Example: Using the setSortable() method to disable sorting.


Setting a column as not sortable doesn’t delete a Comparator, sort property, or SortOrderProvider that was previously set. You can toggle the sortable flag on and off, without reconfiguration.

To check if a column is currently sortable, you can use the isSortable() method.

Example: Checking if a column is sortable.


Enabling Multi-Sorting

To allow users to sort by more than one column at the same time, you can use the setMultiSort() method to enable multi-sorting at the grid level.

Example: Using the setMultiSort() method to enable multi-sorting.


Receiving Sort Events

You can add a SortListener to the grid to receive general sort events. Every time sorting of the grid is changed, an event is fired. You can access the DataCommunicator to receive the sorting details.

Example: Using the addSortListener() method to add a SortListener.

grid.addSortListener(event -> {
    String currentSortOrder = grid.getDataCommunicator()
            .map(querySortOrder -> String.format(
                   "{sort property: %s, direction: %s}",
            .collect(Collectors.joining(", "));
            "Current sort order: %s. User-clicked: %s.",
            currentSortOrder, event.isFromClient()));

Styling the Grid

Styling the Grid component (or any Vaadin component) requires some Web Component and shadow-DOM knowledge. Styling depends on the component’s position in the DOM:

  • If the component is in the shadow DOM, you can apply styling within the component or using variables.

  • If the component is in the "normal" DOM (not in the shadow DOM), normal CSS styling applies.

In addition, the Grid supports the theme attribute, which allows you to customize component styling.

Example: Celebrity grid used in styling examples below.

Grid<Celebrity> grid = new Grid<>();
grid.addColumn(new ComponentRenderer<>(person -> {
    TextField textField = new TextField();
    textField.addClassName("style-" +
        event -> person.setName(event.getValue()));
    return textField;

grid.addColumn(new ComponentRenderer<>(person -> {
    DatePicker datePicker = new DatePicker();
    datePicker.addValueChangeListener(event -> {
    datePicker.addClassName("style-" +
    return datePicker;

grid.addColumn(new ComponentRenderer<>(person -> {
    Image image = new Image(person.getImgUrl(),
    return image;

Styling with the Theme Property

The default Lumo theme includes different variations that you can use to style the grid. You can provide one or more variations.

Example: Using the addThemeVariants() method to define theme variations for the grid.

        GridVariant.LUMO_NO_BORDER, GridVariant.LUMO_ROW_STRIPES);

Styling with CSS

You can use normal CSS styling for the content in the grid cells. Although the Grid component itself is in the shadow DOM, the actual values (cell contents) are in slots and therefore in the light DOM.

Example: Setting the maximum size for images in the grid cells.

vaadin-grid vaadin-grid-cell-content img {
    max-height: 4em;
  • vaadin-grid-cell-content is in the light DOM, and the selector vaadin-grid vaadin-grid-cell-content points to all the grid’s cells.

You can also use a class to apply styles to a specific component instance.

Example: Applying rounded borders and centering images in a grid with a "styled" class name.

vaadin-grid.styled vaadin-grid-cell-content img {
    border-radius: 2em;
    margin-left: 50%;
    transform: translate(-50%);

To learn how to customize styles inside a component’s shadow DOM, see Styling Components.