Andreas Kröpelin

Building slides with Pluto.jl

January 14, 2021

I recently gave my first talk (available on GitHub) using nothing but a Pluto notebook as slides. Here are some things I learned.

What is a Pluto notebook?

If you haven't heard of it before, the Pluto.jl package is a very powerful contender to Jupyter notebooks. While Jupyter is somewhat language-agnostic (can you spot the three programming languages hiding in its name?), Pluto is made specifically for Julia. One of its core features is knowing what other cells a cell depends on and updating the dependents accordingly! Say, one cell defines x = 3 and another y = x + 4, then y is automatically updated whenever you change x.

Another nicety is the possibility to hide code cells (by clicking on the eye-button) and/or hide outputs (by appending a ; to the code). This is very useful if you're concerned with how things look, as you probably are when it comes to slides.

Definitely go check this package out if you haven't already, it's probably one of the greatest contributions to the Julia ecosystem so far, in my opinion.

Building slides

For the rest of this article, I assume that you know what Pluto is about and can work with it. Let's start by writing down what we would like to achieve:


As obvious as this goal is, it's actually not (yet) trivial in Pluto. It boils down to what we can find in this issue.

Pluto has an undocumented feature: It starts a hidden presentation mode when the JavaScript function present() is called (not the Julia function!). This has the following effects:

This may feel like a hack and it surely is one but it does work quite nicely. For a real slide-appearance, though, I found it necessary to manually zoom in in the browser (200 % worked for me) and put my browser into fullscreen mode (press F11 or something like that (yeah, you probably know that yourself, I just recently found out there is a <kbd> tag in HTML and wanted to brag with that skill)).

To make calling that JavaScript function convenient, you can add the following to the beginning of your notebook:

html"<button onclick='present()'>present</button>"

Now you only have to click a button. Clicking it again will leave presentation mode.

Taking the traditional route, your slides will have a good amount of textual information, so the md"" (or md""" """ for multiline content) string macro is your valuable companion. Using it effectively is the goal of the next section.

Textual information

Markdown supports quite a range of typography, especially headings, lists, and highlighting. Use it as it suits your needs (keep text on slides compact, you know the deal). Here is what I found to be particularly useful:

Strong text (inside ** **) is nice for highlighting single words but can be too weak for really important statements. Use low order headings (order 5 or 6) for that.

A symbol can express in maximal brevity what you would, otherwise, need many words for. Make use of Julia's excellent Unicode support and Pluto's tab-completion! Need an arrow? \rightarrowTab is your friend. Or what about \rightwavearrow or \downzigzagarrow for your fancy proof by contradiction?

Speaking of proofs, Julia's markdown parser supports maths typesetting. With ``\sin(x \cdot \pi)`` for inline maths and

\int \mathrm{e}^{-x^2} \; \mathrm{d}x

for display style maths, you can summon the powers of LaTeX (or MathJax, rather).

Sometimes you want to have text that clearly stands out from its surrounding, be it for a more/less important note, an interjection, or whatever comes to your mind. Pluto formats blockquotes using a rounded box with gray background so you can use that:

I don't really care if you actually read this...

> But this is important!
> I will ask this in the exam!

And now you can go back to playing on your phone.


While Pluto strictly places content as a large vertical stack, it also allows for arbitrary HTML which we can use for advanced layouting. I present two ideas how we can utilise Julia's type- and IO-system to make our lifes easier.

Foldable content

You might want to have content that is only visible after explicitly clicking a certain element. HTML offers the <details> tag that does exactly that:

    <summary> A secret </summary>
    <p> Pluto is fun </p>


A secret

Pluto is fun

Now let's define a type Foldable that represents a title and some content, as well as telling Julia how we would like to have it displayed by overriding

struct Foldable{C}

function, mime::MIME"text/html", fld::Foldable)
    show(io, mime, fld.content)

It basically says, whenever a Foldable is supposed to be shown on a display capable of HTML (as Pluto is), first write this HTML, then do whatever its content does when it's displayed as HTML, and finally close the HTML tags. We can now just write Foldable("What is the gravitational acceleration?", md"Correct, it's ``\pi^2``.") or even Foldable("Some cool plot:", plot(0:10, x -> x^2)).

Side by side

Having content arranged in two columns, like some text on the left and an image on the right, can look quite nice. The HTML makes use of CSS flexbox this time, but the rest is very similar to the above:

struct TwoColumn{L, R}

function, mime::MIME"text/html", tc::TwoColumn)
    write(io, """<div style="display: flex;"><div style="flex: 50%;">""")
    show(io, mime, tc.left)
    write(io, """</div><div style="flex: 50%;">""")
    show(io, mime, tc.right)
    write(io, """</div></div>""")

You can use it as TwoColumn(md"Note the kink at ``x=0``!", plot(-5:5, abs)). Varying the column widths or introducing a further parameter for that is possible, of course.


Until now, you might wonder why you shouldn't just use LaTeX Beamer (or even PowerPoint *shiver*). And if your slides are rather static there's really no point in using Pluto for them. But if you want cool interactivity, then Pluto has your back!

One cannot overstate how cool Pluto's reactiveness is. As said in the beginning, whenever you change some piece of code, everything dependent on that is updated. You might, for example, have this kind of code:

result = some_fancy_computation(parameters)

followed by this cell:

md"Some text where $result is interpolated."

Now changing parameters automatically changes this Markdown text.

Showing code

While you probably hide most of the code cells in a presentation notebook (especially those cosmetic ones we considered so far), Julia's syntax is quite presentable and can be displayed to show directly what happens. Why not reveal a cell that says

plot(data.time, data.gdp)

and be able to change data.gdp to, say, data.avg_income if you had not planned to present this but are asked to do so by someone in the audience? Or, if your code did not turn out so concise, you can just wrap it in a simple function with few parameters (Clean interfaces, everyone! It always pays off! :p).

I think displaying code is generally helpful for discussions like "Nice finding, but what if we do …?" during a talk.


The Pluto package has a little sister, PlutoUI, which makes interacting with the code visually much more appealing. Check out its documentation for all the features, I'll just gloss over what I have found most useful for presentations.

The fundamental concept here is Pluto's @bind macro. With @bind x some_ui_element, you can get arbitrarily complex UI elements that the user can interact with to change x. Most of the time, you will probably use

@bind x Slider(1:100, default = 42)

which will give you a .

If you need to chose a category instead, try @bind cat Select(["yesterday", "today", "tomorrow"]).

It happens that you'd like to change a variable at mutliple places. While Pluto does not allow redefining a variable, you can have

slider = @bind x Slider(10:20)

and then put slider into any slide you need. This spares you going back and forth a few slides just to adjust x, but note that the appearance of the UI element is not automatically updated at all sites, so it will look wrong!


No need to convince you of their importance, right? I guess you generally have three types of graphics: preexisting images, drawings, and plots.

Preexisting images

This is best done using PlutoUI again. You either use


for online sources (preferred) or


for local files (discouraged, unless you somehow make sure to always distribute them with the notebook). These two types are not restricted to images, by the way, you can display audio and video as well.


If you're the kind of person that heavily uses TikZ or diagrams in slides, you might look for something like Luxor.jl. It allows for a concise, imperative style of building graphics, quite a bit simpler than TikZ, I would say. I recommend using its @draw macro (instead of @png or @svg) to prevent your working directory from being cluttered with graphics files:

@draw begin
    text("A", Point(0, 0), halign=:center)
    arrow(Point(0, 10), Point(0, 40))
    text("B", Point(0, 50), halign=:center)

For networky stuff consider GraphPlot.jl as well.


You probably know the deal with plots. Use the Plots.jl package and its vast ecosystem. When it comes to choosing a backend, Plotly is nice for its interactivity while GR is faster (I think) and supports animations, see the next section. Of course, it's a matter of taste to some extent, so consider the other backends as well. (PlutoUI has a feature to display terminal output if you insist on using UnicodePlots, for example ;))


Your plots don't need to be static! Maybe you have a dynamic simulation or you need that extra time dimension to visualise your high dimensional data? There are basically two possibilities here:

The easiest thing is to have an automatic, looping animation. All you need is the @gif macro from Plots:

@gif for φ in 0 : .1 : 2π
    plot(0 : .1 : 2π, t -> sin(t + φ))

Or, if you need more control, you can use @animate together with the gif() function:

    # show the first 10 builtin colours:
    animation = @animate for i in 1:10
        scatter([0], [0], msize=10, shape=:hexagon, mcolour=i)

    gif(animation, fps=10)

Suppose you have a fairly complex dynamics you'd like to demonstrate and you want to step through your animation manually. You could combine the following three cells to achieve this:

@bind plot_time Slider(1:42)
plots = let
    state = init_my_complex_system()
            update_state!(state, t)
            x, y = extract_x_and_y(state)
            plot(x, y)
        for t in 1:42

Now you can take your time discussing each step in the greatest detail without "Oh and it's gone. Let's wait a moment until it comes again … Ah, here! Nope, gone again."

Miscellaneous and final remarks

You're probably quite invested in making your code fast anyway. But especially when it comes to interactive slides, optimise for minimal execution time! All interactivity is lost when you and your audience have to wait a minute for that new plot.

Also, make sure the notebook loads fast. You might not always have the opportunity to load it long before your talk starts. And you don't want to be known as the one with the fancy pants notebook who always talks for ages until the slides are finally ready. I see two good solutions to this issue:

I once could enjoy my laptop crashing just when I wanted to start talking, and not only did I have to reboot and reconnect to the Zoom meeting but also start up the notebook and load DifferentialEquations again. Don't be like me and use the tips above.

This one is maybe trivial, but exploit that you can move cells freely in Pluto. Dependencies are figured out in a more complicated way anyway, so just move all utility code to the bottom where no one can see it. If you like, put two empty second order headings before that to produce an empty slide.

Pluto has just one scope for all cells which is nice in general but, if you can, use let blocks to keep the global scope clean, as I did in some examples above.

Now is probably the best time you'll ever have for Pluto based presentations. Live changing code is difficult from the middle of a stage, but in a virtual meeting, in front of your own computer, interacting with the notebook is really convenient. You might want to look into my notebook mentioned in the beginning for inspiration. Give it a try, I hope this article helps.