deckwright owns a complete design system that renders correctly against a blank
presentation. A template is an explicit override surface, never the source of the
theme — nothing is inferred at build time. Everything below lives in
src/deckwright/theme/defaults.py and is reachable as from deckwright.theme import ....
To write a deck spec, read docs/authoring.md; this doc is the
reference for what a spec gets for free when it names no template.
- No template at all
- What a theme file may contain
- Colour: roles and pairs
- Type: points at a reference height
- The face, and whether deckwright can measure it
- Geometry: fractions, not inches
- Chrome: the theme's title treatment
- Icons: the brand's own, or ours
- Motion
- Motion roles
from deckwright.theme import load_theme
theme = load_theme() # 13.333 x 7.5in
theme = load_theme(slide_w=26.666, slide_h=15.0) # same system, twice the canvasload_theme() with no path returns the built-in system with theme.template is None. blank_presentation(slide_w=..., slide_h=...) builds the empty canvas it is
measured against — the grid is derived from the presentation's own EMU-rounded size,
so theme.grid.slide_w equals what the saved .pptx really holds.
A template-free Presentation() defaults to 10 x 7.5in, so the canvas is always set
explicitly. The built-in theme composes on its Blank layout, the one layout such a
presentation is guaranteed to offer.
A themeless theme carries no marks:, so an inverse slide has no art to lay down.
It is still painted: the compiler fills the slide with the inverse pair's own
background colour and draws that pair's ink over it. Art is an embellishment on a
painted surface, never the thing that makes the surface dark.
Thirteen top-level keys, and an unknown one is a ThemeError naming every known key —
a theme is not a place where a typo is silently ignored. Wherever an unknown key is
refused, one YAML read as something other than text — on: as a boolean, 2021-01-01: as
a date — is named as YAML read it, with a note to quote it. Everything is optional except
name; a file with only a name is the built-in system under another label.
| Key | What it does |
|---|---|
name |
How a deck's theme: refers to it. |
template |
The brand .pptx this theme specializes into, relative to the theme file. Without one the built-in system stands alone: bind: then resolves literal colours only, and marks: is refused as having nowhere to look. A path holding a NUL, a name longer than the filesystem allows, or a symlink loop is a ThemeError naming the key; icons is checked the same way. |
compose_layout |
Names the layout generated slides compose on, for a template whose ambiguity ranking cannot resolve. The escape hatch, never a requirement — the ranking itself lives in layouts/resolve.py. |
drop_template_slides |
Delete the template's own slides after loading it. Almost always true for a brand template, whose slides are examples rather than content. |
bind |
Maps semantic roles onto the template's clrScheme slots, or onto literal RRGGBB values. Literals need no template. |
scale |
Margins, columns, rows and gutter. rows is the divisor a placement's rows: indexes — 12 by default, and stated here so an author can read the number they are indexing. |
type |
face, heading_face, mono, ea, reference_height, min_pt, line_weight_pt, and per-rung ramp overrides written in pt. |
chart |
The chart renderer's aesthetic knobs — gap width, gradients, shadows, markers, gridlines, label position. Every number is finite: .inf and .nan are refused by key. See charts.md. |
chrome |
Where each chrome line sits and how it is set. |
icons |
A directory of .svg glyphs searched before the shipped set. |
marks |
Art laid over a painted backdrop. A mark's name is the background it decorates, so marks.inverse is the only one there is. Its media: resolves the same way a deck's image does — beside the template, then out of the template's own ppt/media/ — and may not climb out of those with ..; an absolute path is taken as written. |
reserve |
Polygons a placement may not intrude on — a logo wedge, a footer band. |
motion |
How the brand paces a reveal. See Motion. |
Every block layers over the built-in system rather than replacing it: a theme restates only what it wants to move, and everything it omits keeps the default below.
Twelve semantic roles. No brand words — eyebrow, amber, deep, navy and the
rest are one deck's vocabulary and appear nowhere in the system.
| Role | Hex | Role | Hex | |
|---|---|---|---|---|
page |
FFFFFF |
inverse |
12161B |
|
ink |
1A1D21 |
inverse-ink |
FFFFFF |
|
muted |
5F6672 |
accent-1 |
1F5FA8 |
|
line |
E3E6EA |
accent-2 |
0F6E63 |
|
surface |
F2F4F7 |
accent-3 |
A8431C |
|
surface-ink |
1A1D21 |
accent-4 |
6A3FA0 |
The pair is the unit. A foreground may only be painted on a background it was
declared with, so a component never invents a combination nobody vetted. Every pair is
measured against WCAG AA (4.5:1) when the Palette is constructed, and a shortfall is
logged as theme_pair_below_aa — it does not refuse the theme. A real brand file
whose own accent cannot carry AA text would otherwise be unusable, and the decision
belongs where it can be made properly: deckwright qa judges contrast against
what was really painted, including the photograph a pair of nominal colours cannot see.
| Pair | fg on bg | Ratio |
|---|---|---|
page |
ink on page |
16.91:1 |
page-muted |
muted on page |
5.78:1 |
surface |
surface-ink on surface |
15.35:1 |
inverse |
inverse-ink on inverse |
18.16:1 |
accent-1 |
auto ink on accent-1 |
6.44:1 |
accent-2 |
auto ink on accent-2 |
6.12:1 |
accent-3 |
auto ink on accent-3 |
6.03:1 |
accent-4 |
auto ink on accent-4 |
7.42:1 |
line carries no pair — it is a stroke, never a text colour. A theme that binds its own
page and no line gets the page moved 12% toward its ink, the step the built-in line takes
from white, so a dark page gets a dark hairline. A drawn rule still has to clear 3:1 against
its slide and gives way to muted where the line does not, on a dark page as on a light one. An accent pair's
foreground is AUTO_INK: whichever of ink and page reads better on it. An
accent none of them clears falls back to whichever of the theme's other declared roles —
inverse, surface-ink, inverse-ink, surface — reads best, so a pair always resolves
from the theme's own declared colours, never an invented black or white.
An accent is a fill colour, and becomes text only where it is measured. A slide's
live pair is the one its background: names, and SlideCtx.fg()/dim()/paper()
all read off it. An accent role has no pair at all — nothing declares it readable as
text on anything — so:
| Call | Returns |
|---|---|
accent_on(bg, size_pt=…, name=…) |
the accent when it clears required_ratio(size_pt) against bg, else ink_on(bg) |
accent(size_pt=…, name=…) |
the same, measured against the slide's own paper |
ink_on(bg) |
whichever of ink and page reads on an arbitrary fill — the choice AUTO_INK makes for a declared pair |
color(role) |
the unguarded read, for fills and strokes |
required_ratio is AA_LARGE (3:1) from 18pt up and AA_NORMAL (4.5:1) below —
the same rule QA's contrast check applies, so a component cannot pick a colour QA
will then warn about.
The background is whatever the component actually painted. A stat tile is filled
with line, so its text measures against line, not against the slide. Getting this
wrong is what produced accent text at 2.2:1 inside a light tile on a white page,
where the slide-level measurement said the accent was fine. Text a component sets straight
onto the slide is measured the same way, against what is painted under its own box: prose
laid over a panel, a table on an inverse band, a caption over a scrimmed picture, a chart's
labels, axis text and legend over a panel. Every component that paints a fill — a panel, a
card, a disc, a versus or fanout plate, a diverge bar, a grid bar — counts as what is
under a later placement laid over it with bleed: true. A disc covers only its inscribed
square; text in the corners of its frame is also measured against what the disc was laid on.
text_ink(box, size_pt=…, muted=…) and accent_at(box, size_pt=…) return the ink and the
colour it really sits on, and that pair is what the manifest records. A component painting a
fill nothing readable sits on gets a warning (fill_ink_below_aa) naming the fill, not a
ThemeError — the build still completes, and qa's contrast check is what judges it
against what was really painted.
A pair names roles, not hex: DEFAULT_PAIRS holds ("ink", "page"), and
build_palette resolves it. Rebinding a role therefore moves every pair citing it,
instead of leaving a stale copy of the old colour behind.
DEFAULT_PALETTE.accents is a tuple of role names in use order, not hex, so a
binding that repoints accent-1 moves everything that cycles the ramp with it.
The ramp is never shorter than four. A theme's bind: layers over the built-in
roles rather than replacing them, so it can repoint an accent or declare a fifth, but
it cannot remove one: an accent nothing binds — or one bound to a slot still holding
Microsoft's shipped value, which is ignored — keeps its built-in hex. Series colours
cycle whatever that leaves, which is four roles on a template that binds none.
The consequence worth knowing is that a chart past the brand's own accents takes
deckwright's. Derive a theme from a template that yields only accent-1 and a chart's
second series, or a pie's second wedge, is painted 0F6E63 — deckwright's own colour,
beside one drawn in the brand's. Bind every accent you want a chart to cycle. A
highlight: reads only accent-1: the rest of the chart recedes instead of taking a
second accent.
A bind: value is a template slot name or a literal RRGGBB. The literal is
there because a template's real surface need not be in its clrScheme at all: every
one of the sixteen sample templates declares a background on its master, and two of
them paint a photograph. A palette bound only to scheme slots describes a page that
is not on the slide.
Literals need no template. A theme naming no template: is a colour palette of
its own — the fastest way to a deck that is not in deckwright's navy:
name: tidewater
bind:
page: '10212A'
ink: F2F5F7
muted: A8B2BD
inverse: F2F5F7
inverse-ink: '10212A'
accent-1: E4572E
accent-2: '17BEBB'Note that muted and inverse moved with page. The defaults are tuned for a light
page. Inverting the page alone leaves page-muted below AA, which the load logs as
theme_pair_below_aa. It also leaves the default inverse (12161B) so close to a dark
page that every inverse plate disappears into it, which the load logs as
theme_inverse_matches_page once the two are within 3:1. Neither refuses, since qa
judges what was really painted. On a dark page, inverse is the light surface, so its
ink has to be dark.
A slot name in that theme is refused, since there is no clrScheme to name a slot in.
An accent bound to a slot still holding Microsoft's shipped value is ignored — that
slot says the brand set no accent there — but a literal is what you typed and is kept
whatever it equals.
Every role you leave out keeps its system default, so a palette can be two lines. A
type: {face: …} beside it sets the faces — but name one this machine has not got and
qa reports font-substituted and declines to judge the render, because what it
measured is not what your audience will see.
inherited_surface resolves what the composed layout will really show — a bgPr's
own fill, or a bgRef indexed into the theme's bgFillStyleLst with its colour
standing in for phClr — and the result reaches the deck as Theme.surface:
| The template paints | deckwright does |
|---|---|
| a picture | leaves it, and samples its pixels behind every line |
a colour the palette calls page |
nothing; the promise is already kept |
| any other colour, or nothing at all | paints page, so what renders is what the palette declared |
The page is not painted only when the template has already laid down that exact colour. Assuming a master carries the page colour is what put a deck of dark titles on a dark blue photograph with the contrast check reporting nothing wrong.
A line crossing an edge is measured against the worse side. Whatever was painted last over the whole of a line's rectangle replaces what is beneath it, in paint order whichever kind each is, so a picture laid over a panel is what the line sits on. Something covering only part of it is one more surface that line crosses, and the reported paper is whichever of them reads worst against the ink being tried. Either single answer is wrong in one direction — report the page and white ink passes while half the line is lost on a dark panel; report the panel and dark ink passes while half is lost on the page.
Where no ink reads across the whole of it, the line is given a plate of the slide's own paper. An ink the author asked for by name never moves — it raises instead.
A theme writes point sizes. body: {pt: 16} is 16pt on a canvas
type.reference_height inches tall — 7.5in unless the theme says otherwise, which is
the 16:9 slide almost every deck is.
type:
reference_height: 7.5
ramp:
body: {pt: 16}
title: {pt: 34}What the engine keeps is the rung: points per inch of canvas height, pt divided
by reference_height. Height is the right normalizer — a 4:3 10x7.5in deck and a 16:9
13.33x7.5in deck are viewed at the same physical size and take the same type size,
which a width-based rule gets wrong by 33%. So the ramp still scales with the canvas;
only what an author writes changed, because "14pt body" is the thing they mean and
2.1333 is not.
min_pt and line_weight_pt are read the same way. Every size in type: is above zero,
and must stay a finite number of points once divided by reference_height.
reference_height itself is a finite number above zero, large enough that 1pt over it
does not overflow — refused on its own, with or without a ramp: to divide.
DEFAULT_RAMP is modular — 2.13 * 1.25 ** step — so the whole ramp moves with one
number. caption takes a half step: a kicker is bold and set in caps, and a caption — the
rung a chart's axis, a code listing, a stat's label and a citation are set at — is neither,
so one size for both left the caption the smaller of the two to read.
TypeStyle.rung holds the rung; TypeStyle.size returns resolved points.
| Rung | Value | at 7.5in | at 15in |
|---|---|---|---|
kicker |
1.704 | 12.8pt | 25.6pt |
caption |
1.9051 | 14.3pt | 28.6pt |
body |
2.13 | 16.0pt | 31.9pt |
lead |
2.6625 | 20.0pt | 39.9pt |
subtitle |
2.6625 | 20.0pt | 39.9pt |
head |
3.3281 | 25.0pt | 49.9pt |
title |
4.1602 | 31.2pt | 62.4pt |
stat |
4.6512 | 34.9pt | 69.8pt |
display |
5.2002 | 39.0pt | 78.0pt |
hero |
6.5002 | 48.8pt | 97.5pt |
These ten names are the complete vocabulary; a spec may not name a rung outside it.
kicker, head, title, stat, display and hero are bold; subtitle, title,
stat, display and hero render in the heading face. A theme's own ramp: entry
inherits both for its rung — title: {pt: 34} resizes the title without un-bolding
it — and states bold: or face: to override. The minimum readable size is
MIN_RUNG_DEFAULT = 1.40 — 10.5pt at 7.5in — and Theme.min_pt holds it resolved.
A rung's face: names one of the theme's own faces by role — face: body, face: heading,
face: mono — or a literal typeface. The three aliases are the whole vocabulary; a value
matching one only in case (face: Mono) is a literal, and warns as
theme_ramp_face_alias_case because no machine has a typeface by that name.
Every face key — type.face, heading_face, mono, each type.ea entry and a rung's
face: — takes one name as text. A list or a number is refused rather than loaded as its
printed form, because a run in a .pptx names exactly one face.
A figure-heavy deck can give stat — and caption, for axis-style labels — the mono face.
Numerals then align in a column and read as measurements rather than display type. This is a
suggestion, not a rule: it suits decks whose slides carry figures, and reads as an affectation
on decks that do not.
type:
mono: IBM Plex Mono
ramp:
stat: {pt: 34, face: mono}The built-in faces are body Helvetica, headings Helvetica, monospace Courier New.
A theme's own type: face / heading_face / mono overrides them; failing that, a
template's fontScheme supplies the face — which is why a theme derived by conform
counts the faces its slides really use rather than trusting that scheme.
Those defaults are chosen to survive the trip to another machine. A .pptx names one
face and the viewer substitutes whatever it has, so the only safe names are the ones
that land on a metric clone everywhere a deck gets opened: Helvetica is native on
macOS/Keynote, mapped to Arial by Windows Office, and Liberation Sans under
LibreOffice; Courier New is native on macOS and Windows, and Liberation Mono under
LibreOffice. Never default to a Microsoft-only face — Calibri, Aptos and Consolas
have no counterpart off Windows, so they substitute to something unrelated without
saying so.
type.ea names the face Chinese, Japanese and Korean text is set in, one per script:
type:
ea: {ja: Hiragino Sans, ko: Apple SD Gothic Neo, zh-Hans: PingFang SC, zh-Hant: PingFang TC}The keys are ja, ko, zh-Hans and zh-Hant. A script the theme leaves out takes the
face the template's fontScheme names for it in its a:font script= entries (Jpan,
Hang, Hans, Hant); most templates name Microsoft faces there. base has no template
and names none, so a viewer's own CJK face sets that text. How a run picks its script is
authoring.md.
base.yaml ships inside the package, at src/deckwright/theme/builtin/base.yaml,
and nowhere else. A checkout has no copy of it in templates/.
A theme: name resolves against the theme directory first (templates, or
DECKWRIGHT_THEME_DIR) and falls back to the packaged built-ins — which is why
theme: base works from an installed wheel with no checkout anywhere near it. A file
of the same name in the theme directory always wins, so a brand theme can be called
base if you insist.
Do not edit it to make a brand theme. You would be editing the file that ships in
the wheel, and the next install replaces it. Derive one instead:
deckwright conform templates/<brand>.pptx --adopt <name>, which writes
templates/<name>.theme.yaml — gitignored, because it carries a client's palette.
Every wrap decision in a build — how deep a callout row is, whether a table row needs a second line, whether a title costs the body some height — is computed from the face's own per-character advances. deckwright ships those advances for these families:
| Family | Covers |
|---|---|
| Calibri / Carlito | Calibri, and the metric clone LibreOffice substitutes for it |
| Arial / Helvetica / Liberation | Arial, Helvetica, and Liberation Sans, the clone that stands in for both |
| Courier New / Liberation Mono / Cousine | the default mono face and its two metric clones |
| Poppins, Open Sans, Montserrat | the families of those names |
| Amatic | Amatic and Amatic SC |
| Sniglet, Bebas Neue | the families of those names |
| Barlow Semi Condensed | that cut only — plain Barlow is wider and is not covered |
A face outside those is laid out against CEILING, the widest advance measured across
Carlito, Liberation Sans, Verdana and DejaVu Sans, per character. A mono face setting a
code listing is the exception: one without the Courier table — unmeasured, or named like
a proportional family, as Helvetica Monospaced is — gets MONO, the widest monospace
advance measured, for every Latin glyph, since CEILING charges i, . and the space far
under a monospaced column. Beyond Latin such a face may draw a glyph from another face, so
Greek, Cyrillic, arrows and box drawing are charged the widest glyph of their class. For a
face no wider than those it is safe: a box is never sized short of its text. A wide
display face can outrun it (Sniglet's ExtraBold does, which is why it has a table), and for
every face it is loose in a way nothing downstream reveals, so the theme loader says so for
face and heading_face:
warning theme_face_unmeasured theme=brand role=heading_face face='Aptos Display'
A fit refusal built on that estimate — prose, card, versus, an ellipse label, a
chrome box too short for its text, a code listing in an unmeasured mono face — ends by
saying so, so a refusal of copy that would have fitted names its reason:
... split the slide or shorten the copy ('Segoe UI' has no width table, so this estimate errs wide)
A heavy or black weight is unmeasured on purpose. Its advances are wider than the regular's, so the ceiling is the honest answer rather than a table that would under-size every line.
One is about the width tables deckwright ships, the other about the fonts the machine has. They take different fixes.
theme_face_unmeasured |
font-substituted |
|
|---|---|---|
| Where it shows | a build warning, from the theme loader | a deckwright qa finding, severity WARN |
| What is wrong | deckwright ships no advance table for the face | the face is not installed on the machine that rendered |
| What it costs | wrap estimates fall back to the loose CEILING width |
the render, and every finding drawn from it, shows a typeface the deck does not carry |
| What fixes it | name a measured face, or accept the slack | install the face here, or read overflow and render-contrast as approximate |
A face can hit either, both, or neither. The build warning covers face and
heading_face; the QA check covers every face the theme names, mono and any per-rung
face included. font-substituted says nothing when fontconfig cannot be asked — no
fc-list on the machine is an absent answer, not a clean one.
OOXML lets a run name +mj-lt or +mn-lt instead of a typeface — a reference meaning
whatever the template's font scheme calls major latin. deckwright conform resolves those
against the template's own scheme before deriving a face. A theme adopted before it did
carries the reference itself, which no renderer resolves, so the deck sets its type in a
string and every machine substitutes. load_theme warns as theme_face_scheme_reference,
names the key and the token, and falls back to the template's scheme entry; re-adopt the
template to settle it.
The face named in a theme is a request. Whether it renders is a property of the machine opening the file, and there are three machines in play: the one that measured the layout, the one that rendered the contact sheet you checked it on, and the one your audience opens it with. A face that is unmeasured and uninstalled means all three disagree — the build reserves ceiling-width space, the renderer substitutes something narrower, and the viewer substitutes something else again. Text that overlaps for your reader and not for you is this, every time.
So a deck face wants to be both measured by the table above and installed where
the deck will be opened. On macOS, Helvetica is both; Aptos and Calibri are frequently
neither, whatever the template's own fontScheme says.
default_grid(scale) returns a Grid whose fields are fractions and whose
read-backs are inches. Horizontal fractions are of the width, vertical of the height.
| Fraction | Value | at 13.333x7.5in |
|---|---|---|
| left / right margin | 0.055 of width | 0.733in |
| top / bottom margin | 0.060 of height | 0.450in |
| gutter | 0.014 of width | 0.187in |
body_top |
0.22 of height | 1.650in |
| columns | 12 | col_w 0.818in |
The inch column is the built-in canvas. A theme's canvas is its template's slide size —
chalkboard is 10 x 5.625in — and deckwright new writes it into the scaffold's header,
so read a theme's inches there, not here.
Corpus margins run 4.4%–14.6% of slide width, so no universal value exists; the
system default sits near the low end and a theme's scale: block overrides it.
Doubling the canvas doubles every inch — a change of slide size is a no-op.
A theme YAML's scale: block is an override, key by key: omit gutter and the
built-in gutter applies, omit the block entirely and the whole built-in grid does.
Margins, gutter and body_top are written as percents of the canvas (4.5%); nan%,
inf% and a percent too large to be a number are refused with every other value that is
not a percent.
A theme's chrome: block says where each chrome line sits and how it is set. Omit it
and all three stack from the top margin at the content width — the built-in default.
chrome:
kicker: {at: {box: {x: 0%, y: 5.5%, w: 100%, h: 5%}}, align: center}
title: {at: {box: {x: 0%, y: 10.5%, w: 100%, h: 10%}}, align: center}
subtitle: {at: {box: {x: 0%, y: 21.5%, w: 100%, h: 5%}}, align: center}The keys are at, align, anchor, rung, pair and ink, documented for authors under
Moving the chrome — a slide's own chrome: block
takes the same keys and merges over the theme's, key by key. A slide's block also takes
build, which a theme's refuses: it would hold back that line on every slide.
Fractions only. A box outside 0..1 on either axis is a ThemeError, because a
theme declaring inches is a transcription of one deck at one canvas size, which is
exactly what a fraction-based system exists to stop. align/anchor are the only
non-geometric values, and neither has a size.
icons: names a directory of .svg glyphs beside the theme file. It is searched
before the set that ships with deckwright, so a brand that has drawn its own target
gets it everywhere without a single deck changing.
icons: assets/icons # relative to this theme fileThe template's own glyphs are not used, and cannot be: 81% of the corpus's 4,019
freeform paths are schemeClr-linked and would recolour correctly, but they are named
Google Shape;#;p12 and Freeform: Shape 41. Nothing can ask for the one that means
"calendar", which is the whole job an icon name does.
A glyph is painted the first brand accent that clears 3:1 where it lands — WCAG's
non-text floor (1.4.11) — falling back to the surface's ink. Every accent is tried,
not only accent-1: a lime brand has nothing that reads on a near-white plate, and
stopping at the first would black out a violet one too.
cols: narrows the measure and keeps the stack. Templates put artwork in a
corner and expect the title beside it, so all three fields commonly take the same
column range; they stack down the page rather than piling at the top margin. Lines
sharing a measure share one frame, which is what lets a title that wraps wider than
estimated push its subtitle down instead of drawing through it — so a field given
its own columns starts a new frame. deckwright conform writes this block when the
template's background leaves a wide enough clear run to aim at.
A deck spec says how many beats an argument has — animate: one_at_a_time versus
together. The theme says what a beat looks like. This is the same split that keeps
colour out of a spec: retheming a deck changes its pacing along with its palette,
and a deck cannot drift off-brand one hardcoded timing at a time.
| Key | What it does |
|---|---|
stagger_ms |
Offsets each shape after the first within one click, so what that click reveals cascades. 0 (the default) keeps it simultaneous. |
advance |
on_click (default) spends a click per reveal group. after_previous chains them onto one click. |
beat_ms |
The pause under after_previous after one group finishes before the next starts, so groups start the entrance's duration plus beat_ms apart — 500 + 400 ms for a fade at the default. Default 400. |
roles |
Binds each semantic motion role to an entrance kind. See Motion roles. |
transition |
The deck's default slide transition — a mapping of kind, optional dir, and speed. See The deck's transition. |
motion:
advance: after_previous
beat_ms: 350animate: one_at_a_time normally spends a click per group — four stat tiles, four
clicks. Under after_previous the first click starts the sequence and the rest follow
themselves, each starting beat_ms after the one before has finished. The slide still
builds in order; you just stop tapping.
The spec is unchanged either way — how many beats an argument has is the deck's business, whether you advance them by hand is the brand's.
A component reports what it is; the theme decides how that kind of thing moves.
Neither the component nor the spec ever names an OOXML preset — the same indirection
accent-1 uses onto a palette slot.
motion:
roles:
line: {kind: wiperight} # a rule draws itself left to right
text: {kind: fade}| Role | Reported for | Default |
|---|---|---|
text |
a shape that carries words, on its own fill or not: prose, bullets, callout and card copy, a stat tile, a table, a code listing, a caption, words over a picture |
fade |
surface |
a filled shape that carries no words: a card's plate, a panel, an ellipse, a callout's dot, a bar, a swatch chip, a scrim |
fade |
line |
a stroke: rule, connector, the join between flow steps, a fanout's trunk, spine and branches |
wiperight |
datum |
a chart, and each part of a chart's own build | wipeup |
figure |
a picture, a glyph (icon, or a mark on a card, callout, stat or fanout), a document card |
fade |
Three entrance kinds exist: fade, wipeup, wiperight. A role bound to anything else
is refused when the theme loads, and so is a role name outside the five above.
A wipe runs along one axis, so a shape more than eight times as long across that axis as
along it — a vertical join between flow steps, a fanout's spine — takes the other wipe:
wiperight becomes wipeup on a vertical stroke, and wipeup becomes wiperight on a
horizontal one.
animate: together ignores roles. It gives every shape on the slide the same fade
through a different build, so there is nothing for a per-shape role to say. A deck that
wants its rule to wipe needs one_at_a_time.
motion:
stagger_ms: 80 # 0 (the default) reveals a group's items simultaneouslystagger_ms offsets each shape after the first within one click by i * stagger_ms
milliseconds. It never changes how many clicks a slide takes — only what happens
inside one.
What a click covers differs by mode, so the same number reads differently:
animate: |
One click covers | What a stagger does |
|---|---|---|
together |
every shape on the slide | The cascade you probably want — a row of tiles arriving left to right. |
one_at_a_time |
one reveal group | Offsets the parts of a group. A stats group is one tile, so its label trails its number by stagger_ms; a callouts row's text trails its dot. Subtle, and easy to set too high. |
A negative value is refused at load: it would schedule a shape before the click that reveals it.
motion:
transition: {kind: fade, speed: fast} # dir: only where the effect takes oneThe built-in base theme carries exactly this block, so a deck with no template fades
between slides. conform derives no transition from a template, so a brand theme moves
only if you add this block to its sidecar.
A transition belongs to the slide it arrives at — it says how the show moves to
this slide from the one before it. Every slide gets the theme's; a slide writes
transition: none for a deliberate hard cut, and that is the only thing a slide may
say about it. Naming a different transition would be naming a look, which is what
this block exists to hold.
speed is slow, med or fast. There is no duration knob — the base schema has
no dur attribute, and the sub-second form needs a 2010-namespace extension deckwright
does not write.
Twenty-one effects, and each takes its own direction vocabulary — a shared
l/u/r/d list is invalid the moment it meets strips:
dir: accepts |
Effects |
|---|---|
| (none) | circle, cut, diamond, dissolve, fade, newsflash, plus, random, wedge, wheel |
horz, vert |
blinds, checker, comb, randomBar |
l, u, r, d |
push, wipe |
l, u, r, d, lu, ru, ld, rd |
cover, pull |
lu, ru, ld, rd |
strips |
out, in |
split, zoom |
A bad kind, direction or speed is refused when the theme loads, not at the slide that first uses it.
For a business deck,
fadeor apushis usually the whole answer. The rest are in the schema, not in good taste.
Nothing in a static render shows a transition —
bin/run renderproduces identical images with and without one, anddeckwright qareads the manifest, which a transition does not touch. Only presentation mode shows it.
A still render cannot show a stagger — LibreOffice draws the final state of every slide. Only real PowerPoint shows the cascade.