Asking a language model to write a slide deck is not the same problem as asking it to write a document. A document is text, and text is what the model emits. A deck is a spatial arrangement of boxes, and the tools most people use to build one — PowerPoint, Google Slides — expose that arrangement through a graphical interface, not through a file the model can write. The model therefore cannot produce the deck; it can only produce a program that produces the deck.
That extra hop is the whole story of this post. To see what it costs, I took the first five pages of a real presentation, the EDF producer booklet (FET17CR), and asked Gemini 2.5 Pro to reproduce them in four formats: hand-written HTML and CSS, Quarto/Reveal.js, the Google Slides API, and PowerPoint through python-pptx. Same source, same model, same five slides.
Section 1 sets out the argument. Section 2 shows the source document, and Section 3 through Section 6 walk through each attempt with its rendered output embedded. Section 7 compares them on the only terms that matter once the decks are built: how much source each one took, and what each one got wrong.
The code is on GitHub, and the rendered decks are on a companion site. The generated code is preserved as the model wrote it, bugs included, since the bugs are the evidence.
Summary
- Code-first formats put the model’s natural output — structured text — directly into the artifact. GUI-first formats make it write a program instead, and add a layer where errors hide.
- The same five slides took 130 lines in Quarto, 492 in HTML/CSS, 270 in
python-pptx, and 977 in the Google Slides API. - Every defect in the
python-pptxdeck was silent: wrong aspect ratio, a transparency setting that does nothing, a misspelled title. The script exits zero and produces the wrong slides. - Quarto/Reveal.js is the practical choice today. The gap is tooling, not models: what is missing is an editor that keeps Markdown underneath and can still export
.pptxwhen someone asks for one.
Why code-based formats work better
A Quarto presentation is a Markdown file with a YAML header. When the model writes one, the thing it writes is the presentation. Rendering is mechanical, and if the content is wrong it is wrong in a file you can read.
A Google Slides presentation is not a file the model can write. It lives on Google’s servers, and to put anything into it the model has to emit JSON requests that name objects by identifiers it invented earlier in the same script, position them in absolute coordinates, and submit them in a batch. PowerPoint is nominally better — .pptx is a documented ZIP of XML, so a library can write one offline — but the model still writes a program that emits shapes at hardcoded offsets rather than writing the deck.
The difference is not just verbosity, though Section 7 shows it is that too. It is that the intermediate program has its own failure modes, and they do not look like failures. A Markdown file that says the wrong thing says it visibly. A python-pptx script that sets a property the library does not have raises nothing, changes nothing, and prints nothing; you find out by opening the deck. Three of the four defects I found in the PowerPoint output are of exactly this kind.
The source document
The EDF producer booklet is a French-language onboarding document for winners of the FET17 onshore wind tender: a title slide over a photograph, a table of contents, a dense two-column preamble with sidebar callouts, a diagram of the parties involved, and a contractual timeline. It is ordinary corporate material, which is the point — it is the kind of deck people actually need to rebuild, with a real layout and a real brand to match.
HTML and CSS
The first attempt gives the model no framework at all: one HTML file, styled by hand, expected to behave like a deck. In principle that means writing three things — the slide structure as a series of <section> elements, the CSS that sizes and styles them, and the JavaScript that handles navigation, keyboard events, speaker notes, and incremental reveals.
The model wrote the first two and skipped the third. What came back is 492 lines, of which 314 are a single <style> block, and not one line of JavaScript. There are six <section class="slide"> elements stacked down the page, and the way you advance through them is to scroll. There is no keyboard navigation, no presenter view, no fragments, no way to project this and drive it from a clicker.
Judged as a rendering job, the result is good. The layout is faithful, the EDF orange is right, the two-column preamble with its info boxes reproduces closely, and it arrived on the first attempt. Judged as a presentation, it is a web page. The absence is easy to miss precisely because nothing about the file announces it — you notice when you try to present.
Quarto and Reveal.js
Reveal.js supplies what the hand-written version lacked: navigation, transitions, speaker notes, fragments, PDF export. Quarto sits above it as the authoring layer, so the source stays Markdown and a single quarto render produces the finished HTML.
---
title: "My presentation"
format: revealjs
theme: sky
---
## Second slide
- Point 1
- Point 2The deck for the booklet is 130 lines of .qmd, including a YAML header that sets the theme, the EDF logo, the footer, and a 1280×720 canvas, plus a small companion stylesheet for the branded elements. Content that has no Markdown equivalent — the overlay on the title slide, the info boxes — drops into inline HTML divs, which Quarto passes through. That is the honest cost of the format: when the layout stops being a list of bullets, you write HTML anyway, just less of it.
The model handled the syntax comfortably and produced a working deck on the first attempt with minor corrections. Editing it afterwards is the part that distinguishes it from everything else here. Changing a heading means changing a heading, and re-rendering.
HTML/CSS compared with Quarto
| HTML and CSS | Quarto and Reveal.js | |
|---|---|---|
| Authoring | HTML structure, hand-written CSS, JavaScript for behaviour | Markdown, with inline HTML where layout demands it |
| Presentation features | Built individually, or absent | Navigation, notes, fragments, transitions included |
| Styling | Unlimited, and entirely your responsibility | Theme first, extended through CSS or SASS |
| Code and maths | A highlighting library and MathJax to wire up | Built in |
| Output | HTML | HTML, PDF, and other formats from one source |
| Review | Diffs mix content and markup | Diffs are mostly prose |
Writing the deck from scratch buys precise control at the cost of building the presentation machinery yourself — and, as the previous section shows, the machinery is the part that gets quietly skipped. Reveal.js has already solved that problem, and Quarto makes it reachable from Markdown. For anything short of genuinely bespoke design work, that is the better trade.
Google Slides
Two routes lead into Google Slides programmatically. Apps Script runs JavaScript inside the Google account, needs no setup, and suits automation that stays within Workspace; it is also confined to it, and subject to execution quotas. The Slides API is the route for anything external: a REST interface, a Cloud Console project, an OAuth flow, and client libraries in most languages. I used the API, since it is what an LLM-driven pipeline would realistically target.
The generated script is 977 lines for five slides, and its shape explains why. Nothing can be positioned by name, so every element gets an identifier the script invents and then threads through subsequent requests — objectId appears 71 times. Nothing can be positioned relatively, so every box carries absolute coordinates, 73 of them. Images cannot be referenced from disk: they are uploaded to Drive first, and their Drive file identifiers are passed back into the slide requests. Five per-slide builder functions, one running to roughly 180 lines, assemble a request list that is finally submitted in a single batchUpdate.
Getting there took many rounds of iteration and manual repair. Errors surfaced late, in the batch response, phrased in terms of object identifiers rather than anything visible on a slide. The result works, and the fidelity is respectable — 16:9, correct colours, correct text. It is also code that neither I nor the model would enjoy modifying: to move a box, you find its identifier and edit a coordinate.
PowerPoint
PowerPoint offers more room to manoeuvre, because .pptx is an open OOXML format that a library can write without PowerPoint installed. In Python that library is python-pptx; Node has pptxgenjs, Java has Apache POI, .NET has Microsoft’s own Open XML SDK for raw part-level manipulation, and the Microsoft Graph API covers file management in Microsoft 365 while its slide-authoring capabilities remain thin. VBA and Office add-ins automate the running application rather than generating files. For generating a deck from a script, python-pptx is the default choice, and it is the one I used.
At 270 lines the script is far shorter than the Google one, and it needs no authentication. It is also the only output of the four with defects I did not notice until I opened the file, and all three are worth stating precisely.
The deck is 4:3. The script sets its canvas to ten inches by seven and a half, directly beneath a comment announcing a widescreen format:
prs = Presentation()
# Use a widescreen format (16:9)
prs.slide_width = Inches(10)
prs.slide_height = Inches(7.5)Ten by seven and a half is 4:3, and the exported PDF measures 720×540 points to prove it. The Quarto deck is 1280×720 and the Google deck exports at 720×405, both correctly 16:9. Only this one is letterboxed, and the comment sitting above the mistake asserts the opposite.
The title slide’s semi-transparent overlay is not transparent. The script asks for it this way:
shape.fill.solid()
shape.fill.fore_color.rgb = RGBColor(255, 255, 255)
shape.fill.transparency = 0.25python-pptx has no transparency property on FillFormat. The assignment binds an attribute on the Python object, no alpha value is ever written into the XML, and the rectangle renders opaque white over the photograph. Nothing raises, nothing warns. The same call appears again later with the comment # Make it see-through, and does nothing there too.
The title is misspelled: LIVRET D'ACCEUIL where the booklet reads ACCUEIL. The Quarto deck, the HTML deck, and the Google script all spell it correctly. Only the transcription into python-pptx slipped, and only here is there no rendered source to proofread against.
None of the three is a hard failure. The script runs to completion, exits cleanly, and writes a .pptx that opens. Everything that is wrong with it is visible only in the output.
What the four attempts cost
Five slides, one model, four formats:
| Format | Source | Lines |
|---|---|---|
| Quarto/Reveal.js | quarto/presentation.qmd |
130 |
| PowerPoint | powerpoint/create_powerpoint_slides.py |
270 |
| HTML/CSS | raw/presentation.html |
492 |
| Google Slides | google/create_slides.py |
977 |
The spread is wide enough to be the argument on its own. The Quarto file is seven times shorter than the Google script for the same five slides, and it is also the only one of the four that a non-programmer could edit.
The failures are more telling than the line counts, because they sort by format rather than by difficulty:
| Format | What went wrong | How it surfaced |
|---|---|---|
| Quarto/Reveal.js | Minor corrections on first render | Visible in the source |
| HTML/CSS | No navigation — a scrolling page, not a deck | On trying to present it |
| Google Slides | Many rounds of iteration and manual repair | Batch errors naming object identifiers |
| PowerPoint | 4:3 canvas, inert transparency, misspelled title | Only on opening the file |
Read down the last column and the pattern is the one Section 1 predicts. Where the model’s output is the artifact, mistakes are in plain text and you catch them by reading. Where the model’s output is a program that builds the artifact, the program can be entirely well-formed and still be wrong, and the only detector is a human looking at the result. The API-driven formats are not harder for the model in some abstract sense; they are harder to check.
That also reframes what “fidelity” means for this comparison. The Google deck looks better than the PowerPoint one, but it took the most work to get there and would take the most work to change. The Quarto deck required the least of both.
Conclusion
| Quarto/Reveal.js | Google Slides | PowerPoint via code | |
|---|---|---|---|
| Path from prompt | Markdown, rendered | JSON requests against a live document | Python that emits shapes |
| Reliability | High — errors are visible in the source | Low — object identifiers, absolute coordinates | Low — errors are silent |
| First draft | One file, one render | OAuth setup, then iteration | Fast to run, slow to get right |
| Editing later | Plain text, readable diffs | Edit the code that edits the deck | Verbose generated script |
| Collaboration | Git-native | Strong in the GUI, weak on generated code | Strong in the GUI, weak on generated code |
If a model is in the loop, Quarto/Reveal.js is the most reliable path from prompt to slides. GUI tools remain better for humans working together on a deck; they are poor targets for generation, and worse targets for verification.
The editor this suggests
The remaining gap is tooling rather than model capability. What the experiment points to is an editor that keeps Markdown underneath and puts a real interface on top: source and live Reveal.js preview side by side; a chat that is aware of the current slide and selection, so that “tighten this bullet” or “split this slide in two” become structured edits to the .qmd; an outline view, themes, notes, and asset management; Git-native diffs against the plain-text source.
The one feature that matters most is the least glamorous: export to .pptx. People ask for PowerPoint files, and they will keep asking. The lesson here is not that PowerPoint should be avoided but that a model should not be the thing writing it — a deterministic Markdown-to-.pptx converter can handle that hop, and can be tested, which is exactly what the generated scripts could not be.
Citation
@online{brosse2025,
author = {Brosse, Nicolas},
title = {Generating Slide Decks with an {LLM:} A Comparison of
Formats},
date = {2025-08-15},
url = {https://nbrosse.github.io/posts/llm-slides/llm-slides.html},
langid = {en}
}