Skip to content

Themes & styling ​

json-to-office keeps content and appearance separate: your document JSON says what to render, a theme says how it should look. Because colors and fonts are referenced through semantic tokens rather than hard-coded values, swapping one theme name restyles an entire document — no find-and-replace across your JSON.

Semantic color tokens ​

Anywhere a component accepts a color, you can pass either a literal hex value ("#0066CC") or a semantic token ("primary", "accent", ...). Tokens are resolved against the active theme at generate time. Authoring with tokens is what makes documents portable across themes, so prefer them over raw hex whenever the color has a role (brand color, muted text, subtle border) rather than a one-off value.

The two formats use different token schemes.

DOCX: 13 required colors + 3 optional ​

Word themes define thirteen required color keys, plus three optional chart-series slots:

GroupTokensRequired
Coreprimary, secondary, accentyes
Texttext, textPrimary, textSecondary, textMutedyes
Backgroundbackground, backgroundPrimary, backgroundSecondaryyes
Borderborder, borderPrimary, borderSecondaryyes
Chartaccent4, accent5, accent6no

accent4–accent6 carry the same names as the PPTX slots below. They exist for the chart palette, but once a theme defines one it is an ordinary token: any component color prop can name it. Every built-in DOCX theme fills all three (each with chart-series colors in its own palette), so "accent4" resolves under any bundled theme; under a custom theme that leaves them unset, referencing one throws like any other unknown name.

Resolution rules:

  • A #RRGGBB value is validated and used as-is; an invalid hex string throws at generate time.
  • Any other string is looked up in the theme's color map (merged over built-in defaults) and resolved recursively — a theme color's value may itself be another token name (e.g. "border": "backgroundSecondary"), and the chain is followed until a hex value is reached.
  • An unknown token name throws. DOCX color resolution is strict by design: a typo fails the build instead of silently producing a wrong color.

PPTX: 10-slot scheme ​

PowerPoint themes use a 10-slot palette. Five slots are required, five optional:

SlotRequiredTypical role
primaryyesMain brand color, first chart series
secondaryyesSecond brand color
accentyesHighlight color
backgroundyesSlide background
textyesMain text color
text2noSecondary text (subtitles, captions)
background2noAlternate background (cards, stripes)
accent4, accent5, accent6noExtra chart-series colors

PowerPoint-style aliases are accepted anywhere a token is: accent1 → primary, accent2 → secondary, accent3 → accent, tx1 → text, tx2 → text2, bg1 → background, bg2 → background2. This lets JSON produced from existing PowerPoint conventions resolve naturally.

Resolution is more forgiving than DOCX:

  • If a token points at an optional slot the theme did not fill, the resolver falls back to primary and emits a THEME_COLOR_FALLBACK warning.
  • Literal hex values may omit the #, and 3-digit shorthand is expanded (FFF → FFFFFF). A string that is neither a token nor valid hex produces an UNKNOWN_COLOR warning.

Charts inherit the palette

The pptx chart and the highcharts component in both formats default their series colors to the same six tokens in the same order — ['primary', 'secondary', 'accent', 'accent4', 'accent5', 'accent6'] — so a theme that fills every slot gives you one on-brand chart palette across a deck and a document.

A theme that fills only some of them behaves identically in both formats too: the unset slot is dropped, the palette is only as long as the theme has tokens defined, and the chart library reuses that shorter list. No warning is emitted — the fallback rule above governs a token named explicitly, not the implicit chart palette. Every built-in DOCX theme fills accent4–accent6, so a docx chart on a bundled theme draws from six curated series colors. See Charts.

The theme Word sees ​

A DOCX document also carries a Word theme (word/theme/theme1.xml), filled from the jto theme with the same slots a twin deck writes:

Word slotWord calls itjto token
dk1Text 1text
lt1Background 1background
dk2Text 2textSecondary
lt2Background 2backgroundSecondary
accent1Accent 1primary
accent2Accent 2secondary
accent3Accent 3accent
accent4–6Accent 4–6accent4–accent6
major font(Headings)the heading family
minor font(Body)the body family

The theme is named after the jto theme (consulting), and the hyperlink colours stay Office's.

On either renderer only a native chart draws from these, as Word's own charts do: its gridlines and axis lines take a tint of Text 1, and a pie or doughnut's slice borders Background 1. Every other colour and font is stated, so apart from those lines the document looks the same under any Word theme. The slots are what someone editing it is offered: the Theme Colors row of each colour menu (Font Color, Shading, Design > Page Color), (Headings) and (Body) in the font menu, and the colours of a chart, SmartArt or table style inserted in Word. Word does not list the scheme by name: its Design > Colors and Design > Fonts galleries hold only Office's built-in schemes and any saved on the machine. Choosing another theme in Word does not restyle jto text.

Unset accent4–accent6 keep Office's colours. A twin deck writes the same slots.

On the experimental office-open renderer a few of Word's own spare styles (Quote, Heading 7–9) and a contents field's first run follow the theme too; jto applies none of those styles.

Shared visual roles ​

Add a type ladder and palette without changing a bundled theme. For example, use this root on a DOCX document:

json
{
  "name": "docx",
  "props": {
    "theme": "vermilion",
    "themeOverrides": {
      "palette": {
        "rule": "#58595B",
        "textMuted": "rule",
        "chart": ["primary", "accent", "rule"]
      },
      "typography": {
        "roles": {
          "display": {
            "face": "heading",
            "weight": 300,
            "case": "upper",
            "color": "primary"
          },
          "source": { "size": 9, "color": "textMuted" }
        },
        "scale": { "a4": { "base": 12, "ratio": 1.25, "baselinePt": 4 } }
      },
      "spacing": { "canvas": { "a4": { "safeAreaIn": 0.75 } } }
    }
  },
  "children": [
    {
      "name": "paragraph",
      "props": { "text": "A stronger report", "themeStyle": "display" }
    },
    {
      "name": "paragraph",
      "props": { "text": "Source: field observations", "themeStyle": "source" }
    }
  ]
}

PPTX themes accept the same layers; use wide169 or standard43 scales and style: "display" on text. A theme controls visual values only. It does not require a title, source line or any other content. Chrome recipes are what the DOCX blocks paint from — key-takeaways, cover and running-head today; motif recipes store values for later rendering work. See the shared visual reference for precedence, units and case behavior.

Built-in themes ​

DOCX themes ​

Four themes are always registered (statically imported, so they exist independent of any dist build). consulting is the default and the fallback for unknown names: the house style the design-quality programme builds on, so a document that names no theme is set in it. minimal, devportal and vermilion are alternates you name with props.theme.

ThemeDisplay nameprimarysecondaryaccentHeading fontBody fontMono fontVoice
consultingConsulting House#1A1F26#4B5563#1B4F8AArial 16Calibri 10.5Consolas 9.5The house report style: near-black ink, three greys, one deep blue, hairline rules, no fills behind text
minimalMinimal Clean#2B302B#4A5B4E#6E7F71Calibri 21Calibri 10Courier New 10Quiet and warm: sage-green ink on ivory neutrals, tracked-tight bold title, wide margins
devportalField Editorial#12191F#172028#E35B3FHelvetica 23Helvetica 9.5Courier New 10Compact editorial: near-black ink, burnt-orange accent, condensed titles, warm tints
vermilionVermilion Editorial#282829#58595B#EF4130Arial 24Calibri 10.5Courier New 10Poster-red display headings with wide tracking, ink text, warm creams

Every bundled theme also states whenToUse beside its description: the voice says what it looks like, the use says which documents it suits, and jto_discover, jto://themes and the generated design guide quote both, so an agent chooses a look rather than inventing one.

Every bundled theme defines the full heading1–heading6 ladder, title/subtitle, and componentDefaults for tables, lists, images and statistics — so a document that uses any component gets the theme's treatment without stating anything. consulting, vermilion and devportal carry the shared visual layers in full; consulting was the first: every type role (display 22pt, stat 22pt regular in the accent, chartLabel 9pt, source 8pt and the rest, from a 10.5pt base at ratio 1.2), the palette roles with a blue-and-grey chart series, an 8pt spacing base, and the chrome recipes blocks paint from — the key-takeaways box, the cover, the running head and footer. Safe fonts only — Arial, Calibri, Consolas — so it needs no font setup anywhere (Consolas is safe in Word; the hosted LibreOffice preview substitutes DejaVu for it, with different metrics, and only code spans are set in it), and the chart typography it injects draws with faces every export server host has. It requires nothing: a document on it gets the look, and only a blueprint or profile asks for trackers, sources or footers. minimal and devportal carry the palettes and type of the shipped practice-note and field-review examples, which state no themeOverrides at all; vermilion's table default is the vermilion-annual-report recipe: gray hairline rows, open sides, red bold headers over a red hairline rule. minimal and devportal also define TOC1–TOC3 entry styles. vermilion paints its display, stat and table-header roles in the vermilion, sets its scale at ratio 1.25 with a 0.8in safe area and rules the cover with a 4pt red line; devportal condenses the display role, letterspaces its labels, sets a 9.5pt base at ratio 1.2 with a 0.75in safe area and rules the cover at 3pt. Both keep the chart series ordered from their own accent; both are safe-font themes, so neither needs a registry.

Every family the bundled themes name has a metric-compatible substitute in the hosted playground's LibreOffice preview image (Helvetica/Arial → Liberation Sans, Calibri → Carlito, Courier New → Liberation Mono), so the PDF preview breaks lines where Word does. When picking fonts for a custom theme, prefer those families — or Times New Roman and Cambria, which are covered too; Georgia, Verdana, Menlo, Monaco and Consolas fall back to DejaVu faces with different metrics, and Segoe UI to Carlito.

PPTX themes ​

Five built-in themes: consulting, dark, minimal, vermilion and devportal. consulting is the default and the fallback for unknown names. It is the twin of the DOCX house theme: the same ink, greys, accent, positive/negative, ordered chart series, chrome recipes and motif, Arial over Calibri with Consolas for code — safe fonts only, with the same Consolas caveat as the report themes — so a deck and its report match without restyling. dark and minimal are plain alternates you name with props.theme. vermilion and devportal are twins of the DOCX alternates on the same terms: palette roles, chart series, chrome recipes, motif and font families shared with the report theme of the same name, and an agreement test holds each pair together.

Slotconsultingdarkminimalvermiliondevportal
primary#1A1F26#5B9BD5#000000#282829#12191F
secondary#4B5563#FF6F61#666666#58595B#172028
accent#1B4F8A#6BCB77#999999#EF4130#E35B3F
background#FFFFFF#2D2D2D#FFFFFF#FFFFFF#FFFFFF
text#1A1F26#FFFFFF#000000#282829#232323
text2#4B5563#CCCCCC#444444#58595B#46494C
background2#F2F4F7#3D3D3D#F5F5F5#FAF7F5#F7ECE7
accent4#5B8DC9#FFB347#BBBBBB#F4857A#46494C
accent5#A9C4E4#77DD77#DDDDDD#F9C4BE#8A9299
accent6#7B8794#AEC6CF#888888#939598#E8A18D
Fonts (heading / body)Arial / CalibriArial / ArialHelvetica / HelveticaArial / CalibriHelvetica / Helvetica
Defaults14pt, #1A1F2618pt, #FFFFFF18pt, #00000014pt, #28282914pt, #232323

consulting also carries the shared visual layers: every type role (display 28pt, stat 40pt, quote 18pt, eyebrow 10pt tracked caps, chartLabel 11pt, source and footer 9pt), the palette roles with the same blue-and-grey chart series as the report, a 12 × 8 grid with a half-inch safe area on both 16:9 and 4:3, hairline table borders and gridline-light chart defaults, and the chrome recipes the action-chart block binds. One projection differs from the report on purpose: the small roles — footer, tracker, source — take text2 rather than textMuted, because a 9pt run in the light grey sits under the 4.5 : 1 the contrast rule asks of projected text. A deck on consulting inherits the look and nothing else; only the consulting-deck quality profile asks a slide for a takeaway or a source.

dark and minimal share the same set of style presets; consulting, vermilion and devportal set their own, left-aligned and smaller (title 32pt bold — 30pt on devportal — subtitle 16pt, heading1 24pt bold, body 14pt, caption 10pt), vermilion painting title and heading1 in the accent:

PresetSizeWeight/emphasisColor tokenAlignment
title36boldtextcenter
subtitle20italictext2center
heading128boldprimary—
heading222boldprimary—
heading318boldtext—
body14———
caption10italictext2—

Rendered through LibreOffice from the two playground templates that carry the house theme — the DOCX client-report-blocks report page and the PPTX consulting-deck-blocks action-chart slide — on each of the three extended themes; the alternates are the same two documents with only props.theme changed. In every pair the same ink, greys, accent and chart series; the report's running head and the deck's tracker, source line and page number are painted from the same chrome recipes.

ThemeDOCX report pagePPTX content slide
consultingConsulting report pageConsulting action-chart slide
vermilionVermilion report pageVermilion action-chart slide
devportalDevportal report pageDevportal action-chart slide

Applying a theme ​

Set props.theme on the root component. For DOCX (default "consulting"):

json
{
  "name": "docx",
  "props": { "theme": "devportal" },
  "children": [
    {
      "name": "section",
      "props": {},
      "children": [
        {
          "name": "heading",
          "props": { "text": "Quarterly Report", "level": 1 }
        }
      ]
    }
  ]
}

For PPTX (default "consulting"):

json
{
  "name": "pptx",
  "props": { "theme": "dark" },
  "children": [
    {
      "name": "slide",
      "props": {},
      "children": [
        { "name": "text", "props": { "text": "Q3 Review", "style": "title" } }
      ]
    }
  ]
}

Custom themes ​

PPTX: inline theme object ​

For presentations, props.theme accepts either a theme name or a full inline theme object, keeping the document fully self-contained — one JSON file carries content and branding:

json
{
  "name": "pptx",
  "props": {
    "theme": {
      "name": "brand",
      "colors": {
        "primary": "#0C2340",
        "secondary": "#1B3A5C",
        "accent": "#D4A843",
        "background": "#FFFFFF",
        "text": "#1A1A1A",
        "text2": "#5A6B7C",
        "accent4": "#8A9BAA",
        "accent5": "#C4A35A",
        "accent6": "#2E4A66"
      },
      "fonts": { "heading": "Georgia", "body": "Calibri" },
      "defaults": { "fontSize": 18, "fontColor": "#1A1A1A" }
    }
  },
  "children": []
}

At generate time the inline object is normalized into a named custom-theme entry (using theme.name, or inline-theme if unnamed) before the pipeline runs.

DOCX: customThemes option or CLI flags ​

The DOCX document schema deliberately keeps props.theme string-only. Custom Word themes are supplied out-of-band:

Library — pass a name-to-config map via options.customThemes (exact name match first, then case-insensitive, then built-in fallback):

ts
import { generateAndSaveFromJson } from '@json-to-office/core-docx';
import brandTheme from './brand.docx.theme.json';

await generateAndSaveFromJson(documentJson, 'report.docx', {
  customThemes: { brand: brandTheme },
});

CLI — point at a theme file with --theme-path, or name a built-in with --theme:

bash
# --theme-path loads and registers the theme file; also accepts a JS/TS module
# exporting `default` or `theme`
jto docx generate report.json --theme-path ./brand.docx.theme.json

# --theme selects a built-in (or a customThemes key, or a .json path)
jto docx generate report.json --theme modern

Both flags apply on every run — with plugins loaded and without — and both override the document's own props.theme. They also outrank the config file, as a pair: passing either flag discards both config-file keys (theme and themePath), so a config-file themePath never beats a --theme on the command line. Use no flag and the config file keeps both of its keys, where the theme path is tried before the theme name. With nothing requested anywhere, props.theme applies unchanged. A theme file loaded via --theme-path is additionally registered under its name, so props.theme can still reference it by name. A theme that cannot be resolved leaves props.theme in charge instead of falling back to a default, and warns — Unknown theme "X"; keeping the document's own theme for a --theme value that names nothing, Failed to load theme from <path>: <reason> for a --theme-path that cannot be read. See the CLI reference.

Theme files are loaded with hardening guards (.json only, 10 MB cap, path-traversal checks) — see Theme schema reference for details, and the full file formats for both DOCX and PPTX themes.

componentDefaults cascade ​

Both theme files and documents can set default props per component type, so you can say "every table hides its borders" once instead of on every table. The cascade, weakest to strongest:

  1. Built-in component defaults
  2. Theme componentDefaults
  3. Document props.componentDefaults (deep-merged on top of the theme's)
  4. Props set on the component itself

Merging is a deep merge: nested objects merge key-by-key with the stronger layer winning, arrays are replaced wholesale. A component with no props key at all behaves exactly like one with "props": {} — it picks up the same defaults; there is no carve-out for propless nodes.

Whether the key may be omitted is a separate question

Defaults treat a missing props as {}; validation does not accept it everywhere. Each registry entry decides, and the published JSON Schema is generated from that same decision, so an editor wired to $schema and the validator agree. In DOCX the key is omissible on section, toc, image, text-box and the docx root; in PPTX only on slide — the pptx root requires it (write "props": {}), and so does every content component. See Validation for the full rule.

Only real component props are valid componentDefaults. For table that means borderColor, borderSize, hideBorders, cellDefaults, headerCellDefaults, columns, width, keepInOnePage, keepNext, and repeatHeaderOnPageBreak — the schema is a strict partial of the component's props, so an invented key such as striped, borders, borderWidth, headerBackground or headerColor fails theme validation. Header styling goes through headerCellDefaults (backgroundColor, color), border width through borderSize, and borders on or off through hideBorders.

json
{
  "name": "docx",
  "props": {
    "theme": "minimal",
    "componentDefaults": {
      "table": {
        "repeatHeaderOnPageBreak": true,
        "cellDefaults": { "padding": 6 }
      }
    }
  },
  "children": [
    {
      "name": "section",
      "props": {},
      "children": [
        {
          "name": "table",
          "props": {
            "repeatHeaderOnPageBreak": false,
            "columns": [
              {
                "header": { "content": "Metric" },
                "cells": [{ "content": "Revenue" }]
              },
              {
                "header": { "content": "Value" },
                "cells": [{ "content": "1.2M" }]
              }
            ]
          }
        }
      ]
    }
  ]
}

Here every table inherits 6-point cell padding and repeating headers from the document defaults, but this particular table opts back out of header repetition.

Available keys differ per format:

  • DOCX: heading, paragraph, image, statistic, table, section, columns, list
  • PPTX: text, image, shape, table, highcharts, chart

Using style presets ​

PPTX: the style prop ​

Text components reference one of the seven preset names (title, subtitle, heading1, heading2, heading3, body, caption) — or one of the type roles a theme declares — via style. The cascade is: component props → style preset → theme defaults. Anything you set inline wins over the preset:

json
{
  "name": "text",
  "props": { "text": "Roadmap", "style": "heading1", "color": "accent" }
}

title and heading* styles use the theme's heading font face; the others use the body font. Custom themes may override any subset of the seven presets under styles — unlisted presets keep their built-in definitions.

DOCX: real Word styles and themeStyle ​

A DOCX theme's styles block (normal, heading1–heading6, title, subtitle, TOC1–TOC6, plus arbitrary custom names) is compiled into real Word paragraph styles in the output file — heading components pick up heading1–heading6 automatically, and readers see the styles in Word's style gallery. A paragraph can opt into any named style with themeStyle:

json
{
  "name": "paragraph",
  "props": { "text": "A closing thought.", "themeStyle": "subtitle" }
}

TIP

Because DOCX styles become native Word styles, documents stay editable after generation: someone tweaking the file in Word inherits your typography instead of fighting direct formatting.

Where to go next ​

Released under the MIT License.