Column Groups
Column groups let you organize related columns under shared, multi-row headers. A group label spans across its child columns, making it easier for users to scan and understand the structure of complex tables. Groups can be nested to any depth, and the Table automatically renders the correct number of header rows.
Creating column groups
Create a group with the ColumnGroup.of() factory method, then chain add() calls to populate it with column references, other groups, or a mix of both. Apply the groups to a Table with setColumnGroups().
Toon Code
- Java
When groups are set, the Table renders a multi-row header where each group label spans across its child columns. The nesting depth determines how many header rows appear. A flat group adds one extra row, while a two-level nesting adds two, and so on.
Toon Code
- Java
Groups can be set or changed at any time, before or after the Table is rendered. Pass null or an empty list to setColumnGroups() to remove all grouping and return to a single-row header.
// Remove all column groups
table.setColumnGroups(null);
Column ordering
When groups are active, the visual column order is determined by the group tree rather than the order in which columns were added to the Table. The tree is walked depth-first, left to right.
Columns added: [A, B, C, D, E]
Groups: Group "G1" [C, A], Group "G2" [E, D]
Visual order: C, A, E, D, B
Ungrouped columns, those not referenced in any group, aren't hidden. They appear at their natural position relative to grouped columns, based on the order they were originally added to the Table.
In this example, Number appears first because it was added before Title. Label appears between Genre and Cost because it was added between them in the original column order:
Columns added: [Number, Title, Artist, Genre, Label, Cost]
Groups: Group "Music" [Title, Artist, Genre], Group "Pricing" [Cost]
Visual order: Number, Title, Artist, Genre, Label, Cost
The following demo illustrates this behavior. Number and Label aren't referenced in any group, but they retain their natural positions based on the order they were added to the Table.
Toon Code
- Java
To control ungrouped column placement explicitly, include them as top-level column references in the group tree.
Column moving within groups
When groups are active, drag-and-drop column movement is constrained to maintain group integrity:
- Within a group: a column inside a group can only be moved within its immediate parent group. Dragging it outside the group is rejected, and the column snaps back to its original position.
- Ungrouped columns: an ungrouped column can only move to positions occupied by other ungrouped columns. It can't be dropped into the middle of a group.
- Reordering groups: an entire group can be dragged to reorder it among its siblings at the same nesting level.
Groups: Group "G1" [A, B, C], Group "G2" [D, E]
Move A to position 2 -> OK (within G1, result: [B, C, A])
Move A to position 3 -> Rejected (position 3 is inside G2)
Move D to position 4 -> OK (within G2, result: [E, D])
Move D to position 1 -> Rejected (position 1 is inside G1)
Pinning groups
A group can be pinned left or right using setPinDirection(). All columns inside the group inherit the group pin direction, and individual column pin settings are overridden by the group.
ColumnGroup idInfo = ColumnGroup.of("id-info", "ID Info")
.setPinDirection(PinDirection.LEFT)
.add("number")
.add("title");
// Both "number" and "title" are pinned left,
// regardless of their own pin settings
Ungrouped columns retain their own pin direction from their column definition.
Toon Code
- Java
Group header height
Group header row height can be controlled independently from regular column headers using setGroupHeaderHeight().
table.setGroupHeaderHeight(32); // Group rows are 32px tall
table.setHeaderHeight(48); // Column header row stays at 48px
The default group header height matches the default header height.
Styling groups with CSS parts
Group headers and columns expose CSS parts for styling via ::part(). The following parts are available:
| Part | Description |
|---|---|
cell-group-{ID} | Group header cell, targeted by group ID |
cell-group-depth-{N} | Group header cell, targeted by depth (0 = top-level, 1 = second-level, etc.) |
cell-column-{ID} | All cells (header and body) for a given column ID |
cell-content-group-{ID} | Content wrapper within a group header |
cell-label-group-{ID} | Label within a group header |
Toon Code
- Java
- CSS
Styling by group ID
Use the group ID to target specific groups with unique colors or typography.
dwc-table::part(cell-group-catalog) {
background: var(--dwc-color-primary-30);
color: var(--dwc-color-primary-text-30);
font-weight: 600;
}
dwc-table::part(cell-group-bio) {
background: var(--dwc-color-primary-40);
color: var(--dwc-color-primary-text-40);
}
dwc-table::part(cell-column-title) {
font-weight: 600;
}
Styling by depth
Depth-based parts are useful when you want to apply consistent styling to all groups at a certain nesting level without targeting each group individually.
/* Style all top-level groups */
dwc-table::part(cell-group-depth-0) {
background: var(--dwc-color-primary-30);
font-weight: 700;
}
/* Style all second-level groups */
dwc-table::part(cell-group-depth-1) {
background: var(--dwc-color-primary-40);
}
Hidden columns
Hidden columns are excluded from the visual order and the header layout. If a group contains a mix of visible and hidden columns, only the visible ones appear and the group colspan adjusts accordingly. If every column in a group is hidden, the group header isn't rendered at all.
Toon Code
- Java