Making HTML Slides with the markdown Package

Yihui Xie

2023-12-05

1. Get started

Specify at least one CSS file (snap.css) and one JS script (snap.js) in YAML metadata:

---
output:
  markdown::html_format:
    meta:
      css: [default, slides]  # "slides" is an alias of "snap.css"
      js: [slides]  # "slides" is an alias of "snap.js"
---

You will learn more about snap.css and snap.js at the end of this presentation.


Equivalently, you can specify them in the arguments of markdown::mark_html():

markdown::mark_html("test.md", output = "test.html", meta = list(
  css = c("default", "slides"),
  js = "slides"
))

but I recommend that you specify these options in YAML metadata instead.


2. Create slides

There are two ways to create a new slide:

  1. Insert a horizontal rule (---).
  2. Or use the level-two section heading (##).

You must choose only one way, i.e., either use horizontal rules to separate all slides, or avoid horizontal rules but only use section headings.

The first way is more flexible—you don’t have to start a slide with a section heading.


2.1 Example (---)

## First slide

**Content**.

---

More _content_ on the next page.

---

## Third slide

2.2 Example (##)

## First slide

Content.

## Second slide

Content.

## Third slide

3. Keyboard shortcuts



4. CSS and styling

You can pass more CSS files to the css option, e.g., if you have extra.css under the same directory as the Markdown input file:

---
output:
  markdown::html_format:
    meta:
      css: [default, slides, extra.css]
      js: [slides]
---

If your input document is .Rmd, you can also embed CSS directly in a css code chunk:

```{css, echo=FALSE}
/* your CSS rules */
```

Below is a CSS code chunk in which we defined the font families for this presentation:

body {
  font-family: Georgia, serif;
}
.slide h1, .slide h2 {
  font-family: Baskerville, Garamond, serif;
}
code {
  font-family: Consolas, "Andale Mono", monospace;
  font-weight: bold;
}

4.1 Example: section numbers

When the Markdown option number_sections is enabled, all sections are numbered. You can hide all numbers via CSS:

.section-number { display: none; }

For this presentation, only section numbers for level-two headings are displayed:

#TOC > ul > li > .section-number,
h2 > .section-number {
  display: inline-block;
}

4.2 Example: TOC

If you enable the table of contents (TOC) by setting the option toc: true, you will get a TOC slide after the title slide. It uses a two-column layout by default. You can custom its styles via the CSS selector #TOC. For example, you can use three columns:

#TOC { columns: 3; }

Or define the TOC title by:

#TOC::before { content: "Outline"; }

Or shorten TOC (hide lower-level headings):

#TOC li ul { display: none; }

For this presentation, we don’t hide lower-level headings in TOC but just make them more compact (display: inline;):

#TOC li ul li {
  display: inline;
  border-left: 0.2em dotted #ccc;
  padding-left: 0.2em;
}
#TOC li ul li a {
  color: #666;
  text-decoration: none;
}

4.3 Responsive layout

Media Width Mode Font size Overview columns
Super large devices ≥ 1800px 4
Larger desktops ≥ 1400px 3
Desktops ≥ 992px Slides 200% 2
Phones and tablets < 992px Article 100% N/A

You can resize your browser window to see the effect (also try to press o and test the overview mode). If you are on a mobile device, you should see a normal continuous page, since you cannot adjust the window size.


4.4 Printing


5. Slide attributes

You can add more attributes to a slide via an HTML comment that starts with # (Cmd / Ctrl + Shift + C in RStudio’s visual Markdown editor), e.g.,

<!--# class="inverse" style="color: red;"
contenteditable -->

The syntax is just HTML. These attributes will be added to the slide container:

<div class="slide inverse" style="color: red;" contenteditable>
</div>

5.1 Built-in classes

You can define your own arbitrary class names (e.g., <!--# class="large" -->) and corresponding CSS rules (e.g., .large { font-size: 150%; }).


5.2 Example: an inverse slide

<!--# class="inverse" style="font-size: 130%;" -->

5.3 Example: center content

Everything is centered both vertically and horizontally.

<!--# class="middle center" -->

Of course, you don’t have to use both classes at the same time. Depending on how you want to center content, you can use one of these classes.


5.4 Example: fade a slide

<!--# class="fade" -->

This slide is not important. You do not need to read it carefully. You can even take a nap, since the speaker is boring.


5.5 Example: a background image

<!--#
style="background-image: url(path/to/image);"
-->

We use the style attribute to introduce a background image to this slide. You can learn more about the background-image property here.


5.6 Example: an editable slide

<!--# contenteditable -->

Believe it or not, this slide is editable because we have enabled the contenteditable attribute. If you find any mistake on your slide during your presentation, you can click on it and edit any text.

Note that your edits will not be saved, though.


6. Miscellaneous elements

6.1 Page numbers

They are placed in <span class="page-number"></span> at the bottom right of all slides.

If you click on a page number, the URL of the presentation will be appended by a hash of the form #N, where N is the page number. You can share this URL with other people when you want to point them to a specific page in the slides.


6.2 Timer

A timer is added to the bottom left in <span class="timer"></span> by default. If you want a countdown timer, you can add a custom <span> to your document (in Markdown, you can use a raw HTML block ```{=html}), and specify the total number of seconds in the attribute data-total, e.g.,

<span class="timer" data-total="1200"></span>

The timer will start when the presentation becomes fullscreen. To restart the timer, click on it.

For the countdown timer, when the time is up, the timer will start to blink.


7. Caveats

7.1 Lengthy slides

When the height of a slide exceeds the window height, you need to be careful because it can be easy to accidentally scroll to the next page as you approach the bottom of the slide.

One solution is to add more space to the bottom of the slide using the extend class.

<!--# class="extend" -->

This will add a placeholder with the class spacer to the end of the slide. This placeholder has a height of 50vh, i.e., half of the window height (you can customize it via CSS). That means as soon as the bottom of the slide reaches the middle of the screen, you will be navigated to the next slide. Be careful! If you move from an oversized slide to the next slide by accident, you will not be able to move back to the bottom of the previous slide directly! Instead, you will always be navigated to the top of the previous slide if you want to go back. When you are on a long slide, I recommend that you use your mouse wheel or the Down arrow key to scroll at small steps, instead of using the PageDown key to scroll over to the next screen at once.

Because of this hassle, you may not really want to make a slide lengthy, but it may be unavoidable when you have lengthy content to show on one slide and it cannot be broken. Below is a toy example (the output block cannot be split onto two slides):

cat(1:10, sep = "\n")
1
2
3
4
5
6
7
8
9
10

7.2 Aspect ratio

The aspect ratio of slides is not defined by default. The content will fit all available space on the screen. If you present the slides directly with your own computer, this may not be a problem since you know if all content fits well on your own screen. However, if you connect your computer to a projector or present the slides on another computer, you’d better know the screen resolution beforehand, because the aspect ratio may be different, and your slides can look different on that screen.

You can fix the aspect ratio by setting the body width in CSS. For example, if your computer screen’s resolution is 900 × 1440, and you want the aspect ratio 4:3, you should set the body width to 1200px (= 900 × 4/3).

body { width: 1200px; }

Or set the width to a proportion of the height. A relative value will work with any screen height, e.g., 4/3 ≈ 1.33:

body { width: 133vh; }

However, note that when the presentation is not fullscreen, the slides will not take the full screen height (the browser UI elements such as the menu and tool bar will take some vertical space), so 133vh will be an underestimate of the width.


8. Technical notes

How does this HTML presentation work under the hood?

8.1 The original HTML

<h2>First slide</h2>
<p>Content</p>
<hr>
<h2>Second slide</h2>
<p>More content</p>
<hr>

This can be generated from Markdown.


8.2 snap.js

The script snap.js converts HTML to a structure more convenient to style as slides:

<div class="slide-container">
  <div class="slide">
    <h2>First slide</h2>
    <p>Content</p>
  </div>
  <div class="slide">
    <h2>Second slide</h2>
    <p>More content</p>
  </div>
</div>

8.3 snap.css

The core technique is CSS Scroll Snap. The full CSS is snap.css, and the essential rules are:

html {
  scroll-snap-type: y mandatory;
}
.slide {
  min-height: 100vh;
  scroll-snap-align: start;
}

The JS and CSS code can be used outside the R package markdown, too. You just need to have the correct HTML structure. Then in your HTML document:

<script src="https://cdn.jsdelivr.net/gh/rstudio/markdown@VERSION/inst/resources/snap.min.js" defer></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/combine/gh/rstudio/markdown@VERSION/inst/resources/default.min.css,gh/rstudio/markdown@VERSION/inst/resources/snap.min.css">

Remember to substitute VERSION with an appropriate version (e.g., 1.11). You can omit @VERSION to use the latest version but it is not recommended because future updates might break your old slides.

Both the JS and CSS are quite lightweight (total size is 8.6 Kb when uncompressed) and have no dependencies. They were written from scratch.


9. Enjoy!

  1. See this post for a possible application of this odd feature.