Design Resolution
How an engine decides the effective design for any given slide. The schema spreads these rules across field descriptions; this page states them once, as an algorithm.
Precedence
For every design field independently, the most specific source wins:
wins +--------------------------------------------------------+
^ | 1. slide design slides[i].design.* |
| +--------------------------------------------------------+
| | 2. deck design design.* on the presentation root |
| +--------------------------------------------------------+
| | 3. resolved theme colorScheme, fontScheme, |
| | background, dimensions from the |
| | theme record |
| +--------------------------------------------------------+
loses | 4. engine defaults e.g. spec/reference/ |
v | engine-defaults.json |
+--------------------------------------------------------+- Slide design —
slides[].design.* - Deck design —
design.*on the presentation root - Resolved theme — defaults carried by the theme record (
colorScheme,fontScheme,background,dimensions) - Engine defaults — engine configuration such as
spec/reference/engine-defaults.json
Resolution is per field, not per object. A slide that sets only design.contentAlignment inherits everything else from the deck design; a deck that sets only design.colorScheme keeps the theme's font scheme and background.
Two field-level rules complete the picture:
-
Base-plus-overrides within one object. Wherever a reference object carries an
id(Theme,ColorScheme,FontScheme), theidresolves a catalog record as the base and sibling fields override the resolved record per key. The string shorthand ("colorScheme": "cool-horizon") is equivalent to setting onlyid.text "colorScheme": { "id": "cool-horizon", "accent1": "#0F4C81" } catalog record "cool-horizon" sibling fields on the object accent1: "#2874A6" <-- replaced -- accent1: "#0F4C81" accent2: "#1B4F72" <-- kept light1: "#FFFFFF" <-- kept | v effective scheme: accent1 from the override, everything else from the record -
Explicit suppression.
watermark,header, andfooteracceptfalseto switch off an inherited value — distinct from omitting the field, which inherits.
Catalog lookups inside this chain follow the standard resolution order (inline catalogs.<kind>.records[] → catalogs.<kind>.source → default catalog); see how-opf-works.md.
Worked example 1: color scheme through every level
{
"design": {
"theme": "classic",
"colorScheme": "forest-green"
},
"slides": [
{ "title": "Inherits the deck" },
{
"title": "Slide override",
"design": {
"colorScheme": { "id": "cool-horizon", "accent1": "#0F4C81" }
}
}
]
}- Slide 1: the
classictheme record supplies its own default color scheme, but the deck design setscolorSchemeexplicitly, soforest-greenwins (level 2 beats level 3). Fonts, background, and dimensions still come fromclassic. - Slide 2: slide design beats deck design (level 1 beats level 2). The
cool-horizonrecord resolves as the base, thenaccent1is replaced by#0F4C81. All othercool-horizonslots survive.
There is no ambiguity between "override" and "reference": every scheme value is a reference, and any sibling fields on the same object are overrides applied after the reference resolves.
Worked example 2: backgrounds and suppression
{
"design": {
"theme": "dark",
"background": "light1",
"footer": {
"left": { "text": "Acme Corp" },
"right": { "slideNumber": true }
}
},
"slides": [
{ "title": "Light slide in a dark theme" },
{
"title": "Section divider",
"design": {
"background": {
"type": "gradient",
"gradient": {
"angle": 90,
"stops": [
{ "color": "#0B1B2B", "position": 0 },
{ "color": "#123A5F", "position": 1 }
]
}
},
"footer": false
}
}
]
}- Slide 1: the deck-level
background: "light1"overrides thedarktheme's default background.light1is a theme slot — it resolves through the effective color scheme, which itself resolved through the chain above. - Slide 2: the gradient replaces the deck background for this slide only, and
footer: falsesuppresses the inherited footer rather than inheriting or replacing it.
Worked example 3: font scheme models
{
"design": {
"fontScheme": {
"id": "aptos",
"code": "JetBrains Mono"
}
}
}The aptos record supplies the OOXML pair (major/minor). The code role is an OPF-specific addition with no OOXML slot, so it layers on top without disturbing the pair. When serializing to PowerPoint, engines write major/minor to majorFont/minorFont and map abstract roles (heading, body) onto those slots. accent has no slot; the code family is written directly on code runs. The same slot-versus-role split applies to color schemes: OOXML slots (accent1–accent6, dark1/2, light1/2) round-trip directly, abstract roles (primary, text, surface, …) are mapped onto slots by the engine.
Code font
The code role resolves per key like every other override:
codeon the effectivedesign.fontSchemeobject;codeon the resolved font-scheme record (theconsolasandcourier-newrecords carry"Consolas"and"Courier New");- otherwise Roboto Mono, the documented fallback that
@openpresentation/opf-renderbundles.
The heading and body families are never reused as the code fallback, so choosing aptos still gives Roboto Mono code unless the deck sets code. resolveFontFamilies() in @openpresentation/opf/composition applies these rules for all engines.
Engine default font scheme
The last-resort font scheme applies only when neither the slide, the deck nor the resolved theme names one. Every bundled theme names a font scheme (minimal uses aptos), and engines default the theme to minimal, so a document with no design gets aptos in every engine.
Every engine shares one last resort, aptos, so a custom theme without fontScheme is measured, paginated, previewed and exported in the same fonts. @openpresentation/opf/composition exports it as DEFAULT_FONT_SCHEME (resolveScriptFonts() uses it too), and engine-defaults.json records it as fontScheme.pptx.latin:
| Engine | Last resort | Where |
|---|---|---|
| Core pagination | DEFAULT_FONT_SCHEME (aptos) | packages/javascript/src/pagination.ts |
| opf-render preview | aptos (engineDefaults.fontScheme.pptx.latin) | src/svg.js |
| opf-editor composition and slide transfer | aptos | src/font-defaults.js |
| opf-pptx export | aptos (DEFAULTS.fontScheme) | src/index.js |
fontScheme.google (roboto, noto-sans-sc, noto-sans) is not read by any current engine. It is kept as the intended default for a future Google Slides exporter, whose output renders in Google-hosted fonts.
Aptos is not openly licensed, so no OPF package bundles it. Previews take the same path for the last resort as for any aptos deck:
- Estimated layout (no
fonts): the SVG namesAptosandAptos Display, and the default raster engine draws them with its bundled sans-serif fallback (Roboto). - Measured layout with the opf-render office font pack (
loadFonts({pack: 'office'})):AptosandAptos Displayresolve to Intos and Intos Display, metric-compatible replacements from the OPF font policy (0.000% mean width difference against Aptos 2.01), and the substitution report lists both. With only the base pack andsubstitutionPolicy: "visual"they fall back to the visual alternates Roboto and Carlito. The PPTX always names Aptos. See font-fidelity.md. - Measured layout with only the base pack under the metric policy:
font-unavailablefor Aptos, as for a document with nodesign. Supply licensed Aptos faces, allow visual substitution, or set afallbackFamily.
Until FF-35 (font-fidelity-everywhere), core pagination, opf-render and opf-editor fell back to roboto while opf-pptx used aptos, so such a deck was measured in Roboto but exported with Aptos. None of the 126 bundled examples reaches the last resort: all 805 renderer golden rasters and all 126 exported PPTX files are byte-identical before and after the change. packages/javascript/test/font-scheme-defaults.test.mjs checks the shared default in core pagination, and each sibling repository has a parity test.
Unknown layout, theme and colour scheme
The same rule holds for the other catalog references, and no engine throws for them:
- Layout. A slide with no
layout, or with an id that no inline record, host catalog or default catalog defines, is composed with no layout record: title, subtitle, tag and content are arranged automatically, a slide with no body payload is a cover (the title block vertically centred) and the design hints apply. An engine must not substitute a different layout for an omitted one. An unknown id reportsunresolved-layout(andvalidateacatalog-referencewarning). - Theme. An unknown theme id uses the
minimalrecord and reportsunresolved-theme. An object reference with fields of its own is an inline record and reports nothing; an object with anidis laid over that id's record, and an object without anidover the default record (minimalfor a theme,cool-horizonfor a colour scheme), so a partial inline scheme keeps the slots it does not name. - Colour scheme. An unknown colour scheme id uses
cool-horizonand reportsunresolved-color-scheme; the theme's own colour scheme counts as the reference when the deck and slide name none.
Both diagnostics are { code, path, id, fallback, message } with path where the id is written (slides.N.layout, slides.N.design.theme, design.colorScheme, ...). resolveSlideContext reports them, and it also returns darkBackground (the preview's decision: the one colour the background names, resolved through the colour scheme and the deck's variables so var:brand, a scheme slot or primary/secondary/accent work, has a WCAG relative luminance below 0.179; a gradient counts as light, a picture reads light1, a pattern its backgroundColor; no opacity is applied), which composeSlide uses to pick logo variants. Core pagination continues with the fallbacks and passes each diagnostic to onDiagnostic.
Unknown font scheme
A font-scheme id that matches no inline or bundled record ("fontScheme": "no-such-scheme", { "id": "no-such-scheme", ... }, or a theme record that names one) is handled the same way in every engine. The document still validates, because an id may name a record from a catalog the engine has not loaded:
- The
DEFAULT_FONT_SCHEMErecord (aptos) is the base. Sibling fields on an object reference still override it per key, so{ "id": "no-such-scheme", "major": "Inter", "minor": "Inter" }uses Inter, and acoderole still applies. - The engine reports one
unresolved-font-schemediagnostic:{ code, path, id, fallback: "aptos", message }.pathis where the id is written:slides.N.design.fontScheme,design.fontScheme, or theslides.N.design.theme/design.themereference whose record names it. - An object without
idis an inline scheme on the same base and reports nothing.
resolveFontSchemeReference(reference, lookup, path) in @openpresentation/opf/composition implements this rule, and resolveSlideContext(presentation, index, { fonts }) (package root) applies it, with the slide, deck and theme precedence above, for core pagination and the layout checks: it returns the ComposeSlideOptions for one slide (canvas, layout, families as fontFamilies, alignment, measurement) plus an unresolved-font-scheme diagnostic. Hosts that compose a slide call it instead of repeating the chain. resolveFontFamilies() also falls back to the default scheme's families (Aptos Display, Aptos) when a scheme names no heading or body family, instead of Roboto. Authoring-time validate() already warns about the unknown id (opf/catalog-reference).
| Engine | Diagnostic channel | Reported |
|---|---|---|
| Core pagination | paginate(..., { onDiagnostic }) | once per path per call |
| opf-render preview | renderSvg / renderSlideSvg onDiagnostic | once per path per rendered slide |
| opf-editor | session.composeSlide / paginateSlide onDiagnostic option | once per call |
| opf-pptx export | toPptx(..., { onDiagnostic }) | once per path per export |
Before FF-35b, core pagination and opf-editor measured such decks in Roboto and opf-render threw catalog-resolution-failed. opf-pptx already exported Aptos, but reported nothing. None of the 126 bundled examples names an unknown font scheme. The 805 example SVGs, the 805 golden rasters and the 126 exported PPTX files are byte-identical before and after the change.
Sibling agreement checks
opf-render, opf-editor and opf-pptx run the same unknown-scheme cases as core (test/default-font-scheme.mjs). Each package keeps a local copy of the default, and in opf-editor of the resolver. Their checks against core's DEFAULT_FONT_SCHEME, resolveFontSchemeReference and paginate run only when the installed core exports them. Those checks are skipped today: the siblings install the published @openpresentation/opf 0.11.0, which predates FF-35. They activate in either of two ways:
- Sibling CI: after a core release that includes FF-35 and FF-35b is published, and each sibling's
@openpresentation/opfdependency and lockfile move to it. After that release, the local copies can import core directly. - Core ecosystem CI (
.github/workflows/ecosystem-ci.yml), which links this checkout's core into pinned sibling commits and runs theirnpm test: after those pins move to sibling commits that contain the FF-35 and FF-35b tests (the program's sibling pin bump).
Until then, the equality with core is established by running the sibling tests against a locally linked core.
Color references in content
Content color fields (TextRun.color, styled table cell style.fill / style.color, table cell border color) accept references as well as literal hex, and those references resolve through the same chain above:
"color": "accent2" slot name -> effective color scheme slot
"color": "text" role name -> role-to-slot mapping, then the slot
"color": "var:risk" variable -> top-level variables map
"color": "#B42318" literal -> used as-is (frozen at authoring time)-
Slot names (
accent1–accent6,dark1,dark2,light1,light2,hyperlink,followedHyperlink) read the named slot from the effective color scheme — the one produced by the slide → deck → theme → engine-default precedence at the top of this page. A slide-leveldesign.colorSchemeoverride therefore recolors that slide's named runs too. -
Role names (
primary,secondary,accent,background,surface,text,textSecondary) resolve through one shared definition,resolveColorRoles(), which the opf-render preview, the PPTX export andvalidateall call, so the same deck draws the same colors in each. A role defined on the effective scheme wins; otherwise it defaults from a slot:Role Default primary,secondary,accentaccent1,accent2,accent3backgroundthe slide's own resolved background when it is one color (solid, theme slot or pattern background color); else the scheme's backgroundrole, elselight1. A gradient or picture background uses the scheme default.surfacelight2, ordark2on a dark slidetextdark1, orlight1on a dark slide. Atextoverride applies on a light slide only, so a dark slide always keeps readable light text.textSecondarydark2, orlight2on a dark slideA slide is dark when its background's WCAG relative luminance is under 0.179, the point where white and black text contrast equally.
-
Links. A link run (
link) with nocolorof its own is drawn underlined in the scheme'shyperlinkslot (the OOXMLhlinkcolor; Office blue#0563C1when the scheme sets none), in the preview and in the PPTX export (a:schemeClr hlinkwhere the deck theme holds that color). Where that color has under 4.5:1 contrast against the slide background (the default Office blue on a dark slide), the slide'stextcolor is used instead, as the slide tag does for a low-contrast primary color. A link run with its owncolorkeeps it, and ahyperlinkColorRef always names the slot.followedHyperlinkis written to the PPTX theme; a static preview cannot know which links were visited, so it draws every link inhyperlink. -
Variable references (
var:<id>) resolve against the document's top-levelvariablesmap, independent of the scheme. Variables are deck-scoped named colors — use them for values that have meaning (var:risk) or repeat across slides. An unknown id is a validation warning, never an error, and engines fall back to their default text color.
The styled table cell and border color fields enforce the reference forms at the schema level (a typo like "acent2" is a schema error there — neither hex, a known name, nor a var: reference). Run colors stay open strings so imported decks keep validating: an unrecognized run color is a validation warning, and renderers fall back to the theme text color — the same warn-don't-error posture unknown catalog ids get. Unknown var: ids are warnings everywhere.
Background colors are ColorRefs
The colors of a solid background (SolidBackground.color), of each gradient stop and of a pattern (foregroundColor, backgroundColor) take the same forms as the content fields above: a literal hex, a slot or role name, or a var:<id> reference. The schema keeps these fields as plain strings, so a reference validates; engines resolve it through resolveColorRef() against the effective color scheme and the deck variables, and a reference that resolves to nothing is drawn as the engine default (white for a background), as for any other unresolvable color. The default text color of the slide follows the resolved background. A PPTX export writes a:schemeClr where the deck theme holds the named slot or role exactly, and the resolved literal otherwise (FF-24 conventions).
Decision, 2026-09-30 (agent decision, vetoable). The spec coverage audit found that
SolidBackground.coloracceptedvar:and slot names (the field is a string) but both engines painted white, whilecontent-item-design-overrides.mdalready states that every color field must accept the ColorRef forms and never hex alone. Rather than tighten the schema (which would break documents that validate today), the engines resolve ColorRefs in backgrounds. The owner can veto this by restricting the three background color fields toHexColorin the schema and the engines to hex only.
@openpresentation/opf/composition exports resolveColorRef() with the shared slot, role, variable, and hex rules above so renderers and exporters do not drift. Pass the effective color scheme, optional resolved role colors, the deck variables map, and a theme-text fallback for unrecognized references.
Script fonts and language
OOXML gives each theme font (major and minor) three script slots: latin, East Asian (ea) and complex script (cs). The presentation language and the effective font scheme resolve to all three:
latin design font scheme heading/body (the chain above)
eastAsian 1. design.fontScheme.eastAsian explicit slot
complexScript 2. the scheme's own major/minor when languageFamily is ea / cs (or eastAsian /
complexScript, the same values) and its
languages list (language ids) is empty or
names the language
3. the language's font scheme when the language's script uses the slot
4. the latin family otherwise- A language record's
script(ISO 15924) picks its slot. East Asian scripts (Jpan,Hans,Hant,Kore, ...) useeastAsian. Complex scripts (Arab,Hebr,Deva,Thai, ...) usecomplexScript. Latin, Cyrillic, Greek and other scripts uselatin.directiondefaults from the script (Arabic and Hebrew are right-to-left). - The language's
fontSchemeapplies to PowerPoint output andgoogleFontSchemeto Google Slides output. For Latin-script languages, the design font scheme always supplies the latin slot. - A language sets
lang, the text direction and the script slots, and never the Latin scheme: onlydesign.fontScheme(slide, then deck, then theme, then the shared defaultaptos) sets the latin fonts, and a language record'sfontSchemeis a default for its own script slot, not a deck font. The PPTX theme'sa:eaanda:csare written only for a slot a script font is selected for (the scheme's explicit slot or the language's script font) and stay empty otherwise, as in Office's own themes. See Language contract and Theme slots. - A Latin deck therefore repeats its heading/body family in
ea/cs. A Japanese deck withdesign.fontScheme: { "major": "Carlito", "minor": "Carlito" }keeps the Latin family inlatinand uses Meiryo (PowerPoint) or Noto Sans JP (Google Slides) inea.design.fontScheme.eastAsian/.complexScript({ "major": ..., "minor": ... }) name a script font explicitly, for example for CJK text inside a Latin deck.
@openpresentation/opf/composition exports resolveScriptFonts(document, { app, slideIndex }) (app is "powerpoint", the default, or "google-slides"), and the package root exports normalizeLanguageFamily(value) (which reads a font scheme's languageFamily, eastAsian as ea and complexScript as cs). resolveScriptFonts returns the heading and body slots, the OOXML lang (a curated ooxmlLang culture tag such as ja-JP or ms-MY, or an authored region tag), the canonical bcp47 tag, script, direction/rtl, and the per-script supplemental theme font. Renderers and exporters should use it rather than re-deriving slots. The model, the OOXML mapping and the open questions are in programs/font-fidelity-everywhere/script-font-model.md. opf-render and opf-pptx implement it (FF-07, FF-19, FF-49).
Brand assets and layout hints
The 2026-09-30 spec coverage audit found that design.logo, organization.logo, speaker.photo, design.contentDirection, design.chartPrimary, design.listBullet and fontScheme.accent validated, edited and round-tripped but changed nothing in any engine. This section states what they do now. Every rule below is implemented once, in composeSlide() and resolveLogo() of @openpresentation/opf, and consumed by the renderer and the exporter; the decisions marked (vetoable) are agent decisions the owner can overturn.
Layout records: placeholders and design
A layout record (opf-layout/v1) holds what it contains in placeholders and how it is meant to look in design. The content kinds are one vocabulary: title, subtitle, tag, text, list, image, video, chart, table, code, metric, quote and timeline.
layoutContent(record)(exported from@openpresentation/opf) derives{ kind, count, heading: { title, subtitle, tag } }from the placeholders.kindis the most frequent body placeholder kind (a tie goes to the first in placeholder order), ortitlewhen there is none;countis the number of body placeholders. Nothing derived is stored on the record.designuses the keys and lowercase values of the deck's and a slide'sdesign:titleAlignment,contentAlignment,contentBox,contentDirection,chartPrimary,imageFill,listBulletandslideImage: { position }. An absent key means the layout has no opinion.pnpm check:specverifies that the layout schema'sDesignHintsandDesignagree.- One merge, per key, the slide winning: the slide's
design, then the deck'sdesign, then the slide's layout recorddesign, then the engine default. This holds fortitleAlignment,contentAlignment,contentBox,contentDirection,chartPrimary,imageFillandlistBullet, so a slide overrides exactly what its layout sets, by the same name, and a layout value nobody overrides is honored without copying it anywhere.resolveDesignHints({ slide, layout, presentation, slideIndex })(exported from@openpresentation/opf/composition) is the one place that computes it; it also reports which level supplied each key.composeSlide()returns the result asSlideComposition.design, and the renderer, the PPTX exporter, pagination andvalidatetaketitleAlignment,contentAlignment,contentBox,imageFillandlistBulletfrom there instead of re-deriving them. A value the schema does not allow for its key is skipped, so the next level answers. - The layout's
composition.modestill ranks above the design hint:contentDirectionacts below it (see the decision below), and a layout's owncompositioncolumns and weights are overridden only by an effectivechartPrimary. slideImage.positionis not a per-key merge: a layout whose record setsslideImageis what lets a deck-widedesign.slideImageapply, and itspositionis the fallback when the slide's or the deck's value gives none.- Hosts no longer need to copy a layout's
designinto the deck or a slide. The pptx.gallery example builder and the editor's layout apply still do, which is harmless: a copied value is a deck or slide value and ranks above the record. - Decision, 2026-10-06 (agent decision, vetoable). On a cover,
tagandsubtitlefollowtitleAlignmentunless the slide's owndesign.contentAlignmentis set; a deck or layoutcontentAlignmentdoes not split the heading group, exactly as a deck value did before.
Logo source and variant selection
resolveLogo(presentation, slide, { slot, onDark, slideIndex }) returns { source, path, variant, slot } or null:
source 1. slides[i].design.logo
2. design.logo
3. the primary organization's logo role "primary", else the first
organization; object or array
variant a string or Asset object is the "default" variant
a LogoSet picks by slot and tone (below)
path design.logo, design.logo.light, organization.2.logo,
slides.3.design.logo.icon, ...Absence inherits (there is no false for logos); a level that yields no usable asset falls through to the next. A LogoSet is searched in this order, same-tone variants first, neutral ones next, the opposite tone last:
| Slot | On a dark background (onDark: true) | On a light background |
|---|---|---|
lockup | light, default, stackedLight, stacked, wordmarkLight, wordmark, iconLight, icon, then dark, stackedDark, wordmarkDark, iconDark | dark, default, stackedDark, stacked, wordmarkDark, wordmark, iconDark, icon, then light, stackedLight, wordmarkLight, iconLight |
icon | iconLight, icon, then the dark lockup chain | iconDark, icon, then the light lockup chain |
stacked | stackedLight, stacked, then the dark lockup chain | stackedDark, stacked, then the light lockup chain |
Hosts pass their own background luminance test as composeSlide(..., { darkBackground }); core never inspects colors.
Where the logo is drawn (vetoable)
- Cover and section slides. A slide with no body payload on a heading-only layout (
title,title-subtitle,section-divider, any layout whose placeholders are all headings, or no layout: the same rule that centers covers) draws thelockuplogo at the top-left of the free area, inside the slide padding and below any header furniture.composeSlidereturns it asgeometry.logo({ box, slot: 'lockup', path, source, variant, anchor: 'left' }):x = area.left + padding,yat the image-safe heading top,height = 56reference pixels at a 720-pixel short edge,width = min(4 * height, free width). Headings start one gap below the box and the cover-centering rule centers the tag/title/subtitle group in the remaining span; the logo itself does not move. Consumers fit the image inside the box preserving its aspect ratio, anchored left and vertically centered (SVGpreserveAspectRatio="xMinYMid meet"; PPTX computes the fitted size from the raster dimensions and places it atbox.x). Nothing is drawn when no logo resolves. Content slides never get an automatic logo (vetoable: it would move every content area). - Headers and footers.
HeaderFooterItem.logo: truegenerates an image furniture part withfield: 'logo',generated: true,image: resolved.source,path: <zone>.logoandsourcePath: resolved.path, from theiconslot, in the same box as a zoneimage: as wide as the icon's proportions make it at the band height (a square when core cannot read them), flush with the zone's left edge, centered, or flush with its right edge like the zone's text. Fields in a zone stack in the order logo, image, text, organization, socials, section, slide number, date. Without a logo the engine reportsunresolved-contentat<zone>.logo("Generated logo needs design.logo or a primary organization logo."). - Picture bullets. See
listBulletbelow. organization.logois therefore drawn wherever the deck logo is: it is the fallback source, never a separate placement.
Decision, 2026-09-30 (agent decision, vetoable). 84 of the 126 bundled example decks carry a
design.logoor anorganization.logo, so 81 cover slides gain a logo and their heading group moves down. The placement (top-left, 56 px, lockup) and the content-slide exclusion are the reference-engine defaults; a layout-driven logo slot is a separate design.
speaker.photo is a built-in image variable (vetoable)
No reference engine draws a speaker photo by itself: the schema has no speaker slot on any slide and no slide-to-speaker link, and a speaker block on covers would be a separate design. The author places it explicitly with the built-in image variable ("image": "var:speaker.photo", see templates and variables); the field also round-trips through PPTX provenance.
contentDirection
The effective value is slides[i].design.contentDirection, then design.contentDirection. It sets the root arrangement mode: vertical is column, horizontal is row. Precedence for the root mode:
1. composition.mode explicit: the slide's own, else the
layout record's geometry contract
2. design.contentDirection slide design, then deck design
3. the layout record's design.contentDirection the layout's own direction
4. autoPromoted regions (left, top:left, ...) keep their explicit geometry: contentDirection does not reinterpret them. Nested groups keep their own composition. The decision record keeps reason: 'configured-mode'. Reserved placeholder slots still count when only the hint sets the mode, as they do for the layout record's design.contentDirection.
Decision, 2026-09-30 (agent decision, vetoable). A layout record's
composition.moderanks above the design hint. pptx.gallery copies a layout record'sdesign.contentDirectioninto the design of its examples, so a hint that overrode the layout'scomposition.modewould flatten the layout's own grid by construction:chart-2x(design.contentDirection: horizontal,mode: grid, columns: 2) would compose its four blocks as one row under thehorizontalit derives for itself, and the renderer's gallery-layout fixtures (57 layouts whose preview must differ from the default only in alignment) fail. The hint therefore ranks with the layout record'sdesign.contentDirection, above it, and acts where no composition contract exists: slides without a layout and the bundled layouts withoutcomposition.mode(62 of 100). Under this rule no bundled example slide changes geometry forcontentDirection(101 decks set it; all of their blocks slides use layouts that carry a mode). The alternative, ranking the hint above the layout mode, would change 125 single-payload example slides and break the gallery's own layouts.
titleAlignment and contentAlignment in right-to-left decks
Decision, 2026-10-01 (RR-05, supervisor decision, vetoable). Alignment is logical for right-to-left text. In a deck whose language is written right to left,
leftmeans the start edge andrightthe end edge of each paragraph: an Arabic or Hebrew paragraph with the default (or authored)leftis drawn against the right edge, with its bullets, indents and table cells to match, while an English paragraph in the same deck keeps the left edge.centeris unchanged and a left-to-right deck is unchanged. The effective value (slide design, then deck design, thenleft) is still whatitem.alignmentreports;placement.lines[i].alignmentand the exportedalgncarry the physical edge. A right-to-left deck that wants a paragraph at its end edge setsright. See Layout direction.
chartPrimary
The effective value is slides[i].design.chartPrimary, then design.chartPrimary, then the layout record's design.chartPrimary. It applies to the root arrangement only when the slide has no promoted regions, no composition.mode of its own, and its root nodes contain at least one chart leaf and at least one node that is not a chart. The first chart node is primary and the other root nodes form one synthetic sub-grid:
left/right: the root is a two-track row with weights[3, 2](chart first forleft, last forright); the rest arrange inautomode inside their track.top/bottom: a two-track column with weights[3, 2](chart first fortop).none, or any other value: no change, the existing automatic grid with equal weight.
The synthetic container has no OPF path, so it records no groups, flows or explanation entry; the root decision has reason: 'chart-primary' and selectedColumns 2 (row) or 1 (column). Explicit root columns and weights, from the slide or the layout record, are ignored while it applies, and reserved placeholder slots are not applied. A chart inside a nested group, a chart-only root, or a single root chart payload leaves the arrangement unchanged.
Decision, 2026-09-30 (agent decision, vetoable). Unlike
contentDirection,chartPrimaryoverrides the layout record'scomposition.mode,columnsandweights(only the slide's owncomposition.modeblocks it). It is an author opt-in: no bundled layout record setsdesign.chartPrimary, so nothing derives it, while every bundled chart layout that mixes a chart with text carries acomposition.mode(chart-2xgrid,data-visualizationrow[2, 1], ...). Ranking the layout mode above the hint would make the field inert on every bundled chart layout. 40 example slides (10 per side) change under this rule.
listBullet (vetoable)
character (the default) draws the current glyph marker. image draws the deck's icon logo (resolveLogo(..., { slot: 'icon', onDark })) as a picture bullet: every items/bullets item in composeSlide and every listEntries[] entry of its fit carry bulletImage: { source, path }. Marker geometry is unchanged, and each entry carries bulletBox, where the image draws: a square of side marker.fontSize * PICTURE_BULLET_SCALE (0.65, exported) whose bottom sits on the marker baseline (marker.y) with its left edge at marker.x. The scale is what desktop PowerPoint draws for an a:buBlip at a:buSzPct 100000 (what the exporter writes), measured as a square 10, 15, 16, 20 and 31 px wide at font sizes of 16, 24, 25, 32 and 48 px (0.625 to 0.646; the heights run a pixel more from anti-aliasing) with its bottom on the text baseline, in Arial, Aptos, Georgia and Courier New alike, so it does not depend on the typeface; the text start and hanging indent are identical in both. The exporter sets no size, because PowerPoint sizes the bullet itself; consumers that draw it themselves (the preview) use bulletBox. Vetoable: the constant is a measurement, not a rule of the format. When image is set and no logo resolves, the glyph stays and the slide reports one unresolved-content diagnostic at slides.N.design.listBullet or design.listBullet, only when the slide has a list. The renderer draws an <image> per marker; the exporter writes native picture bullets (a:buBlip).
fontScheme.accent
resolveFontFamilies() returns accent only when the effective scheme defines an accent role (a family string or a Font object). The slide tag (eyebrow) and the quote body use fonts.accent ?? <current family> (body for the tag, heading for the quote body); nothing else changes. The renderer loads and embeds it, the exporter writes it on those runs (a:latin) while the theme fonts stay major/minor, and import keeps restoring design.fontScheme from provenance. None of the bundled examples sets an accent font.
What is not part of this chain
Beyond the color references above, content payloads carry no design controls in v1 — position, fontSize overrides at payload level, and the like were deliberately kept out while the content model stabilizes (see content-item-design-overrides.md; styled table cells and rich-text runs carry the only per-content styling, and their color fields take the reference forms above). The design system, plus layout hints (titleAlignment, contentBox, chartPrimary, ...) and dynamic composition, is the styling surface of an OPF document.