Themes
A theme is a named set of styles for the parts every report has: the title, headers, group headers, summary rows and body cells. Define it once, apply it to a schema or to a single table, and columns stop repeating the same CellStyle objects.
How styles stack
Every cell's style is merged from up to six layers. A higher layer overrides the properties it sets and keeps the rest from below.
- wins
- 1Conditional styles
conditionalStyleExcel applies it on top whenever the rule is true - 2Column style
style, headerStylestatic, or computed per row with ({ row, ctx }) => - 3Table defaults
table(…, { defaults })per-export overrides of any slot - 4Table theme
table(…, { theme })one workbook, several looks - 5Schema theme
.theme(theme)the report's default look - 6Library defaults
built inthin borders, bold headers - fallback
The pages for each layer: Cell Styles, Dynamic Styles, Conditional Styles.
Start from a preset
spreadsheetThemes ships five ready themes: classic (the default), slate, forest, rose and ocean.
import { createExcelSchema, spreadsheetThemes } from "typed-xlsx";
const schema = createExcelSchema<{ owner: string; amount: number }>()
.theme(spreadsheetThemes.forest)
.column("owner", { accessor: "owner" })
.column("amount", { accessor: "amount" })
.build();
Change the tokens
Every theme is built from tokens: a palette (titleFill, headerFill, headerText, summaryFill, inputFill, hyperlink, the sparkline colors and more) and two border styles. Overriding a token recolors every slot that uses it, so a brand theme is often just a few colors.
import { defineSpreadsheetTheme } from "typed-xlsx";
const brand = defineSpreadsheetTheme({
tokens: {
colors: {
titleFill: "0E3B26",
headerFill: "17603D",
headerText: "FFFFFF",
summaryFill: "E7F4EC",
},
},
});
Override slots
Slots are the styled parts of a table: title, groupHeader, groupHeaderFiller, header, summary, and the body states cellBase, cellLocked, cellUnlocked, cellHidden and hyperlink. A slot takes a CellStyle, or a function of the tokens when it should follow the palette. .extend() derives a variant that changes only what it lists.
import { createExcelSchema, defineSpreadsheetTheme, createWorkbook } from "typed-xlsx";
type Deal = { amount: number; owner: string };
const boardTheme = defineSpreadsheetTheme({
tokens: { colors: { headerFill: "0B1220", headerText: "F8FAFC" } },
slots: {
title: ({ tokens }) => ({
fill: { color: { rgb: tokens.colors.headerFill } },
font: { color: { rgb: tokens.colors.headerText }, bold: true, size: 14 },
}),
summary: { font: { bold: true } },
},
});
const watchlistTheme = boardTheme.extend({
slots: {
title: { fill: { color: { rgb: "450A0A" } } },
groupHeader: { fill: { color: { rgb: "FECACA" } }, font: { color: { rgb: "9F1239" } } },
},
});
const schema = createExcelSchema<Deal>()
.theme(boardTheme)
.column("owner", { accessor: "owner" })
.column("amount", { accessor: "amount" })
.build();
createWorkbook().sheet("Board").table("deals", {
title: "Executive Watchlist",
rows: [],
schema,
theme: watchlistTheme,
});
The watchlist keeps the board's header and summary and only changes the title and group header.
defineSpreadsheetTheme({ tokens }) recolors every default slot, but tokens passed to .extend() only reach the slots passed in that same call. To recolor a whole theme, define a new one from tokens.Reuse a slot anywhere
theme.slot(name) returns the slot's CellStyle, so a column can borrow it. Pass a second argument to adjust it.
import { defineSpreadsheetTheme } from "typed-xlsx";
const theme = defineSpreadsheetTheme();
theme.slot("header");
theme.slot("header", { alignment: { horizontal: "left" } });
Pivot slots
Themes also style pivot tables through pivotBody, pivotFrame, pivotHeader, pivotSubheading, pivotTotal and pivotStripe. Each is bound to a table slot by default, such as pivotHeader to header, so a pivot matches the tables of its theme, and keeps following them when you extend the theme. Pivot slots take colours, font styles, fills and borders, which is what an Excel pivot style can change.
Schema theme or table theme
Put the theme on the schema when the report should always look the same. Put it on the table when one workbook shows the same schema in several looks, for example a neutral sheet and a red watchlist.
import { createExcelSchema, defineSpreadsheetTheme, createWorkbook } from "typed-xlsx";
type Deal = { amount: number; owner: string };
const baseTheme = defineSpreadsheetTheme({
slots: {
header: {
font: { bold: true, color: { rgb: "F8FAFC" } },
fill: { color: { rgb: "1E293B" } },
},
},
});
const warmTheme = baseTheme.extend({
slots: { header: { fill: { color: { rgb: "7C2D12" } } } },
});
const schema = createExcelSchema<Deal>()
.theme(baseTheme)
.column("owner", { accessor: "owner" })
.column("amount", { accessor: "amount" })
.build();
const workbook = createWorkbook();
workbook.sheet("Board").table("deals", { rows: [], schema });
workbook
.sheet("Watchlist")
.table("deals-watch", { rows: [], schema, theme: warmTheme });
Themes don't protect cells
Themes and conditional styles are visual. Locking and hiding cells is set through style.protection on the column and only takes effect on a protected sheet. See Protection.