Table Flow
- Basic Usage
- Table Structure
- Rows and Cells
- Spanning Cells
- Column Groups
- Binding Rows to a Signal
- Styling
- Testing
- Migrating from Native Table
- Related Components
The Table component family renders a standard HTML table element, together with its caption, column groups, sections, rows, and cells.
Basic Usage
A table is created with Table and filled through its row factories. Each factory creates the row — and the section that contains it — and attaches it in a single call.
setCaptionText() adds a caption element, addHeaderRow() adds a row of th scope="col" cells to the thead, and addRowWithHeader() adds a tbody row that starts with a th scope="row" cell. The example above produces this markup:
Source code
HTML
<table>
<caption>Data about the planets of our solar system</caption>
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Mass (10^24 kg)</th>
<th scope="col">Diameter (km)</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Mercury</th>
<td>0.330</td>
<td>4,879</td>
</tr>
<!-- ... -->
</tbody>
</table>Table Structure
The family has one component per table element:
| Component | HTML Element | Description |
|---|---|---|
|
| The table itself. Holds an optional caption, any number of column groups, an optional head, one or more bodies, and an optional foot. |
|
| The table’s title, which gives the table an accessible description. |
|
| A group of columns, used to apply attributes — most often a CSS class — to several columns at once. |
|
| A single column, or a band of columns when it carries a span. |
|
| The rows that make up the head of the table. |
|
| A group of rows that make up a body of the table. |
|
| The rows that make up the foot of the table, typically summaries. |
|
| A row of cells. |
| — | The abstract base class of the two cell types. |
|
| A header cell, which labels a row, a column, or a group of either. |
|
| A data cell. |
There is no generic add(Component) on Table, TableHead, TableBody, TableFoot, or TableRow. Children go in through the typed accessors and factories described below.
The HTML specification requires the children of a table to appear in a fixed order: the caption first, then the column groups, then the head, bodies, and foot. Table inserts each child at the position that order calls for, whatever order the calls are made in, so a caption added last still renders first.
Caption
setCaptionText() sets the caption’s text, creating the caption element if the table doesn’t have one. For a caption that contains components rather than plain text, use addCaption():
Source code
Java
table.addCaption(new Emphasis("Q1"), new Span(" revenue by region"));getCaption() returns the caption, creating one if the table has none. To inspect a table without changing it, use hasCaption() or getCaptionText(). removeCaption() removes it.
Head, Body, and Foot
getHead(), getBody(), and getFoot() return the corresponding section, creating it if the table doesn’t have one. Addressing sections directly is useful when a section has to be styled, bound to a signal, or filled with rows built elsewhere.
Most code never has to name a section. addRow(), addRowWithHeader(), addHeaderRow(), and addFooterRow() on Table create the enclosing tbody, thead, or tfoot on demand, and getHeaderRows(), getBodyRows(), getFooterRows(), getAllRows(), and removeRow() read and remove rows without naming a section either.
As with the caption, hasHead() and hasFoot() inspect the table without creating anything, and removeHead() and removeFoot() remove the sections.
Multiple Bodies
A table may have several tbody elements, which is a way to group rows — for example, one body per region — and to bound the reach of a rowspan. addBody() appends a new body and returns it, getBodies() lists them, and removeBody() removes one. The row factories on Table always append to the first body; to append to another, call addRow() on the body itself.
Source code
Java
TableBody northern = table.addBody();
northern.addRow().addDataCells("Helsinki", "612,664");
TableBody southern = table.addBody();
southern.addRow().addDataCells("Cape Town", "433,688");Rows and Cells
TableRow offers a factory for each kind of cell. The single-cell factories return the new cell, so it can be configured further; the plural ones return the row, so calls can be chained.
| Method | Produces | Notes |
|---|---|---|
|
| Overloads take a text, a |
|
| Returns the row. |
|
| No |
|
| Labels the rest of the row. |
|
| Labels the column below. |
|
| Returns the row. |
|
| An overload also takes a |
|
| An overload also takes a |
|
| Insert at a given position. |
getCells(), getDataCells(), and getHeaderCells() read a row’s cells back.
A cell holds flow content, so any component may go inside one, including a button or a field.
Source code
Java
TableRow row = table.addRow();
row.addRowHeaderCell("Invoice #1042");
row.addDataCell(new Button("Download", event -> download(1042)));Header Cell Scope
The scope attribute on a th states which cells the header labels, and it’s what lets a screen reader read out the right header when the user moves into a data cell. The scoped factories set it, and setScope() sets it explicitly with a value from the TableHeaderCell.Scope enum: ROW, COL, ROWGROUP, COLGROUP, or AUTO.
Leaving the scope unset — as addHeaderCell() does — leaves the browser to infer it from the table structure, which is adequate for a simple table with a single header row. Set it explicitly for anything more involved.
Spanning Cells
setColspan() and setRowspan() on TableCell make a cell reach across several columns or rows. Both have hasColspan() and resetColspan() counterparts, so an explicit colspan="1" can be told apart from no attribute at all.
A colspan has to be a positive integer. A rowspan may also be 0, which carries its HTML meaning: the cell reaches to the end of the row group it belongs to.
Associating Cells with Headers
In a table where scope alone can’t say which headers apply to a cell, the headers attribute names them by id. setHeaders() takes the header cells themselves and writes their ids, and setHeaderIds() takes the ids directly. The referenced cells must have an id.
Source code
Java
TableHeaderCell mass = new TableHeaderCell("Mass");
mass.setId("mass");
TableHeaderCell metric = new TableHeaderCell("Metric");
metric.setId("metric");
TableDataCell cell = row.addDataCell("5.97");
cell.setHeaders(mass, metric);getHeaderIds() reads the ids back, and resetHeaders() removes the attribute.
Column Groups
A colgroup applies attributes to whole columns at once, which is the least repetitive way to give a column a width or a background. Only a few CSS properties apply to columns: background, border (with border-collapse: collapse), visibility: collapse, and width.
The example uses a col with a span of 2 to skip past the first two columns, then a col carrying a CSS class for the third. The class is styled with ordinary CSS:
Source code
CSS
col.highlight {
background-color: var(--vaadin-background-container-strong);
}Per the HTML specification, a colgroup is used in one of two modes: either it carries a span attribute and has no children, or it contains col children and has no span. TableColumnGroup enforces this — adding a column to a group that carries a span throws IllegalStateException, as does setting a span on a group that already has columns. Call resetSpan() or removeAll() first to switch between the modes.
Binding Rows to a Signal
TableHead, TableBody, and TableFoot support bindChildren(), so a section’s rows can track a ListSignal instead of being rebuilt by hand. Rows are added, removed, and reordered as the signal changes.
Individual cells and captions can track a signal too: the TableCaption, TableDataCell, and TableHeaderCell constructors, and the cell factories on TableRow, all have an overload that takes a Signal<String>.
Source code
Java
row.addDataCell(unreadCount.map(String::valueOf));Column groups are an exception: bindChildren() on TableColumnGroup throws UnsupportedOperationException, because a binding would bypass the rule that keeps span and col children mutually exclusive. See Component Bindings for more about signal bindings.
Styling
A table renders as plain HTML, so it inherits nothing from the Vaadin theme and is styled with ordinary CSS. A class name on the table, a section, or a column group is usually the most convenient hook:
Source code
Java
table.setClassName("pricing-table");Source code
CSS
.pricing-table {
border-collapse: collapse;
}
.pricing-table :is(th, td) {
border: 1px solid var(--vaadin-border-color);
padding: var(--vaadin-padding-xs) var(--vaadin-padding-s);
text-align: start;
}Testing
The flow-html-components-testbench module provides TableElement and a matching element class for each component in the family. See Testing for how to set up TestBench.
Two methods on TableElement are worth knowing apart. getCell(row, column) counts cells as they were written, which stops matching what’s on screen as soon as spans are involved. getCellCovering(row, column) counts the positions a reader sees, so a spanning cell is returned for every slot it reaches, and getColumnCount() reports how wide the table is once spans are resolved. Both index rows across the whole table: the head rows first, then the rows of each body, then the foot rows.
Applied to the "Units sold by category" table above, which sets the id sales, the "Oranges" row writes no cell of its own in the first column — the "Fruit" header spans into it:
Source code
Java
TableElement table = $(TableElement.class).id("sales");
Assert.assertEquals(3, table.getColumnCount());
Assert.assertEquals("Fruit", table.getCellCovering(2, 0).getText());Migrating from Native Table
The NativeTable family is deprecated as of Vaadin 25.3 and is scheduled for removal in Vaadin 26. Each class has a direct replacement:
| Deprecated | Replacement |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The TestBench element classes follow the same pattern: NativeTableElement becomes TableElement, NativeTableCellElement becomes TableDataCellElement, and so on.
Migration isn’t a pure rename, because the new types are stricter. The old components all extended HtmlContainer and accepted any child, so code that relied on the generic add(Component) has to move to a typed factory or accessor — for example, nativeTable.add(new NativeTableRow()) becomes table.addRow(), and nativeTable.add(caption) becomes table.setCaption(caption). In exchange, the new family adds colgroup and col support, cell factories that set scope, spans with colspan and rowspan, the headers attribute, and signal bindings.