> Markdown version of [Table](https://vaadin.com/docs/next/components/html-elements/table). Section index: [llms.txt](https://vaadin.com/docs/next/components/llms.txt)

# Table (since V25.3) Flow

The Table component family renders a standard HTML `table` element, together with its caption, column groups, sections, rows, and cells.

> **Note:** Table is low-level and flexible, which suits smaller data sets that are part of the page — a specification list, a comparison of plans, a summary of figures. For lazy loading, large data sets, sorting, filtering, and inline editing, use [Grid](https://vaadin.com/docs/next/components/grid.md), [Grid Pro](https://vaadin.com/docs/next/components/grid-pro.md), or [Tree Grid](https://vaadin.com/docs/next/components/tree-grid.md) instead.

## <a id="basic-usage"></a>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.

`TableBasic.java`

```java
Table table = new Table();
table.setCaptionText("Data about the planets of our solar system");

table.addHeaderRow("Name", "Mass (10^24 kg)", "Diameter (km)");

table.addRowWithHeader("Mercury", "0.330", "4,879");
table.addRowWithHeader("Venus", "4.87", "12,104");
table.addRowWithHeader("Earth", "5.97", "12,756");

add(table);
```

`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:

```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>
```

## <a id="table-structure"></a>Table Structure

The family has one component per table element:

| Component          | HTML Element | Description                                                                                                                           |
| ------------------ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| `Table`            | `table`      | The table itself. Holds an optional caption, any number of column groups, an optional head, one or more bodies, and an optional foot. |
| `TableCaption`     | `caption`    | The table’s title, which gives the table an accessible description.                                                                   |
| `TableColumnGroup` | `colgroup`   | A group of columns, used to apply attributes — most often a CSS class — to several columns at once.                                   |
| `TableColumn`      | `col`        | A single column, or a band of columns when it carries a span.                                                                         |
| `TableHead`        | `thead`      | The rows that make up the head of the table.                                                                                          |
| `TableBody`        | `tbody`      | A group of rows that make up a body of the table.                                                                                     |
| `TableFoot`        | `tfoot`      | The rows that make up the foot of the table, typically summaries.                                                                     |
| `TableRow`         | `tr`         | A row of cells.                                                                                                                       |
| `TableCell`        |  —           | The abstract base class of the two cell types.                                                                                        |
| `TableHeaderCell`  | `th`         | A header cell, which labels a row, a column, or a group of either.                                                                    |
| `TableDataCell`    | `td`         | 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.

### <a id="caption"></a>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()`:

```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.

### <a id="head-body-and-foot"></a>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.

`TableSections.java`

```java
Table table = new Table();
table.setCaptionText("Quarterly revenue, in thousands of euros");

table.getHead().addRow().addColumnHeaderCells("Quarter", "Revenue");

table.getBody().addRow().addDataCells("Q1", "1,200");
table.getBody().addRow().addDataCells("Q2", "1,450");

TableRow total = table.getFoot().addRow();
total.addRowHeaderCell("Total");
total.addDataCell("2,650");

add(table);
```

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.

### <a id="multiple-bodies"></a>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.

```java
TableBody northern = table.addBody();
northern.addRow().addDataCells("Helsinki", "612,664");

TableBody southern = table.addBody();
southern.addRow().addDataCells("Cape Town", "433,688");
```

## <a id="rows-and-cells"></a>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                                                     |
| ---------------------------------------- | ------------------------- | --------------------------------------------------------- |
| `addDataCell()`                          | `td`                      | Overloads take a text, a `Signal<String>`, or components. |
| `addDataCells()`                         | `td` per text             | Returns the row.                                          |
| `addHeaderCell()`                        | `th`                      | No `scope` attribute; the browser infers one.             |
| `addRowHeaderCell()`                     | `th scope="row"`          | Labels the rest of the row.                               |
| `addColumnHeaderCell()`                  | `th scope="col"`          | Labels the column below.                                  |
| `addColumnHeaderCells()`                 | `th scope="col"` per text | Returns the row.                                          |
| `addRowGroupHeaderCell()`                | `th scope="rowgroup"`     | An overload also takes a `rowspan`.                       |
| `addColumnGroupHeaderCell()`             | `th scope="colgroup"`     | An overload also takes a `colspan`.                       |
| `insertDataCell()`, `insertHeaderCell()` | `td`, `th`                | 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.

```java
TableRow row = table.addRow();
row.addRowHeaderCell("Invoice #1042");
row.addDataCell(new Button("Download", event -> download(1042)));
```

### <a id="header-cell-scope"></a>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.

## <a id="spanning-cells"></a>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.

`TableSpanningCells.java`

```java
Table table = new Table();
table.setId("sales");
table.setCaptionText("Units sold by category");

table.addHeaderRow("Category", "Product", "Units");

TableRow fruit = table.addRow();
fruit.addRowGroupHeaderCell("Fruit", 2);
fruit.addDataCells("Apples", "1,200");
table.addRow("Oranges", "900");

TableRow vegetables = table.addRow();
vegetables.addRowGroupHeaderCell("Vegetables", 2);
vegetables.addDataCells("Carrots", "450");
table.addRow("Potatoes", "780");

TableRow total = table.addFooterRow();
TableHeaderCell totalLabel = total.addRowHeaderCell("Total");
totalLabel.setColspan(2);
total.addDataCell("3,330");

add(table);
```

### <a id="associating-cells-with-headers"></a>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.

```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.

## <a id="column-groups"></a>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`.

`TableColumnGroups.java`

```java
Table table = new Table();
table.setCaptionText("Subscription plans");

TableColumnGroup columns = table.addColumnGroup();
columns.addColumn(2);
TableColumn recommended = columns.addColumn();
recommended.setClassName("highlight");

table.addHeaderRow("Feature", "Standard", "Enterprise");
table.addRowWithHeader("Seats", "10", "Unlimited");
table.addRowWithHeader("Support", "Business hours", "24/7");

add(table);
```

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:

```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.

## <a id="binding-rows-to-a-signal"></a>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.

`TableSignalRows.java`

```java
ListSignal<Planet> planets = new ListSignal<>();
planets.insertLast(new Planet("Mercury", "0.330", "4,879"));
planets.insertLast(new Planet("Venus", "4.87", "12,104"));

Table table = new Table();
table.setCaptionText("Data about the planets of our solar system");
table.addHeaderRow("Name", "Mass (10^24 kg)", "Diameter (km)");

table.getBody().bindChildren(planets, planetSignal -> {
    Planet planet = planetSignal.peek();
    TableRow row = new TableRow();
    row.addRowHeaderCell(planet.name());
    row.addDataCells(planet.mass(), planet.diameter());
    return row;
});

Button addEarth = new Button("Add Earth", event -> planets
        .insertLast(new Planet("Earth", "5.97", "12,756")));
```

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>`.

```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](https://vaadin.com/docs/next/flow/ui-state/building-ui.md) for more about signal bindings.

## <a id="styling"></a>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:

```java
table.setClassName("pricing-table");
```

```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;
}
```

## <a id="testing"></a>Testing

The `flow-html-components-testbench` module provides `TableElement` and a matching element class for each component in the family. See [Testing](https://vaadin.com/docs/next/flow/testing.md) 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:

```java
TableElement table = $(TableElement.class).id("sales");

Assert.assertEquals(3, table.getColumnCount());
Assert.assertEquals("Fruit", table.getCellCovering(2, 0).getText());
```

## <a id="migrating-from-native-table"></a>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       |
| ----------------------- | ----------------- |
| `NativeTable`           | `Table`           |
| `NativeTableCaption`    | `TableCaption`    |
| `NativeTableHeader`     | `TableHead`       |
| `NativeTableBody`       | `TableBody`       |
| `NativeTableFooter`     | `TableFoot`       |
| `NativeTableRow`        | `TableRow`        |
| `NativeTableHeaderCell` | `TableHeaderCell` |
| `NativeTableCell`       | `TableDataCell`   |

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.

## <a id="related-components"></a>Related Components

| Component                                                         | Usage Recommendation                                                     |
| ----------------------------------------------------------------- | ------------------------------------------------------------------------ |
| [Grid](https://vaadin.com/docs/next/components/grid.md)           | For displaying, sorting, filtering, and lazily loading application data. |
| [Grid Pro](https://vaadin.com/docs/next/components/grid-pro.md)   | For a data grid with inline editing.                                     |
| [Tree Grid](https://vaadin.com/docs/next/components/tree-grid.md) | For hierarchical data.                                                   |
