Excel Export Theming Implementation
How exportToExcel() produces a spreadsheet that matches the grid's active
theme, and how custom themes declare their own export palette. This builds on
the Export to Excel Implementation and the
Themes Implementation; this document is the
authoritative design intent.
Summary
The grid owns its export palette. A host no longer needs to re-type each
theme's colours to style an export — exportToExcel() themes the sheet to
match the active theme by default:
grid.exportToExcel(); // colours match the active theme (default)
grid.exportToExcel({ themeStyle: 'auto' }); // same, explicit
grid.exportToExcel({ themeStyle: false }); // opt out: unstyled
Theming is on by default. Pass themeStyle: false to get the legacy
unstyled sheet.
The palette descriptor
A VanillaGridExportPalette is a small object of bare Excel hex strings
(RRGGBB / AARRGGBB, no leading #):
interface VanillaGridExportPalette {
headerBg: string; headerFg: string; headerBorder: string;
cellFg: string; cellBg: string; cellBorder: string;
altBg?: string | null; // alternating-row fill; null/absent ⇒ no banding
}
The grid converts a palette into the three static style objects the export
understands (headerCellStyle, dataCellStyle, dataCellStyleAlt, in the
format described in Export to Excel § Cell Styling)
— a bold, vertically-centred header, solid fills, and uniform thin
borders. Because they are static objects (not per-cell callbacks) they are
resolved once per export: each is merged with every column's number format and
alignment, giving a handful of entries in the workbook's cell-format table
(one per column for the header, even rows and odd rows), and each cell simply
references its column's entry.
CSS tokens (primary source)
Each src/vanilla-grid/themes/vn-grid-<name>.css declares the palette on
.vn-grid-table-container, mirroring how --vn-grid-row-height is declared:
| Token | Palette field |
|---|---|
--vn-grid-export-header-bg |
headerBg |
--vn-grid-export-header-fg |
headerFg |
--vn-grid-export-header-border |
headerBorder |
--vn-grid-export-cell-fg |
cellFg |
--vn-grid-export-cell-bg |
cellBg |
--vn-grid-export-cell-border |
cellBorder |
--vn-grid-export-alt-bg |
altBg (none/omit ⇒ no banding) |
_readThemeExportPalette() (on VanillaGrid.prototype) reads all six mandatory
tokens in one getComputedStyle(this.container) call — mirroring
_readThemeRowHeight() — and returns null when any mandatory token is missing
(so resolution falls through to the JS fallback). The shared
themes/TEMPLATE-vn-grid-theme.css carries the same token block so new and
custom themes inherit the contract.
Resolution precedence
exportToExcel() resolves the effective styling in resolveExportStyles()
(exposed on window.VanillaGridExcelExportInternals for testing). Precedence
is resolved per style slot — headerCellStyle, dataCellStyle, and
dataCellStyleAlt each independently pick their own winner, so an explicit
value for one slot does not suppress theming for the others:
- An explicit
headerCellStyle/dataCellStyle/dataCellStyleAlt(whichever is present) → used as-is for that slot (a raw style object; existing behaviour). - Otherwise, if
themeStyleresolves to a palette, that slot falls back to the palette's built style for it:themeStyle: 'auto'(the default whenthemeStyleis omitted) → resolve a palette:- CSS tokens via
_readThemeExportPalette(), else - JS registry
VanillaGridElement.getThemeExportStyle(themeName), else - the grid's built-in fallback table for
themeName, else - the built-in
defaultpalette.
- CSS tokens via
themeStyleis a palette object → build styles from it.
- Otherwise (
themeStyle === false, or no palette resolved) → that slot stays unstyled.
This means { headerCellStyle: myStyle } (with themeStyle left at its
'auto' default) keeps your explicit header style and themes the data
rows — it does not disable theming for the whole export the way an
all-or-nothing "first hit wins" reading might suggest.
The active themeName is read from the host element via
grid.container.closest('vn-grid')?.dataset.theme (set by the element's
attributeChangedCallback), falling back to 'default'. The CSS-token path
(2.i) works for both built-in and custom CSS themes without ever needing the
theme name, so it covers most cases on its own.
headersOnly
exportToExcel({ headersOnly: true }) keeps the resolved header style but forces
dataCellStyle/dataCellStyleAlt to null, so data cells carry no fill,
border or font colour. Column-level numFmt/alignment still apply. The modifier
is applied after a palette is chosen, so it composes with any themeStyle.
Custom-theme story
Three ways to give a custom theme an export palette, all parallel to how custom themes are registered:
CSS (preferred, zero JS): the stylesheet referenced by
theme-css-path/registerTheme(name, cssPath)declares the--vn-grid-export-*tokens. Export follows the theme automatically.Static registration:
VanillaGridElement.registerThemeExportStyle(name, palette)— sits besideregisterTheme(name, cssPath); useful when the custom theme's CSS is third-party / not editable.VanillaGridElement.registerThemeExportStyle('corp', { headerBg: '1B3A5C', headerFg: 'FFFFFF', headerBorder: 'AAB7C4', cellFg: '1A1A1A', cellBg: 'FFFFFF', cellBorder: 'E3E8EC', altBg: 'F2F6F9', });Per-call:
exportToExcel({ themeStyle: palette })— a one-off override.
API surface (additive)
| Member | Where | Description |
|---|---|---|
exportToExcel({ themeStyle?, headersOnly? }) |
VanillaGrid / <vn-grid> |
themeStyle: 'auto' (default) | VanillaGridExportPalette | false. headersOnly: boolean. |
_readThemeExportPalette() |
VanillaGrid.prototype |
Reads --vn-grid-export-*; mirrors _readThemeRowHeight(). |
static registerThemeExportStyle(name, palette) |
VanillaGridElement |
Register/override a named theme's export palette. |
static getThemeExportStyle(name) |
VanillaGridElement |
Lookup (returns null when unknown). |
Migrating from the opt-in default
themeStyle used to default to unstyled (false); it now defaults to
'auto'. This is a deliberate breaking change to the default — the option
itself, and its precedence rules, are unchanged:
- Callers passing explicit
*CellStylefor all three slots → unchanged (rule 1 covers every slot, sothemeStyle's new default never gets consulted). - Callers passing explicit
*CellStylefor some but not all slots, with nothemeStyle→ behavior change: the previously-unset slots now pick up the'auto'theme palette instead of staying unstyled (rule 2 now applies where rule 3 used to). - Callers passing nothing → behavior change: the export is now themed
(rule 2,
'auto') instead of unstyled (rule 3). PassthemeStyle: falseto keep the old unstyled output. - The eight built-in theme CSS files carry the token block (additive; no visual change to on-screen rendering).
northwind-orders-js,-3,-4,-6, and-7each hand-rolled agetExcelThemeStyles()palette duplicate — all deleted, since the grid's default now does the same job.people-cities-jsandgrid-minimal-jsalready relied onthemeStyle: 'auto'(or the toolbar's standard command, for grid-minimal-js) and simply dropped the now-redundant explicit option.