An Ultimate Guide for non-WLJS or non-WL users
The idea of programmatically generating slides and graphics for presentations, reports, or lecture notes is far from new. Today, you can do this using Python, HTML, JSX, Julia, and more. Most of these tools follow a similar concept—combining declarative markup like Markdown and HTML. We’ll follow a similar path but add support for dynamic elements, reusable components, and event bindings. Sounds complicated? Actually, the goal is to simplify.
⚠️ Heads-up: This approach involves traditional text-based programming.
⚠️ Warning: Lots of images ahead. It is about presentations 😄
Introduction & Motivation
In academic settings or at conferences, presentations can be critical to conveying ideas. In Russian academic culture, form was historically considered secondary to content. But times have changed. Visuals are richer, animations more common. Some journals even require eye-catching thumbnails to attract broader readership.
Yet, interactivity in presentations—and in publications—is often overlooked. This area has potential, especially for internal reports, lecture notes, or educational materials where interactivity could aid understanding.
Take a look at this example ⭐️
Creating traditional slides is time-consuming: drag items, align, format... And if you're dealing with 3D content (protein structures, crystal models), you’ll often resort to GIFs.
My personal issue with this workflow is the cycle:
- Prepare data in one environment
- Plot it in another
- Export to file
- Format into slides
- Repeat from step 2 if changes are needed
Wouldn’t it be better if we could reuse visual elements like templates? This is where a declarative, component-based approach starts to shine ⤵️
Declarative Markup
Let’s revisit our roots. TeX Beamer is likely one of the earliest tools in this space:
\documentclass{beamer}
\title{Sample title}
\author{Anonymous}
\institute{Overleaf}
\date{2021}
\begin{document}
\frame{\titlepage}
\begin{frame}
\frametitle{Hi Harbor}
This is some text in the first frame.
\end{frame}
\end{document}
Beamer is extremely powerful, but also intimidating. For something easier and web-native, there's RevealJS:
# Heading
## Subheading
Hi there!
---
# Next Heading
Hi again!
Slides are separated by ---
, and the layout is controlled by CSS. Since RevealJS runs in a browser, you can use raw HTML—allowing things like video, audio, PDFs, even entire websites using iframe
. Want Mermaid diagrams? You can embed those too.
But interactivity and reusable components? Not quite built-in.
RevealJS is a framework, not a full system. Even referencing an image locally can become tricky. This is where tools like Motion Canvas shine. Based on JSX and React, they treat everything as a component:
import {makeScene2D, Txt} from '@motion-canvas/2d';
import {beginSlide, createRef, waitFor} from '@motion-canvas/core';
export default makeScene2D(function* (view) {
const title = createRef<Txt>();
view.add(<Txt ref={title} />);
title().text('FIRST SLIDE');
yield* beginSlide('first slide');
yield* waitFor(1);
title().text('SECOND SLIDE');
yield* beginSlide('second slide');
yield* waitFor(1);
title().text('LAST SLIDE');
yield* beginSlide('last slide');
yield* waitFor(1);
});
Or MDX:
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
## Desktop App
<Tabs defaultValue="Windows" values={[{label: 'Windows', value: 'Windows'}, {label: 'Linux', value: 'Linux'}, {label: 'Mac', value: 'Mac'}]}>
<TabItem value="Windows">- Windows build</TabItem>
<TabItem value="Linux">- Linux builds</TabItem>
<TabItem value="Mac">- Mac builds</TabItem>
</Tabs>
JSX might feel complex, but it introduces very useful concepts:
- Components as functions
- Custom HTML-like tags
So you can write something like this once:
<MakeTitle>Slide Header</MakeTitle>
Here's your content.
<SomeWidget align="center" />
Feels like a cross between Beamer and JSX. Now let’s see how to implement it.
Bridging Worlds ⚗️
As a physicist, it’s easier to demonstrate calculations with sliders—especially during lectures.
JSX/React means adopting frontend tooling (Vite, bundlers, etc.), which may be too much overhead for quick educational material. Plus, JavaScript isn’t ideal for scientific plotting. Python, R, Julia, or even MATLAB often provide a smoother experience.
But as of 2025, no platform beats Wolfram Mathematica for producing clean, precise, interactive plots with minimal setup.
ContourPlot[Cos[x] + Cos[y], {x, 0, 4 Pi}, {y, 0, 4 Pi}]
Want interactivity?
Manipulate[Plot[Sin[a x], {x, 0, 2 Pi}], {a, 1, 10}]
Our goal is to make these tools feel native inside Markdown + HTML.
Figure = ContourPlot[Cos[x] + Cos[y], {x, 0, 4 Pi}, {y, 0, 4 Pi}];
# First Slide
Look at this plot:
<Figure />
Want to style it?
<div style="background: gray; border: solid 1px red;">
<Figure />
</div>
Or make it a reusable component. This is where Wolfram Language XML (WLX) comes in—a syntax extension designed to bring this all together. You don’t need to install anything extra—it’s already integrated in the environment we’ll explore next.
TL;DR – How to Code Presentation Slides
Environment: WLJS Notebook
Download binaries here. You don’t need the app to view presentations—a browser is enough, even offline.
This is a client-server setup. The client is your browser (or Electron app), and computations happen on the server.
Cells are typed—starting with extensions like .slide
. If you don’t specify anything, it's treated as standard Wolfram Language.
You can attach images, insert code blocks, define graphics inline, or dynamically. Everything gets evaluated and embedded inside your final slide.
First Slide with Images and Graphs
Let’s create our first .slide
cell with an image. Simply drag and drop a picture into the cell:
.slide
# Hey There!

The image is automatically uploaded to the notebook directory and inserted as a Markdown image reference. Press Shift + Enter or click the play button to render the slide.
Now let’s move from 2D to 3D with a custom graph. First, define a helper function:
square[{{imin_, imax_}, {jmin_, jmax_}}] :=
Table[
UnitStep[i - imin, imax - i] UnitStep[j - jmin, jmax - j],
{i, 0, 20}, {j, 0, 20}
]
Then create the plot:
OurPlot = ListPlot3D[square[{{2, 5}, {3, 7}}], Mesh -> None];
Now insert it into your slide:
.slide
# Hey there!
<OurPlot />
Yes—you can rotate it live in the browser.
Prefer Plotly? We’ve got you covered. The Plotly interface is available and works identically to the JS version:
OurPlotly = With[{data = square[{{2, 5}, {3, 7}}]},
Plotly[<|"type" -> "surface", "z" -> data|>]
]
Important Notes on WLX
- Tags starting with a lowercase letter are interpreted as HTML.
- Tags starting with an uppercase letter are WL components.
- All tags must be properly closed.
- Leave a blank line above and below Markdown headers and HTML blocks to avoid parsing issues.
- Full WLX documentation and presentation-specific docs here.
Presenting Slides
Want fullscreen mode? Press f
on any slide or choose “Project to a New Window” in cell settings.
Want to show all slides in sequence? Just use the .slides
cell to collect content from all other .slide
cells:
.slides
Thank you for your attention
This aggregates slides across the notebook regardless of order or location.
Styling Your Slides
Minimalist slides are often best—just a graphic and a short list. But if you want custom fonts, themes, or logos, use a .wlx
cell to insert CSS:
.wlx
<style>
.reveal h1 {
font-family: consolas;
}
</style>
Reusable headers or footers? Easy. Let’s define and use them as components next...
Reusable Local Components
Columns Layout
Want to create a two-column layout? Here’s a simple component implementation using WLX:
.wlx
Columns[data__, OptionsPattern[]] := With[{
Style = OptionValue["Style"]
},
With[{DataList = Table[
<div>
<Item />
</div>,
{Item, List[data]}
]},
<div class="flex flex-row justify-between" style="{Style}">
<DataList />
</div>
]
]
Options[Columns] = {"Style" -> ""};
Use it on a slide like this:
.slide
# Hey There!
<Columns>
<p>Column 1</p>
<p>Column 2</p>
</Columns>
You can safely use Markdown by wrapping content in <p>
tags and adding blank lines:
<Columns>
<p>
# Heading 1
</p>
<p>
# Heading 2
</p>
</Columns>
Style the component directly:
<Columns Style={"
border-radius: 4px;
color: #ffffff;
background: rgb(49 87 170);
padding: 1rem;
"}>
<p>Heading 1</p>
<p>Heading 2</p>
</Columns>
Row Layout and Grouped Plots
You can also use the built-in Row
layout. Example: a signal and its Fourier transform side-by-side.
square[{{imin_, imax_}, {jmin_, jmax_}}] :=
Table[
UnitStep[i - imin, imax - i] UnitStep[j - jmin, jmax - j],
{i, 0, 20}, {j, 0, 20}
]
OurPlot = Row[{
ListPlot3D[square[{{2, 5}, {3, 7}}], Mesh -> None],
ListPlot3D[Abs@Fourier@square[{{2, 5}, {3, 7}}], Mesh -> None, ColorFunction -> "Rainbow"]
}];
.slide
# Example
<OurPlot/>
You can also bind each to a separate variable and use them together:
{Figure1, Figure2} = {
ListPlot3D[square[{{2, 5}, {3, 7}}], Mesh -> None],
ListPlot3D[Abs@Fourier@square[{{2, 5}, {3, 7}}], Mesh -> None, ColorFunction -> "Rainbow"]
};
.slide
# Example
<Row>
<Figure1 />
<Figure2 />
</Row>
Footers and Headers
You can use the same pattern to define reusable headers and footers—great for academic presentations. Example:
.wlx
MakeTitle[Title__String] := MakeTitle[StringJoin[Title]]
MakeTitle[Title_String] := <div class="relative flex w-full text-left flex-row gap-x-4" style="align-items: center; margin-bottom:1.5rem;">
<div style="bottom:0; z-index:1; position: absolute; background: linear-gradient(to left, red, blue, green); width: 100%; height: 0.7rem;"></div>
<img style="margin:0; z-index:2; padding:0rem; border-radius:100px;" width="120" src="https://www.trr360.de/wp-content/uploads/2022/04/cropped-logo_small-1.png"/>
<h2><Title /></h2>
</div>
Footer = <div class="w-full ml-auto mr-auto bottom-0 text-sm absolute">
DFG Retreat Meeting TRR360: <i>C4 Ultrastrong matter-magnon coupling</i>, Kirill Vasin
</div>;
Use them like this:
.slide
<!-- .slide: style="height:100vh" -->
<MakeTitle>Ultrastrong coupling</MakeTitle>
Content goes here...
<Footer />
Fragments & Animations
RevealJS supports slide fragments, which allow staged reveals of content. This can be used to animate text appearance or any HTML element.
Basic Example
.slide
<!-- .slide: data-background-color="black" -->
# Red <!-- .element: style="color:red" class="fragment" -->
# White <!-- .element: style="color:white" class="fragment" -->
Each .fragment
appears step-by-step as the user presses →. You can also control the order using data-fragment-index
:
# Red <!-- .element: style="color:red" data-fragment-index="1" class="fragment" -->
# White <!-- .element: style="color:white" data-fragment-index="1" class="fragment" -->
You can apply the same styling to any HTML element.
Math & LaTeX
Wolfram supports LaTeX rendering directly. Just avoid single backslashes unless you escape them (\\
).
.slide
## LaTeX
$$
\\begin{align*}
\\mathbf{E}(t,x) &= \\sum_{\omega} \\mathbf{E}_0^{\omega} ~\\exp\\Big( i\\omega t - \\frac{i\\hat{n}(\\omega) \\omega x}{c}\\Big) \\\\
&= \\sum\\mathbf{E}_0^{\\omega} \\colorbox{white}{$\\exp(-\\frac{\\alpha x}{2})$} ~\\exp\\Big(i\\omega t - \\frac{i n \\omega x}{c}\\Big)
\\end{align*}
$$
Animated Equations
Use the data-eq-speed
attribute for animated reveal:
$$
\\begin{align*}
...your equation here...
\\end{align*}
$$ <!-- .element: data-eq-speed="0.1" -->
Diagrams with Mermaid
You can embed Mermaid diagrams using CellView
:
MyDiagram = CellView["
graph LR
A[Text Header] --> B[Binary Header]
B --> C1[Trace 1] --> T1[Samples 1]
B --> C2[Trace 2] --> T2[Samples 2]
", ImageSize -> 650, "Display" -> "mermaid"];
Use it in a slide:
.slide
# Embedded Diagram
<MyDiagram />
Source Code on Slides
To display syntax-highlighted WL source code inline, define a helper:
.wlx
CodeInset[str_String] := With[{Fe = EditorView[str]},
<div style="text-align: left; font-size:14px;"><Fe /></div>
]
Add styling to align properly:
.wlx
<style>
.slide-frontend-object .cm-editor {
text-align: left;
}
</style>
Use in a slide:
.slide
## Source code (WL) on the slide
<CodeInset>
1-((*FB[*)((1)(*,*)/(*,*)(6))(*]FB*)) ((*SpB[*)Power[x(*|*),(*|*)2](*]SpB*))+((*FB[*)((1)(*,*)/(*,*)(120))(*]FB*)) ((*SpB[*)Power[x(*|*),(*|*)4](*]SpB*))-(*FB[*)(((*SpB[*)Power[x(*|*),(*|*)6](*]SpB*))(*,*)/(*,*)(5040))(*]FB*)+(*FB[*)(((*SpB[*)Power[x(*|*),(*|*)8](*]SpB*))(*,*)/(*,*)(362880))(*]FB*)-(*FB[*)(((*SpB[*)Power[x(*|*),(*|*)10](*]SpB*))(*,*)/(*,*)(39916800))(*]FB*)
</CodeInset>
Excalidraw Integration
Excalidraw is a vector-based sketching tool that feels like a digital whiteboard. It’s lightweight, fast, and outputs SVG graphics.
To embed a drawing area, just use this simple syntax in a .slide
cell:
.slide
!![]
This creates an embedded drawing canvas. Your sketch is saved inside the widget itself, allowing it to be moved or nested inside other tags.
Support is still evolving, but it works well for basic annotation and ideas.
Dynamic Elements with Slide Events 🧙
Let’s build a simple dynamic counter that reacts to slide transitions.
Step 1: Static Widget
.wlx
Stat[Text_, OptionsPattern[]] := With[{
Count = OptionValue["Count"]
},
<div class="text-center text-gray-600 m-4 p-4 rounded bg-gray-100 flex flex-col">
<Count />
<span class="text-md"><Text /></span>
</div>
]
Options[Stat] = {"Count" -> 1};
Render it on a slide:
.slide
# Basic counter
<Stat Count={11}>Number of publications</Stat>
Step 2: Make It Dynamic
Add a module and use SlideEventListener
to update it in real-time:
.wlx
Stat[Text_, OptionsPattern[]] := Module[{
cnt = 0,
task
}, With[{
ev = CreateUUID[],
HTMLCounter = HTMLView[cnt // Offload],
max = OptionValue["Count"]
},
EventHandler[ev, {
"Destroy" -> Function[Null, EventRemove[ev]; If[task["TaskStatus"] === "Running", TaskRemove[task]]; ClearAll[task];],
"Left" -> Function[Null, cnt = 0],
"Slide" -> Function[Null, task = SetInterval[
If[cnt < max, cnt += 1, TaskRemove[task]], 15]]
}];
<div class="text-center text-gray-600 m-4 p-4 rounded bg-gray-100 flex flex-col">
<HTMLCounter/>
<span class="text-md"><Text/></span>
<SlideEventListener Id={ev}/>
</div>
] ]
Options[Stat] = {"Count" -> 1};
Now use multiple instances:
.slide
# Dynamic Counters
<Row>
<Stat Count={11}>Citations</Stat>
<Stat Count={110}>Hours</Stat>
<Stat Count={1010}>Symbols</Stat>
</Row>
Each counter is self-contained and responds only when the slide is active.
Interactive Plots with Manipulate
Remember Manipulate
in Wolfram Language? In this system, there's a more efficient version called ManipulatePlot
, which works great for presentations.
Try it first in a regular cell:
Now let’s embed it directly into a slide:
.slide
# Interactivity
You can drag the sliders!
<Widget />
Each slider input sends a signal back to the WL kernel to re-evaluate the plot in real time. But this doesn’t mean you can’t export it to HTML.
Precomputed Animations with AnimatePlot
Sometimes, full reactivity isn’t necessary. Use AnimatePlot
for looping, client-side animations.
AnimatePlot[
Sum[(Sin[2π(2j - 1) x])/(2j), {j, 1.0, n}],
{x, -1, 1},
{n, 1, 30, 1}
]
This approach caches every frame in advance—great for offline use or lighter devices.
Reactive Data: Real-Time Graphics Updates
Let’s simulate dynamic data updates using Offload
, which enables reactive primitives like Line
, Disk
, etc.
myData = Table[{x, Sin[x]}, {x, 0, 5 Pi, 0.1}];
Graphics[{
ColorData[97][1], Line[myData // Offload]
}, Axes -> True, TransitionDuration -> 1000]
Update the data elsewhere:
myData = Table[{x, Sinc[x]}, {x, 0, 5 Pi, 0.1}];
You can combine this with slide fragments, animations, or event triggers.
Custom Widgets Reacting to Fragments
Want to go beyond sliders? Let’s create a custom widget that reacts to slide fragments. For example, a reactive chart with a flying disk that moves when a fragment appears.
Step 1: The Widget Module
.wlx
PlotWidget[OptionsPattern[]] := Module[{
data = OptionValue["DataA"],
disk = OptionValue["DataA"] // Last
},
With[{
Canvas = Graphics[{
ColorData[97][1], Line[data // Offload],
ColorData[97][3], Disk[disk // Offload, {0.4,0.05}]
}, Axes -> True, ImageSize -> 500, PlotRange -> {{-0.2, 1.1 5 Pi}, 1.1 {-1, 1}},
TransitionDuration -> 500],
uid = CreateUUID[],
dataA = OptionValue["DataA"],
dataB = OptionValue["DataB"]
},
EventHandler[uid, {
"fragment-1" -> Function[Null,
data = dataB;
disk = dataB // Last;
],
("Left" | "Destroy" | "Slide") -> Function[Null,
data = dataA;
disk = dataB // First;
]
}];
<div class="flex flex-col gap-y-2">
<Canvas />
<div class="fragment">Dummy text</div>
<SlideEventListener Id={uid} />
</div>
]
]
Options[PlotWidget] = {"DataA" -> {}, "DataB" -> {}};
Step 2: Generate the Data
{dataA, dataB} = {
Table[{x, Sin[x]}, {x, 0, 5 Pi, 0.1}],
Table[{x, Tan[x]}, {x, 0, 5 Pi, 0.1}]
};
Step 3: Use in a Slide
.slide
# Interactivity
<PlotWidget DataA={dataA} DataB={dataB} />
---
# Second slide
External Event Wiring
You can wire multiple widgets to the same global event ID:
.slide
First fragment <!-- .element: data-fragment-index="1" class="fragment" -->
Second fragment <!-- .element: data-fragment-index="2" class="fragment" -->
<SomeWidget1 Event={"my-first-slide"} />
<SomeWidget2 Event={"my-first-slide"} />
<SlideEventListener Id={"my-first-slide"} />
This lets you control multiple widgets from one place—great for large presentations.
Procedural Backgrounds
Why not make your slide backgrounds dynamic? Let’s simulate orbiting balls around a center with smooth animation.
Step 1: Define the Background Generator
.wlx
BackImageDynamic := Module[{
frameEvent = CreateUUID[],
animationEvent = CreateUUID[],
slideEvent = CreateUUID[],
allowedQ = False,
trigger = 1
}, With[{
Canvas = Graphics[{
Black, Rectangle[{0,0}, {1,1}], Red,
curveDynamicGenerator[{0.5,0.5}, 0.8, animationEvent], Blue,
curveDynamicGenerator[{0.5,0.5}, 0.3, animationEvent],
AnimationFrameListener[trigger // Offload, "Event"->frameEvent]
}, "Controls"->False, ImagePadding->0, TransitionDuration->200, ImageSize->{960,700}, PlotRange->{{0,1}, {0,1}}]
},
EventHandler[frameEvent, Function[Null,
If[!allowedQ, Return[]];
If[Mod[trigger, 5] == 0, EventFire[animationEvent, True]];
trigger = trigger + 1;
]];
EventHandler[slideEvent, {
"Slide" -> Function[Null,
allowedQ = True;
trigger = trigger + 1;
Print["Animation started"];
],
("Destroy" | "Left") -> Function[Null,
allowedQ = False;
Print["Animation stopped"];
]
}];
<div>
<SlideEventListener Id={slideEvent}/>
<Canvas/>
</div>
] ]
Step 2: Create Curve Generator
curveDynamicGenerator[center_, radius_, ev_] := With[{},
Module[{
pts = Table[Norm[center - radius] {Sin[i], Cos[i]} +
center, {i, 0, 2 Pi + 0.1, 0.1}],
disk = {10,10},
modulation = 0.,
phase = 0.,
initial = 12. RandomInteger[{0,10}]
},
EventHandler[EventClone[ev], Function[Null,
pts = Table[(
Norm[center - radius]
+ 0.02 modulation Sin[50. i + 30 phase]
) {Sin[i], Cos[i]} + center
, {i, 0, 2 Pi + 0.1, 0.01}];
disk = With[{i = 3. phase + initial},
(Norm[center - radius]
+ 0.01 modulation Sin[50. i + 30 phase]
) {Sin[i], Cos[i]} + center
];
phase = phase + 0.02;
modulation = Sin[phase/2];
]];
{
Line[pts // Offload],
Disk[disk // Offload, 0.013]
}
]]
Step 3: Embed the Background on a Slide
.slide
<!-- .element: data-background-color="black" -->
<!-- .slide: style="height:100vh; color: white;" -->
<div class="flex flex-col h-full">
<div class="absolute w-full h-full" style="scale: 1.1; left:-30px; z-index:-100">
<BackImageDynamic/>
</div>
<div class="mt-auto mb-auto">
# Procedural background
It will be animated till slide is visible
</div>
</div>
---
<!-- .element: data-background-color="black" -->
<!-- .slide: style="height:100vh; color: white;" -->
Now the animation stops
Use CSS filter
to blur it if needed.
It might looks like an overkill, but in this form it does the following
- ensures a low CPU usage;
- component-like behaviour, i.e. you can copy and paste it to any presentation and reuse;
- stops animation if slide is not visible or removed;
- shares a single animation trigger between all curves and moving objects
Confetti with External JS
Step 1: Load the Library
.wlx
<script src="https://cdn.jsdelivr.net/npm/party-js@latest/bundle/party.min.js"></script>
Step 2: Define a JS Function to Trigger It
.js
core.RunFireworks = async (args, env) => {
const id = await interpretate(args[0], env);
party.confetti(document.getElementById(id).parentNode, {
count: party.variation.range(20, 40),
size: party.variation.range(0.8, 2.2),
});
}
Step 3: Trigger from Slide Event
.wlx
Party := Module[{
UId = CreateUUID[],
Ev = CreateUUID[]
},
EventHandler[Ev, {
"Slide" -> Function[Null,
FrontSubmit[RunFireworks[UId]]
]
}];
<div id="{UId}">
<SlideEventListener Id={Ev}/>
</div>
]
Step 4: Use It
.slide
# Let's have
---
# A Party!
<Party />
Exporting to Standalone HTML 🚀
Everything—plots, animations, code—is rendered by the browser. That makes exporting to HTML incredibly easy.
Step 1: Create a .slides
Cell
.slides
Thank you for your attention
This collects all .slide
cells.
Step 2: Click the Share Icon
Result: A Single HTML File
See it in action 👉 example link
What’s included:
- All graphs (2D, 3D, Plotly)
- All images (converted to base64)
- Code, styles, layout, assets
What’s not:
- Interactive sliders (they require kernel)
Exporting Dynamic HTML with Interactivity
Static HTML is great, but what if you want the sliders and interactivity without the kernel?
That’s where dynamic HTML export comes in. It works in two phases: sniffing and sampling.
Phase 1: Sniffer Mode
The sniffer listens to all interactions and records them. It captures how reactive widgets behave and stores possible input-output pairs.
You’ll need to manually move all sliders in the presentation to their full range. The system will record state changes and build a lookup table.
Widget := ManipulatePlot[Sum[(Sin[2π(2j - 1) x])/(2j), {j, 1.0, n}], {x, -1, 1}, {n, 1, 30, 1}];
.slide
# Offline interactivity
This Widget works with no Wolfram Kernel attached
<Widget/>
Phase 2: Sampling & Bundling
The sampler uses the sniffed events to generate all visual states. It stores these in compressed form and embeds them into your exported HTML.
No kernel is needed afterward.
How does it work?
Once exported, widgets don’t talk to a backend—they call the built-in response table. Think of it like a pre-recorded helpdesk answering slider movements.
Not perfect for everything, but works beautifully for stateless or time-based interactions.
See live: Offline HTML Example ⭐️
⚠️ Text inputs and checkboxes may behave unpredictably—this is still evolving. Please report issues on GitHub!
Published Examples 📔
These presentations were all exported using the techniques above:
- Why fitting the raw data matters – Augsburg 2024
- Basic animation presentation
- THz report on Fe₂Mo₃O₈ in a magnetic field
- Example with
ManipulatePlot
- 2-slide animation demo
Conclusion
This approach blends code and content into a single, powerful system. At a glance:
- Simpler than PowerPoint for tech-heavy talks
- Fully reproducible (every visual has traceable code)
- Runs in any browser—no dependencies
- Supports dynamic or static export to a single HTML file (free of the license restrictions)
Markdown becomes your structure, Wolfram Language your engine (sometimes with a help of Javascript). With a little setup, you can focus on content—not formatting.
And yes, interactivity makes learning and presenting way more engaging. Your students and colleagues will thank you ☺️
Links
- RevealJS – Markdown presentations (core engine)
- Excalidraw – drawing board (SVG tool)
- WLJS Notebook Docs
- Wolfram Engine
- Mathematica Programming Introduction (free PDF)
Happy sliding ✨