title | order | layout |
---|---|---|
Grid |
4 |
page |
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.
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);
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 |
Warning
|
If you change the grid’s items with |
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);
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);
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());
});
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.
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>");
You can enable drag and drop user reordering of columns with setColumnReorderingAllowed()
.
grid.setColumnReorderingAllowed(true);
Columns can be hidden by calling setHidden()
in Column
.
Furthermore, you can set the columns user hidable using method setHidable()
.
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.
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);
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"));
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");
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".
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.
-
Basic renderers - the renderers used to render basic values, such as dates and numbers
-
Template renderer - allows the developer to define cells with HTML markup and Polymer data binding syntax
-
Component renderer - allows the developer to use an arbitrary component inside the cells
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.
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");
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");
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");
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
}));
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.
|
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");
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");
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()…
|
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.
|
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) .
|
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.
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:
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 ).
|
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. |
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.
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");
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();
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);
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 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");
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");
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 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.
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.