Skip to content
sheetsmith

Styles, slots and the cascade

The look of a table is described with two concepts.

A named style is a set of formatting attributes, declared once with @ExcelStyle and identified by a name. A named style does nothing on its own.

A slot is a place where a named style is applied, by name. The slots are:

Slot Declared in Applies to
titleStyle @ExcelSheet the title row
header.base @ExcelSheet(header = @HeaderStyles(...)) every header cell
header.firstColumn, header.lastColumn @HeaderStyles the header cell of the first or last column
headerStyle @ExcelColumn the header cell of one column
body.base @ExcelSheet(body = @BodyStyles(...)) every data cell
body.odd, body.even @BodyStyles the data cells of odd or even rows
body.firstRow, body.lastRow @BodyStyles the data cells of the first or last data row
body.firstColumn, body.lastColumn @BodyStyles the data cells of the first or last column
styles.base, styles.odd, styles.even, styles.firstRow, styles.lastRow @ExcelColumn(styles = @ColumnStyles(...)) the data cells of one column, optionally restricted to odd, even, first or last rows

One named style can be referenced by any number of slots.

@ExcelSheet(
header = @HeaderStyles(base = "header"),
body = @BodyStyles(odd = "zebra", lastRow = "total"))
@ExcelStyle(name = "header", bold = Toggle.TRUE, fillColor = "#1F4E79", fontColor = "#FFFFFF")
@ExcelStyle(name = "zebra", fillColor = "#EEF3F8")
@ExcelStyle(name = "total", bold = Toggle.TRUE, borderTop = Border.DOUBLE)
public record InvoiceLine(
@ExcelColumn(header = "Description", order = 10) String description,
@ExcelColumn(header = "Amount", order = 20, format = "#,##0.00") BigDecimal amount) {
}

Every attribute of a named style, apart from its name, has a default that means unset. An unset attribute overrides nothing: it leaves the value decided by the levels below. Because annotation attributes cannot be null, “unset” is represented as follows:

Kind of attribute Unset value
Enum attributes (align, border, fillPattern, underline, …) the constant INHERIT of the enum
Yes or no attributes (bold, wrapText, locked, …) Toggle.INHERIT (the three states are INHERIT, TRUE, FALSE)
Numeric attributes (rotation, indent, fontSize, and @ExcelColumn.width) ExcelStyle.UNSET, equal to Integer.MIN_VALUE
Text attributes (colours, font name, data format) the empty string

Integer.MIN_VALUE is used instead of -1 because -1 is a valid rotation. A plain boolean is not used for yes or no attributes because it cannot express “unset”: Toggle.FALSE explicitly switches an attribute off, while Toggle.INHERIT leaves it to lower levels.

The role of a data cell is its position in the table, and decides which slots apply to it:

  • odd or even row. Data rows are numbered from 1, in list order, so the first data row is odd.
  • first row and last row. The first and the last element of the data list.
  • first column and last column. By position after sorting by order.

Roles are independent of each other. With one data row, that row is both first and last. With one column, it is both first and last. A header cell has only the first and last column roles. The title has no role.

The effective style of a cell is obtained by laying the applicable styles on top of each other, from the least to the most specific, attribute by attribute. Each level overrides only the attributes it sets and keeps the others. Attributes that no level sets keep the Excel defaults (Calibri 11, no fill, no border, general alignment, General format).

Data cells, from the least to the most specific level:

Level Source
1 the preset body layers, if a preset applies: preset base, then preset odd or even
2 body.base
3 body.odd or body.even
4 the edges of the outer frame (outerBorder)
5 body.lastColumn, then body.firstColumn
6 body.lastRow, then body.firstRow
7 column styles.base
8 column styles.odd or styles.even
9 column styles.lastRow, then styles.firstRow
10 the column format, as data format
(final) the application default format for the kind of value, only when no level set a format

Header cells, from the least to the most specific level:

Level Source
1 the preset header layer, if a preset applies
2 header.base
3 the edges of the outer frame
4 header.lastColumn, then header.firstColumn
5 the headerStyle of the column

Title: the preset title layer, if a preset applies, then titleStyle.

Precedence rules that follow from the cascade

Section titled “Precedence rules that follow from the cascade”

The order of the cascade produces a small number of rules worth remembering:

  1. The row wins over the column. When a body row slot (firstRow, lastRow) and a body column slot (firstColumn, lastColumn) set the same attribute on the same cell, the row slot wins, because it is applied later.
  2. The first wins over the last. With one data row, lastRow is applied before firstRow, so firstRow wins. With one column, firstColumn wins over lastColumn. The same holds for the header column slots and for the column row slots.
  3. The column is the most specific level. Column slots come after every table level, so a column can always override the table. Only the column format comes after them.
  4. Header and data are separate. Header slots and column header styles never apply to data cells. Body and column slots never apply to header cells. None of them applies to the title. The header does not inherit anything from the body.
  5. The frame yields to role slots. The outer frame sits above the base and odd or even levels and below the role slots: a first or last row or column slot, a column slot or a column header style that sets a border side overrides the frame on that side.
  6. The column format is final. @ExcelColumn.format wins over the dataFormat of every style applied to the cell and over the application default formats.
  7. Local wins over shared. A named style declared on the sheet class replaces a style with the same name coming from a style sheet, entirely: the two are not merged.
  8. Any declared style wins over the preset. The preset is the lowest level of every cascade.

A preset is a ready-made table style generated by the library from one accent colour. It produces a title layer, a header layer and body layers (base, odd, even), applied below every declared style. A preset is chosen per sheet class (@ExcelSheet.preset) or for the whole application (application defaults), and can be adjusted with ordinary named styles. Presets are described in section 7.