KeynoteKit turns a plain JSON spec into a real .key file — full-bleed photos, gradients, shapes, colored bullets, and per-bullet build animations, on a 16:9 canvas. Author slides three ways: from scratch, from a reusable inline template, or from a Keynote template deck.
Optional — only needed for slides that clone a real Keynote layout (Way 3 below).
A template is just a .key whose slides each demonstrate one layout, so KeynoteKit
can clone the right one and inherit its masters and styling.
@layout: name.iwatool describe-template to see each layout's
fillable placeholders (role, prompt, geometry).# Explode an installed theme into a tagged template
scripts/make-template.sh "Basic Black" brand.key
# ...or tag slides yourself, in each slide's presenter notes:
# @layout: title
# @layout: bullets
# @layout: statement
# See what a template exposes
iwatool describe-template brand.key
A deck is a list of slides. Each element is placed by frame on a
1920×1080 canvas. Colors are hex or [r,g,b,a]; frames are explicit or a
cover/fit helper that reads the image's real aspect ratio.
{
"$schema": "./deck.schema.json",
"defaultFont": "Helvetica Neue",
"imageBaseDir": "assets",
"template": "brand.key", // optional — for Keynote-template slides
"templates": { }, // optional — reusable inline layouts
"slides": [
{
"background": "#0E2529",
"transition": { "effect": "apple:push", "duration": 0.4, "direction": "fromBottom" },
"elements": [ ]
}
]
}
"#0E2529" or [0.05, 0.14, 0.16, 1]. A bare color is a fill anywhere.
{x,y,width,height}, or cover / fit / coverBox that measure the image.
A deck defaultFont, overridable per element; bold / italic apply as traits.
Nested bullets:
prefix a line in a bulleted element's text with tab characters — one per level —
e.g. "Otters\n\tRiver\n\t\tSea". Keynote stores the level per paragraph; the tabs are stripped.
Mix and match freely — all three can live in one deck. When you set a
deck-level template, from slides clone its layouts while free-form and
use slides are synthesized onto a blank slide alongside them.
{
"background": "#0E2529",
"elements": [
{
"type": "shape",
"shape": { "kind": "roundedRectangle", "cornerRadius": 16 },
"frame": { "x": 160, "y": 300, "width": 90, "height": 16 },
"fill": "#4CD9C2"
},
{
"type": "text",
"text": "Built from scratch",
"frame": { "x": 160, "y": 340, "width": 1500, "height": 220 },
"font": "Futura",
"fontSize": 150,
"bold": true,
"color": "#EDF7F5"
},
{
"type": "text",
"text": "Every element placed by absolute frame.",
"frame": { "x": 168, "y": 560, "width": 1400, "height": 80 },
"fontSize": 44,
"italic": true,
"color": "#8CB3B2"
}
]
}
"templates": {
"content": {
"elements": [
{ "type": "shape", "frame": { … }, "fill": "#4CD9C2" },
{
"type": "text", "name": "kicker", "text": "",
"fontSize": 30, "bold": true, "color": "#4CD9C2"
},
{
"type": "text", "name": "title", "text": "",
"font": "Futura", "fontSize": 96, "bold": true
}
]
}
},
"slides": [
{
"use": "content",
"set": {
"kicker": { "text": "REUSABLE LAYOUT" },
"title": { "text": "Filled from a template" }
},
"elements": [
{
"type": "text", "name": "body",
"text": "Define the layout once\nInstantiate it per slide\nFill slots by name",
"bulleted": { "marker": "•", "color": "#4CD9C2" }
}
]
}
]
{
"template": "brand.key",
"slides": [
{
"from": { "layout": "bullets" },
"set": {
"title": "From a Keynote template",
"body": "Clone a real layout\nInherit its masters & styles\nFill the placeholders"
},
"override": [
{ "target": "Hero Photo", "image": "team.jpg" }
]
}
]
}
One command turns the spec into a deck. Validation is fail-fast and exhaustive: an invalid spec reports every problem at once, each with a path, and writes nothing — so an LLM (or you) can fix them all in one pass.
$ iwatool build-json deck.json out.key
built deck from JSON: out.key
invalid deck spec:
• slides[0].elements[0]: missing frame
• slides[0].elements[1]: unknown alignment "middle"
• slides[0].elements[2]: image not found: hero.jpg
• slides[0].builds[0]: target "ghost" is not a named
element on this slide
The full grammar — with a description on every field — lives in
Examples/deck.schema.json. Point an LLM at it and ask for a deck.