Skip to content
This repository has been archived by the owner on May 6, 2021. It is now read-only.

Latest commit

 

History

History
948 lines (716 loc) · 33.4 KB

tutorial-flow-grid.asciidoc

File metadata and controls

948 lines (716 loc) · 33.4 KB
title order layout
Grid
4
page

Grid

Overview

Grid is for displaying and editing tabular data laid out in rows and columns. At the top, a header can be shown, and a footer at the bottom. In addition to plain text, the header and footer can contain HTML and components. Having components in the header allows implementing filtering easily. The grid data can be sorted by clicking on a column header; shift-clicking a column header enables secondary sorting criteria.

The data area can be scrolled both vertically and horizontally. The leftmost columns can be frozen, so that they are never scrolled out of the view. The data is loaded lazily from the server, so that only the visible data is loaded. The smart lazy loading functionality gives excellent user experience even with low bandwidth, such as mobile devices.

Binding to Data

Grid is normally used by binding it to a data provider, described in Data Providers. By default, it is bound to List of items. You can set the items with the setItems() method.

For example, if you have a list of beans, you show them in a Grid as follows

// 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<>();
grid.setItems(people);
grid.addColumn(Person::getName).setHeader("Name");
grid.addColumn(person -> Integer.toString(person.getYearOfBirth()))
        .setHeader("Year of birth");

layout.add(grid);

Handling Selection Changes

Selection in Grid is handled a bit differently from other selection components, as it is not a HasValue. Grid supports single, multiple, or no-selection, each defined by a specific selection model. Each selection model has a specific API depending on the type of the selection.

For basic usage, switching between the built-in selection models is possible by using the setSelectionMode(SelectionMode). Possible options are SINGLE (default), MULTI, or NONE.

Listening to selection changes in any selection model is possible with a SelectionListener, which provides a generic SelectionEvent for getting the selected value or values. Note that the listener is actually attached to the selection model and not the grid, and will stop getting any events if the selection mode is changed.

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

// switch to multiselect mode
grid.setSelectionMode(SelectionMode.MULTI);

grid.addSelectionListener(event -> {
    Set<Person> selected = event.getAllSelectedItems();
    message.setText(selected.size() + " items selected");
});

Programmatically selecting the value is possible via select(T). In multiselect mode, this will add the given item to the selection.

// in single-select, only one item is selected
grid.select(defaultItem);

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

The current selection can be obtained from the Grid by getSelectedItems(), and the returned `Set contains either only one item (in single-selection mode) or several items (in multi-selection mode).

Warning

If you change selection mode for a grid, it will clear the selection and fire a selection event. To keep the previous selection you must reset the selection afterwards using the select() method.

Warning

If you change the grid’s items with setItems() or the used DataProvider, it will clear the selection and fire a selection event. To keep the previous selection you must reset the selection afterwards using the select() method.

Selection Models

For more control over the selection, you can access the used selection model with getSelectionModel(). The return type is GridSelectionModel which has generic selection model API, but you can cast that to the specific selection model type, typically either SingleSelectionModel or MultiSelectionModel.

The selection model is also returned by the setSelectionMode(SelectionMode) method.

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

// Use multi-selection mode
GridMultiSelectionModel<Person> selectionModel = (GridMultiSelectionModel<Person>) grid
        .setSelectionMode(SelectionMode.MULTI);

Single Selection Model

By obtaining a reference to the SingleSelectionModel, you can access more fine grained API for the single-select case.

The addSingleSelect(SingleSelectionListener) method provides access to SingleSelectionEvent, which has some extra API for more convenience.

In single-select mode, it is possible to control whether the empty (null) selection is allowed. By default it is enabled, but can be disabled with setDeselectAllowed().

// preselect value
grid.select(defaultItem);

GridSingleSelectionModel<Person> singleSelect = (GridSingleSelectionModel<Person>) grid
        .getSelectionModel();

// disallow empty selection
singleSelect.setDeselectAllowed(false);

Multi-Selection Model

In the multi-selection mode, a user can select multiple items by clicking on the checkboxes in the leftmost column.

By obtaining a reference to the MultiSelectionModel, you can access more fine grained API for the multi-select case.

The MultiSelectionModel provides addMultiSelectionListener(MultiSelectionListener) access to MultiSelectionEvent, which allows to easily access differences in the selection change.

// Grid in multi-selection mode
Grid<Person> grid = new Grid<>();
grid.setItems(people);
GridMultiSelectionModel<Person> selectionModel = (GridMultiSelectionModel<Person>) grid
        .setSelectionMode(SelectionMode.MULTI);

selectionModel.selectAll();

selectionModel.addMultiSelectionListener(event -> {
    message.setText(String.format("%s items added, %s removed.",
            event.getAddedSelection().size(),
            event.getRemovedSelection().size()));

    // Allow deleting only if there's any selected
    deleteSelected.setEnabled(event.getNewSelection().isEmpty());
});

Configuring Columns

The addColumn() method can be used to add columns to Grid.

Column configuration is defined in Grid.Column objects, which are returned by addColumn.

The setter methods in Column have fluent API, so you can easily chain the configuration calls for columns if you want to.

Column<Person> nameColumn = grid.addColumn(Person::getName)
    .setHeader("Name")
    .setFlexGrow(0)
    .setWidth("100px")
    .setResizable(false);

In the following, we describe the basic column configuration.

Column Headers and Footers

By default, no header or footer is present for a column. These must be set explicitly using the methods setHeader and setFooter through the API of a column. The methods have two overloads, one which accepts a plain string and one that accepts a TemplateRenderer. Template renderers are covered later in this tutorial.

// Sets a simple text header
nameColumn.setHeader("Name");
// Sets a header containing a custom template,
// in this case simply bolding the caption "Name"
nameColumn.setHeader("<b>Name</b>");

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

Column Order

You can enable drag and drop user reordering of columns with setColumnReorderingAllowed().

grid.setColumnReorderingAllowed(true);

Hiding Columns

Columns can be hidden by calling setHidden() in Column. Furthermore, you can set the columns user hidable using method setHidable().

Column Widths

Columns have by default undefined width, which causes automatic sizing based on the widths of the displayed data. You can set column widths relatively using flex grow ratios with setFlexGrow(), or explicitly by a CSS string value with setWidth() when flex grow has been set to 0.

When setResizable() is enabled the user can resize a column by dragging its separator with the mouse.

Frozen Columns

You can set columns to be frozen with the setFrozen() method in Column, so that they are not scrolled off when scrolling horizontally. Additionally, user reordering of frozen columns is limited between other frozen columns.

nameColumn.setFrozen(true);

Grouping Columns

Multiple columns can be grouped together by adding them in the header row of the grid. After the HeaderRow is retrieved via prependHeaderRow or appendHeaderRow grid api, join method can be used to group the columns. Additionally, setText and setComponent methods can be used on the join result to set the text or component for the joined 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"));

Column Keys

You can set identifier keys for your columns with the setKey() method. This allows retrieving the column from the grid at any time.

nameColumn.setKey("name");
grid.getColumnByKey("name").setWidth("100px");

Automatically Adding Columns

You can configure Grid to automatically add columns for every property in a bean. To do this, you need to pass the class of the bean type to the Grid’s constructor. The property names are set as the column keys, so you can use them for further configuring the columns.

Grid<Person> grid = new Grid<>(Person.class);
grid.getColumnByKey("yearOfBirth").setFrozen(true);

This constructor adds columns only for the direct properties of the bean type and the values are displayed as Strings. To add columns for nested properties, you can use dot notation with setColumn(String) method. For example, if Person has a reference to an Address object, which has a property postalCode, you can add a column for the postal code with:

grid.addColumn("address.postalCode");

The column’s key will be "address.postalCode" and it’s header will be "Postal Code".

Using Renderers

Columns can be configured to use Renderers to show the data in a more suitable way inside the cells. Conceptually renderers are split into the three categories listed below.

  1. Basic renderers - the renderers used to render basic values, such as dates and numbers

  2. Template renderer - allows the developer to define cells with HTML markup and Polymer data binding syntax

  3. Component renderer - allows the developer to use an arbitrary component inside the cells

Using Basic Renderers

There are several basic renderers that can be used to configure Grid columns. The currently supported basic renderers are gathered here under their own subsections.

LocalDateRenderer

Suitable for rendering LocalDate objects inside the grid cells.

grid.addColumn(new LocalDateRenderer<>(Item::getEstimatedDeliveryDate,
        DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)))
        .setHeader("Estimated delivery date");

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

grid.addColumn(new LocalDateRenderer<>(Item::getEstimatedDeliveryDate,
        "dd/MM/yyyy")).setHeader("Estimated delivery date");

LocalDateTimeRenderer

Suitable for rendering LocalDateTime objects inside the grid cells.

grid.addColumn(new LocalDateTimeRenderer<>(Item::getPurchaseDate,
        DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT,
                FormatStyle.MEDIUM)))
        .setHeader("Purchase date and time");

Similar to the LocalDateRenderer, it is possible to configure a DateTimeFormatter (with separate style for date and time) or a String format to properly render LocalDateTime objects.

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

NumberRenderer

Suitable for rendering any type of Number inside the grid cells. It is specially useful for rendering floating point values.

grid.addColumn(new NumberRenderer<>(Item::getPrice,
        NumberFormat.getCurrencyInstance())).setHeader("Price");

It is also possible to setup the NumberRenderer with a String format, and an optional null representation:

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

NativeButtonRenderer

An easy way to create a clickable button inside the grid cells. It creates a native <button> on the client side, and the click and tap events (for touch devices) are treated on the server side.

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

It is also possible to configure a custom label for each item:

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

Using Templates

You can define the contents of the grid cells with HTML markup and use Polymer notation for data binding and event handling. This is done by providing a TemplateRenderer for the appropriate Column.

The following example simply bolds the names of the persons.

Grid<Person> grid = new Grid<>();
grid.setItems(people);

grid.addColumn(TemplateRenderer.<Person> of("<b>[[item.name]]</b>")
                .withProperty("name", Person::getName)).setHeader("Name");

As you can see, the template-string is passed for the static TemplateRenderer.of() method, and every property used in that template needs to be defined with the withProperty() method.

Note
The [[item.name]] is Polymer syntax for binding properties for a list of items. Using this notation in this context is pretty straightforward, but you can refer to Polymer documentation for more details.

Using Custom Properties

You can also create and display new properties that the item doesn’t originally contain.

For example, based on the year of birth, you could roughly compute the age of each person and add a new column to display that.

grid.addColumn(TemplateRenderer.<Person> of("[[item.age]] years old")
        .withProperty("age",
                person -> Year.now().getValue()
                        - person.getYearOfBirth()))
        .setHeader("Age");

Binding Beans

If the object contains a bean property that has properties of it’s own, you only need to make the bean accessible by calling withProperty(), and the sub-properties become accessible as well.

For example, suppose that Person has a field for Address bean, and Address has fields street, number and postalCode with corresponding getter and setter methods. You can use all of those properties in your template with only one withProperty() call, as you can see in the following snippet.

grid.addColumn(TemplateRenderer.<Person> of(
        "<div>[[item.address.street]], number [[item.address.number]]<br><small>[[item.address.postalCode]]</small></div>")
        .withProperty("address", Person::getAddress))
        .setHeader("Address");

Handling Events

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

The following example adds a new column with two buttons: one for editing a property of the item, and another one for removing the item. Both buttons define a method to call for on-click events, and withEventHandler() is used to map those method-names to server-side code.

grid.addColumn(TemplateRenderer.<Person> of(
                "<button on-click='handleUpdate'>Update</button><button on-click='handleRemove'>Remove</button>")
                .withEventHandler("handleUpdate", person -> {
                    person.setName(person.getName() + " Updated");
                    grid.getDataProvider().refreshItem(person);
                }).withEventHandler("handleRemove", person -> {
                    ListDataProvider<Person> dataProvider = (ListDataProvider<Person>) grid
                            .getDataProvider();
                    dataProvider.getItems().remove(person);
                    dataProvider.refreshAll();
                })).setHeader("Actions");

After editing the server-side data used by the grid, you need to refresh the grid’s DataProvider to make those changes show up in the element. After editing an item you just need to call the refreshItem() method. When an item is removed, you need to update all of the data with refreshAll().

Note
You need to use Polymer notation for event handlers, so on-click (with a dash) instead of the native onclick.
Note
TemplateRenderer has fluent API, so you can chain the commands, like TemplateRenderer.of().withProperty().withProperty().withEventHandler()…​

Using Components

You can use any component inside the grid cells by providing a ComponentRenderer for the appropriate Column.

To define how the component will be generated for each item, you need to pass a Function for the ComponentRenderer. The following example adds a column that contains an icon for each person, that is based on the person’s gender.

Grid<Person> grid = new Grid<>();
grid.setItems(people);

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

You can also separately provide a Supplier for creating the component and a Consumer for configuring it for each item.

grid.addColumn(new ComponentRenderer<>(Div::new,
        (div, person) -> div.setText(person.getName())))
        .setHeader("Name");

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

grid.addColumn(new ComponentRenderer<>(
        () -> new Icon(VaadinIcon.ARROW_LEFT)));

Using the component APIs allows you to easily listen for events and wrap multiple components inside layouts, so you can create complex contents for the grid cells.

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

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

    // button for saving the name to backend
    Button update = new Button("Update", event -> {
        person.setName(name.getValue());
        grid.getDataProvider().refreshItem(person);
    });

    // button that removes the item
    Button remove = new Button("Remove", event -> {
        ListDataProvider<Person> dataProvider = (ListDataProvider<Person>) grid
                .getDataProvider();
        dataProvider.getItems().remove(person);
        dataProvider.refreshAll();
    });

    // layouts for placing the text field on top of the buttons
    HorizontalLayout buttons = new HorizontalLayout(update, remove);
    return new VerticalLayout(name, buttons);
})).setHeader("Actions");
Note
Editing the grid’s items requires refreshing it’s DataProvider, like explained above in the template tutorial. More information about DataProvider can be found here.

Showing Item Details

Often you don’t want to overwhelm the user with a complex grid with all the information about each item, but instead show just the basic information by default and hide the details. For this purpose, grid supports expanding it’s rows for showing additional details for the items. This is enabled with the setItemDetailsRenderer() method. You can pass either a TemplateRenderer or a ComponentRenderer for the method to define how the details are rendered.

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

By default you can open the details for a row simply by clicking on it. Clicking on the same row again or opening the details for another row closes the currently opened one. You can disable this default behavior by calling grid.setDetailsVisibleOnClick(false). You can show and hide item details programmatically with the setDetailsVisible() method, and test whether the details for an item is visible with isDetailsVisible().

Note
By default, items are selected by clicking them. If you want clicking just to show the item details without selection, you need to call grid.setSelectionMode(SelectionMode.NONE).

Sorting

A user can sort the data in a grid on a column by clicking the column header. Clicking another time on the current sort column reverses the sort direction. Clicking on a third time resets the column to its unsorted state. If multisorting is enabled, clicking on other sortable column headers adds a secondary or more sort criteria.

Defining how a column is sorted

Before jumping to the code, it’s important to understand 2 key features of the sorting mechanism: in-memory sorting and backend sorting.

In-memory sorting is the sorting that is applied to the items that have been fetched from the backend, before returning them to the client.

Backend sorting is a list of QuerySortOrder objects that can be used when implementing your own fetching logic within a DataProvider. You can check more details about the backend sorting here.

You can have both in-memory and backend sorting at the same time, or you can configure them separately. Here is a list of options you can use to setup the sorting for your Grid:

1. Using a sort property name at the column construction (in-memory and backend sorting)

You can set the sort properties that will be used to do backend sorting at the moment you add the column to the grid. For example:

grid.addColumn(Person::getAge, "age").setHeader("Age");

The Age column will use the values returned by Person::getAge method to do in-memory sorting, and use the age String to build a QuerySortOrder that will be sent to the DataProvider to do the backend sorting.

You can use multiple properties as well:

grid.addColumn(person -> person.getName() + " " + person.getLastName(),
                "name", "lastName").setHeader("Name");

When using multiple properties, the QuerySortOrder objects are created in the order they are declared.

You can use properties created for your TemplateRenderer too. For example:

grid.addColumn(TemplateRenderer.<Person> of(
        "<div>[[item.name]]<br><small>[[item.email]]</small></div>")
        .withProperty("name", Person::getName)
        .withProperty("email", Person::getEmail), "name", "email")
        .setHeader("Person");
Note
For the in-memory sorting to work properly, the values returned by the ValueProviders inside the TemplateRenderer (Person::getAge and Person::getEmail in the example) should implement Comparable.
Note
When using TemplateRenderers, the names of the sort properties must match the names of the properties in the template (set via withProperty).

2. Using a Comparator (in-memory sorting)

When you need a custom logic to compare items to sort them properly, or if your underlying data is not Comparable, you can set a Comparator to your column:

grid.addColumn(Person::getName)
        .setComparator((person1, person2) -> person1.getName()
                .compareToIgnoreCase(person2.getName()))
        .setHeader("Name");
Note
Keep in mind that when a Comparator is set for a column, it is executed for all the items that will be sent to the client. A comparator with poor performance will impact the overall performance of the Grid.

3. Setting sort properties (backend sorting)

You can set strings describing backend properties to be used when sorting the column.

grid.addColumn(Person::getName).setSortProperty("name", "email")
        .setHeader("Person");
Note
Unlike using the sorting properties in the addColumn method directly, calling setSortProperty doesn’t configure any in-memory sorting.

When setting the sort properties, a SortOrderProvider is created automatically for you.

4. Setting a SortOrderProvider (backend sorting)

If you need a fine control on how the QuerySortOrder objects are created and sent to the DataProvider, you can set a SortOrderProvider:

grid.addColumn(Person::getName)
        .setSortOrderProvider(direction -> Arrays
                .asList(new QuerySortOrder("name", direction),
                        new QuerySortOrder("email", direction))
                .stream())
        .setHeader("Person");

Enabling and disabling the sorting in a column

When using any of the 4 methods described above, the column is considered sortable - in other words, it displays the sorter element in the header of the column. You can toggle the sorter display for a column by using:

column.setSortable(false);

Setting a column as not sortable doesn’t delete any Comparator, sort property or SortOrderProvider previously set - so you can toggle the sortable flag on and off without having to reconfigure it every time.

You can check if a given column is currently sortable by calling:

column.isSortable();

Enabling multi-sorting

To enable users to sort the data by more than one sort criteria at the same time, you can enable multi-sorting at the Grid level:

grid.setMultiSort(true);

Receiving sort events

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

grid.addSortListener(event -> {
    String currentSortOrder = grid.getDataCommunicator()
            .getBackEndSorting().stream()
            .map(querySortOrder -> String.format(
                    "{sort property: %s, direction: %s}",
                    querySortOrder.getSorted(),
                    querySortOrder.getDirection()))
            .collect(Collectors.joining(", "));
    System.out.println(String.format(
            "Current sort order: %s. Sort originates from the client: %s.",
            currentSortOrder, event.isFromClient()));
});

Styling

Styling a Grid - or any other Flow Component - is straightforward, but requires some knowledge of web components and shadow-dom. Styling depends on the components position in the DOM. If component is in shadow dom, styling needs to be done within that component or by variables. If the component is not in the shadow dom, then it’s in the "normal" DOM and all normal css applies. Also, many Vaadin components support theme -attribute, which allows to quickly customize the component.

Example grid:

Grid<Person> grid = new Grid<>();
grid.setItems(Person.getPeople());
grid.addClassName("styled");
grid.addColumn(new ComponentRenderer<>(person -> {
    TextField textField = new TextField();
    textField.setValue(person.getName());
    textField.addClassName("style-" + person.getGender());
    textField.addValueChangeListener(event ->
            person.setName(event.getValue()));
    return textField;
})).setHeader("Name");

grid.addColumn(new ComponentRenderer<>(person -> {
    DatePicker datePicker = new DatePicker();
    datePicker.setValue(person.getDob());
    datePicker.addValueChangeListener(event -> {
        person.setDob(event.getValue());
    });
    datePicker.addClassName("style-" + person.getGender());
    return datePicker;
})).setHeader("DOB");

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

Styling with theme property

Vaadin components have different Lumo Theme variations usable. Using those with Flow is simple: just give needed variation, one or more.

grid.addThemeNames("no-border", "no-row-borders", "row-stripes");

Styling with css

The Grid itself is in the shadow-dom, but actual values (cells) have slots which are in the light dom, so normal css styling applies.

For example, lets set max size for images within grid:

vaadin-grid vaadin-grid-cell-content img {
    max-height: 4em;
}

vaadin-grid-cell-content is the slot that’s in the light dom, so selector vaadin-grid vaadin-grid-cell-content points to grids cells.

And we could of course set some fancier grid - a grid with "styled" -class - which would have rounded images, positioned in the center and also capped with max size (size comes with previous css):

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

Styling with overriding component styles

Styling of the grid itself can be done within custom styles by overriding grid-styling:

<dom-module id="custom-grid" theme-for="vaadin-grid">
    <template>
        <style>
            :host(.styled) #table {
                border-radius: 20px;
                box-shadow: 0 0 5px rgba(81, 203, 238, 1);
                border: 1px solid rgba(81, 203, 238, 1);
            }
            :host(.styled) #header {
                border: none;
                border-bottom: 1px solid rgba(81, 203, 238, 1);
            }
            :host(.styled) #header tr {
                text-align: center;
                text-shadow: 0 0 3px rgba(81, 203, 238, 1);
                text-transform: uppercase;
            }
        </style>
    </template>
</dom-module>

The theme-for="vaadin-grid" indigates that it’s overriding vaadin-grid -components styling. In the the css the :host(.styled) is a selector for vaadin-grid that has "styled" as class. So outside of shadow dom that would be vaadin-grid.styled, but the shadow dom is boxed in it’s own dom (ergo, the shadow dom) it must be selected with :host([selector]).

In this example we’re setting special styles for vaadin-grid with "styled" class. A grid without "styled" class remains normal.

Styling with variables

Although the shadow-dom is boxed and cannot be affected outside, there’s one method to pass info to the shadow-dow: CSS variables.

CSS variables will pass through all levels of dom, whether it’s light or shadow dom. So, if variable is set, then that value is available everywhere under that dom, regardless of shadow / light.

In order for variables to work, they need to be supported by component. So with given grid example, let’s make our styled grid have gender-wise colorization of text-fields.

First, lets introduce variable usage for wanted component (textfield):

<dom-module id="custom-text-field" theme-for="vaadin-text-field">
    <template>
        <style>
            .vaadin-text-field-container [part="input-field"] {
                background-color: var(--custom-text-field-bg, var(--lumo-contrast-10pct));
            }
        </style>
    </template>
</dom-module>

This is overriding vaadin-text-field styles. The only change made is that, if there’s a variable --custom-text-field-bg available, use it, otherwise fall back to normal.

Now, changing the variable, based on our person gender

.styled .style-female {
    --custom-text-field-bg: #ff99cc;
}
.styled .style-male {
    --custom-text-field-bg: #99ccff;
}

After this change, the text-field - anywhere - under the .styled .style-female/male uses special background color.

This does not only apply in the text-fields of the grid, but - because date-field uses the text-field internally - also the date-fields. Now the look and feel of used component - the textfield - is always the same, whether it’s directly used or wrapped within another component.