Applying a theme to a workbook
In this guide we’ll create a workbook and apply a custom theme to it: a colour scheme, heading and body fonts, and a set of brand colours. We’ll also update individual parts of the theme at runtime and export the result to an Excel .xlsx file.
This is useful whenever you’re generating spreadsheets programmatically and want them to carry a specific visual identity — a brand palette, a house style, or a client-facing design system.
-
Create a project directory and initialise it:
Terminal window mkdir workbook-themescd workbook-themesnpm init -ynpm pkg set type=module -
Configure npm to access packages in the
@grid-isscope. Create a.npmrcfile:Terminal window cat << 'EOF' > .npmrc@grid-is:registry=https://registry.npmjs.org//registry.npmjs.org:_authToken=${NPM_TOKEN}EOF -
Add Apiary as a dependency:
Terminal window npm add @grid-is/apiary -
Create an
index.jsfile with the following code:import { Model } from "@grid-is/apiary";// Initialise the engine (required for the WASM-based formula parser).await Model.preconditions;// Create a model containing a workbook with a single sheet.const model = new Model();const workbook = model.addWorkbook({name: "quarterly-report.xlsx",sheets: [ { name: "Q1 Report", cells: {} } ],});// Apply a theme. The theme defines the colour palette and fonts that Excel// uses when applying themed cell styles. All twelve colour slots are required.workbook.theme.set({name: "Acme Corp",colorScheme: {name: "Acme colours",lt1: { type: "srgb", value: "FFFFFF" },dk1: { type: "srgb", value: "1C2B4B" }, // deep navylt2: { type: "srgb", value: "F4F6F9" },dk2: { type: "srgb", value: "2E4A7A" }, // mid navyaccent1: { type: "srgb", value: "0072CE" }, // primary blueaccent2: { type: "srgb", value: "E8380D" }, // alert redaccent3: { type: "srgb", value: "00A88F" }, // tealaccent4: { type: "srgb", value: "F5A623" }, // amberaccent5: { type: "srgb", value: "7B5EA7" }, // purpleaccent6: { type: "srgb", value: "4A9D4F" }, // greenhlink: { type: "srgb", value: "0072CE" },folHlink: { type: "srgb", value: "4A4A6A" },},fontScheme: {name: "Acme fonts",major: { latin: { typeface: "Georgia" } }, // used for headingsminor: { latin: { typeface: "Arial" } }, // used for body text},});console.log("Theme applied:", workbook.theme.get().name);console.log("Major font: ", workbook.theme.getMajorFont().latin.typeface);console.log("Minor font: ", workbook.theme.getMinorFont().latin.typeface);// Populate a few cells so the theme has something to work with. Apply the theme's major font to// the header cells, and the minor font to the others.const majorFont = workbook.theme.getMajorFont().latin.typeface;const minorFont = workbook.theme.getMinorFont().latin.typeface;workbook.writeCellData("A1", { v: "Region", s: { fontFamily: majorFont, bold: true } });workbook.writeCellData("B1", { v: "Revenue", s: { fontFamily: majorFont, bold: true } });workbook.writeCellData("A2", { v: "North", s: { fontFamily: minorFont } });workbook.writeCellData("B2", { v: 142500, s: { fontFamily: minorFont } });workbook.writeCellData("A3", { v: "South", s: { fontFamily: minorFont } });workbook.writeCellData("B3", { v: 98300, s: { fontFamily: minorFont } });workbook.writeCellData("A4", { v: "Total", s: { fontFamily: minorFont } });workbook.writeCellData("B4", { f: "SUM(B2:B3)", s: { fontFamily: minorFont } });// The brand team have refreshed the palette --- update just the colour scheme,// keeping all other theme properties intact.workbook.theme.setColorScheme({...workbook.theme.getColorScheme(),dk1: { type: "srgb", value: "2C1810" }, // dark chocolatedk2: { type: "srgb", value: "5C3D2E" }, // mid brown});console.log("Dark 1 colour updated to:", workbook.theme.getColorScheme().dk1.toString());// Add custom colours for callout cells used across our reporting templates.workbook.theme.addCustomColor({ name: "Highlight", color: { type: "srgb", value: "FFF3CD" } });workbook.theme.addCustomColor({ name: "Warning", color: { type: "srgb", value: "F8D7DA" } });console.log("Custom colours:", workbook.theme.getCustomColors().map(c => c.name).join(", "));// On reflection, we only need "Highlight" in this template --- remove "Warning".workbook.theme.removeCustomColor(1);console.log("After removal: ", workbook.theme.getCustomColors().map(c => c.name).join(", "));// Export to .xlsx.await workbook.toXlsxFile("quarterly-report.xlsx");console.log("Saved quarterly-report.xlsx"); -
Run the script:
Terminal window node --experimental-wasm-modules index.jsYou should see output like this:
Theme applied: Acme CorpMajor font: GeorgiaMinor font: ArialDark 1 colour updated to: 2C1810Custom colours: Highlight, WarningAfter removal: HighlightSaved quarterly-report.xlsxOpen
quarterly-report.xlsxin Excel and check Page Layout → Themes — you’ll find the Acme Corp theme applied, and the custom colour “Highlight” will be available in the colour picker under “Custom colours”.
How the theme API works
Section titled “How the theme API works”All theme operations go through workbook.theme, which is a ThemeManager instance.
set(theme)replaces the whole theme in one call. Use this when you have a complete theme to apply.setColorScheme(scheme),setMajorFont(font), andsetMinorFont(font)let you replace individual parts without touching the rest. They work even if no explicit theme has been set (the built-in Office default is used as the starting point).get(),getColorScheme(),getMajorFont(),getMinorFont(), andgetCustomColors()return frozen references to the stored data. Attempting to mutate a returned object will throw at runtime.addCustomColor(color)appends a custom colour.removeCustomColor(index)removes one by its zero-based index in the list.isDefaultistruewhen the workbook is using the built-in Office default theme andfalseonce any write has been made. Useful for deciding whether to include the theme when serialising a workbook.
Colours use the { type, value } shape matching the Color type from the @jsfkit/types package. The most common type is "srgb", where value is a six-digit hex string without a leading # (see SrgbColor).
For more details, see the API documentation for ThemeManager.