Listing layout
Every listing in Mateu renders in one of five layouts. A listing you do not configure is a table — at any screen width. Ask for a different layout with gridLayout() when a table is not the right shape for your data.

The four layouts
Section titled “The four layouts”| Layout | How you get it | Best for |
|---|---|---|
| table | the default | Dense data with many comparable fields |
| list | gridLayout(GridLayout.list) | Navigation-heavy UIs: a title line plus secondary info |
| cards | gridLayout(GridLayout.cards) | Product catalogues, media galleries |
| masterDetail | gridLayout(GridLayout.masterDetail) | Complex entities that need side-by-side browsing |
| tree | gridLayout(GridLayout.tree) | Hierarchical rows — see Tree layout |
Why the default is not inferred
Section titled “Why the default is not inferred”It used to be. The framework measured the total column weight against the available width and
picked table, list, cards or master-detail from the result. The measurement was reasonable and the
outcome was not: the same screen looked like a different screen on a narrower window, on a second
monitor, or the day somebody added a column — and nothing in the model said it would. A listing is
a table until it says otherwise; if you want cards, say cards.
A narrow window keeps the table and scrolls it sideways inside the listing box (the page itself
never scrolls sideways). On a phone, prefer list or cards explicitly.
What a card shows (web and Redwood renderers alike): the first image column as its picture,
the identifier column (or the first one) as its title, Status columns as badges, the @Line(2)
and later columns as secondary lines, the remaining columns (up to six) as label/value pairs, and
the row actions as buttons. Clicking a card does what clicking a table row does. In the Redwood
renderer list still renders as a table, tree as a table whose rows fold (see
Tree layout) and cards as cards; masterDetail
renders the table, and a click on a row (when it has no rowRoute and is not navigable) opens its
fields in a panel beside it — a full-screen sheet on a phone.
Column weights
Section titled “Column weights”Weights no longer choose the layout, but they still describe how much room a column needs, and the
compact layouts (list, masterDetail) use the related @Priority metadata to decide what goes
in the summary line. 1 unit ≈ 76 px.
Default weights by type
Section titled “Default weights by type”| Column type / stereotype | Weight (units) |
|---|---|
bool, icon | 1.0 |
status, integer | 1.5 |
combobox, select, number, date, money | 2.0 |
link, dateTime, dateRange | 2.5 |
plain String | 3.0 |
image | 4.0 |
html, richText, markdown, textarea | 5.0 |
The weight is estimated server-side from the row field’s real Java type (a long counter
weighs 1.5, a LocalDate 2.0, …) and travels with each column on the wire, so the selection is
accurate even though the coarse column dataType collapses most types to string for rendering
purposes. @Weight / @ColumnWidth still override it.
Compact columns are columns marked as the identifier (@Priority(identifier = true)) or with a priority value ≤ 2 (@Priority(1), @Priority(2)). They are the fields shown in the condensed views (list row title, master-detail panel header).
Column widths in the table
Section titled “Column widths in the table”You can list the columns without giving them widths. Both renderers (Vaadin and Redwood) then size them the same way:
- Columns with short values fit their content and do not grow. These are identifiers, numbers,
amounts (
money), dates and times, booleans, statuses, enums (columns with value labels), row actions and buttons. - Text columns grow and share the remaining width. Each one gets a share in proportion to its
weight (default 1), so
weight: 2(or@Weight(2)) gets twice as much as a column with weight 1. - The table fills the available width. When the columns don’t fit, it scrolls horizontally instead of squeezing them. On Redwood, a text column starts at no more than 240px. Longer text is cut with an ellipsis, and the full text shows on hover.
- If no column grows, the compact ones share the width between them.
A declared width (@ColumnWidth) or flexGrow replaces this rule for that column, as before.
flexGrow: "0" makes a text column fit its content. flexGrow: "2" makes a compact column grow,
with weight 2.
Telling the compact layouts what matters
Section titled “Telling the compact layouts what matters”Mark the primary column
Section titled “Mark the primary column”public record ProductRow(
@Priority(value = 1, identifier = true) String name,
String category, BigDecimal price, ProductStatus status) {}identifier = true pins this column as the row title in list and master-detail layouts. Columns with priority ≤ 2 are included in the compact summary line.
Override column weight
Section titled “Override column weight”public record ProductRow(
@Weight(5.0) // treat as very wide String description,
@ColumnWidth("60px") // narrow fixed column — weight derived from px boolean active
) {}@Weight takes precedence over the type-based default. @ColumnWidth also feeds into the weight calculation when @Weight is absent.
Forcing a specific layout
Section titled “Forcing a specific layout”Via the fluent Listing builder
Section titled “Via the fluent Listing builder”return Listing.builder() .gridLayout(GridLayout.cards) // ... .build();Available values: auto (default), table, list, cards, masterDetail, tree.
Via listingType (shorthand for cards)
Section titled “Via listingType (shorthand for cards)”return Listing.builder() .listingType(ListingType.card) // ... .build();listingType(ListingType.card) is equivalent to gridLayout(GridLayout.cards). It exists as a semantic alias for CRUDs whose primary presentation is cards.
Tree layout (hierarchical rows)
Section titled “Tree layout (hierarchical rows)”GridLayout.tree renders the listing as an expandable tree grid instead of a flat table. You opt in explicitly, as with every non-default layout, and here it also changes the data contract: the rows must be hierarchical.
Two requirements:
- The row type exposes a self-referential
childrencollection. A row with a non-emptychildrenlist gets an expand/collapse toggle; leaves leave it empty ornull. gridLayout()returnsGridLayout.tree.
public record CategoryRow(
@Priority(value = 1, identifier = true) // becomes the expandable tree column String name,
int products,
List<CategoryRow> children // sub-categories → the tree branches) {}@UI("/catalog")@Title("Catalog")public class CatalogTree implements Listing<CategoryRow>, Searchable {
@Override public GridLayout gridLayout() { return GridLayout.tree; }
@Override public ListingData<CategoryRow> search(SearchRequest request, HttpRequest httpRequest) { return ListingData.of(rootCategories); // roots only — each carries its children }}search() returns only the root rows; the framework expands each branch lazily from the children list as the user opens it. The identifier column becomes the tree column, with the expand toggle.
To let users edit a node in place, make the CRUD @SplitCrud so selecting a row opens its editor in a detail pane. That full pattern — including heterogeneous trees whose nodes edit with different forms, and grouping nodes that are not openable (viewable = false) — is documented in Tree CRUD.
Every row starts open and folds from the chevron in its first column, with no round trip; a click
anywhere else on the row still opens the record when the listing has a rowRoute. A new search or
filter opens the tree again, so a folded branch never hides a match, and paging keeps what is
folded. A tree has no sortable columns: a flat sort would tear children from their parents.
Fixing the content height (cards and table)
Section titled “Fixing the content height (cards and table)”By default the listing expands to fill the container, which scrolls the whole page. Set contentHeight to constrain the card or table area to a fixed height with its own internal scrollbar:
return Listing.builder() .gridLayout(GridLayout.cards) .contentHeight("400px") .build();Practical guide: which layout will I get?
Section titled “Practical guide: which layout will I get?”| Situation | Layout |
|---|---|
| Anything you did not configure | table |
| The same listing on a phone | table, scrolled sideways |
20 columns and no gridLayout() | table (a wide one) — ask for masterDetail if that is what you want |
gridLayout(GridLayout.cards), or listingType(ListingType.card) | cards |
Rows carry a children list and gridLayout(GridLayout.tree) set | tree (expandable) |
- Customizing CRUD and listings — restrict capabilities, hide fields, add toolbar buttons
- Listing row actions — per-row
ColumnActionandColumnActionGroup
See it live in the Explorer: Listing layouts · Column widths · Row route (the way into a record)