Skip to content

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.

  1. Create a project directory and initialise it:

    Terminal window
    mkdir workbook-themes
    cd workbook-themes
    npm init -y
    npm pkg set type=module
  2. Configure npm to access packages in the @grid-is scope. Create a .npmrc file:

    Terminal window
    cat << 'EOF' > .npmrc
    @grid-is:registry=https://registry.npmjs.org
    //registry.npmjs.org:_authToken=${NPM_TOKEN}
    EOF
  3. Add Apiary as a dependency:

    Terminal window
    npm add @grid-is/apiary
  4. Create an index.js file 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 navy
    lt2: { type: "srgb", value: "F4F6F9" },
    dk2: { type: "srgb", value: "2E4A7A" }, // mid navy
    accent1: { type: "srgb", value: "0072CE" }, // primary blue
    accent2: { type: "srgb", value: "E8380D" }, // alert red
    accent3: { type: "srgb", value: "00A88F" }, // teal
    accent4: { type: "srgb", value: "F5A623" }, // amber
    accent5: { type: "srgb", value: "7B5EA7" }, // purple
    accent6: { type: "srgb", value: "4A9D4F" }, // green
    hlink: { type: "srgb", value: "0072CE" },
    folHlink: { type: "srgb", value: "4A4A6A" },
    },
    fontScheme: {
    name: "Acme fonts",
    major: { latin: { typeface: "Georgia" } }, // used for headings
    minor: { 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 chocolate
    dk2: { 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");
  5. Run the script:

    Terminal window
    node --experimental-wasm-modules index.js

    You should see output like this:

    Theme applied: Acme Corp
    Major font: Georgia
    Minor font: Arial
    Dark 1 colour updated to: 2C1810
    Custom colours: Highlight, Warning
    After removal: Highlight
    Saved quarterly-report.xlsx

    Open quarterly-report.xlsx in 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”.

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), and setMinorFont(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(), and getCustomColors() 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.
  • isDefault is true when the workbook is using the built-in Office default theme and false once 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.