Schema Builder
Styling Overview
[object Object]
Styling in typed-xlsx now spans three different models. They share the same visual style vocabulary, but they run at different times and solve different problems.
The three styling models
| Styling model | Field | Evaluated when | Best for |
|---|---|---|---|
| Base styling | style: CellStyle | export time | Stable formatting such as number formats, alignment, fills, and borders |
| Build-time dynamic styling | style: (row, rowIndex, subRowIndex) => CellStyle | export time | Styling based on source row data known to JavaScript |
| Formula-based conditional styling | conditionalStyle | inside Excel after open | Styling that should react to formulas or later user edits |
All three can coexist on the same column.
Mental model
styledefines the base appearance of the data cellstyle(row => ...)lets the export process choose a style from the source dataconditionalStyleemits native Excel conditional formatting rules that remain live in the workbookheaderStyleis separate and only affects the header row- table-level
.table({ defaults })can provide shared defaults for headers, summaries, and locked/unlocked/hidden cells
Important limitation:
conditionalStyleis visual only- it cannot configure worksheet protection (
locked/hidden) - protection stays a regular cell style concern and only takes effect when the sheet itself is protected
For example:
import { createExcelSchema } from "@chronicstone/typed-xlsx";
type Deal = {
amount: number;
quota: number;
status: "open" | "won" | "at-risk";
};
const schema = createExcelSchema<Deal>()
.column("amount", {
accessor: "amount",
style: {
numFmt: "$#,##0.00",
alignment: { horizontal: "right" },
},
})
.column("quota", {
accessor: "quota",
style: {
numFmt: "$#,##0.00",
alignment: { horizontal: "right" },
},
})
.column("attainment", {
formula: ({ row, fx }) =>
fx.if(row.ref("quota").gt(0), row.ref("amount").div(row.ref("quota")), 0),
style: {
numFmt: "0.0%",
alignment: { horizontal: "right" },
},
conditionalStyle: (conditional) =>
conditional.when(({ row }) => row.ref("attainment").lt(0.5), {
fill: { color: { rgb: "FEE2E2" } },
font: { color: { rgb: "991B1B" }, bold: true },
}),
})
.build();
In that example:
- the base number format and alignment come from
style - the warning fill/font comes from
conditionalStyle - Excel reevaluates the warning rule after the workbook opens
Summary cells are different
Summary cells now support styling too, but the exact capabilities depend on the summary kind:
- reducer summaries support static
style, dynamicstyle(value), andconditionalStyle - formula summaries support static
styleandconditionalStyle
That split exists because reducer summaries have a JavaScript-resolved final value during export, while formula summaries stay live in Excel.