Jesse A. Green / notebook

reveal.js -- User Guide

Generated May 2026 from hakimel/reveal.js at 76dc900

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

reveal.js v6.0.1 is the HTML presentation framework that transforms markup into polished, interactive slide decks with animated transitions, speaker notes, code highlighting, math rendering, and more. Created by Hakim El Hattab. MIT licensed.

This guide covers everything you need to create, configure, and deliver presentations with reveal.js. For architecture details, plugin internals, and deployment strategies, see the Admin Guide.


1. Quick Start

1.1 Fastest Path: CDN-Free Local Setup

Create an index.html file with this minimal structure:

<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="dist/reset.css" />
    <link rel="stylesheet" href="dist/reveal.css" />
    <link rel="stylesheet" href="dist/theme/black.css" />
  </head>
  <body>
    <div class="reveal">
      <div class="slides">
        <section>Slide 1</section>
        <section>Slide 2</section>
        <section>Slide 3</section>
      </div>
    </div>
    <script src="dist/reveal.esm.js" type="module"></script>
    <script type="module">
      import Reveal from './dist/reveal.esm.js';
      Reveal.initialize();
    </script>
  </body>
</html>

1.2 Using npm

npm install reveal.js

Then import in your JavaScript:

import Reveal from 'reveal.js';
import 'reveal.js/dist/reset.css';
import 'reveal.js/dist/reveal.css';
import 'reveal.js/dist/theme/black.css';

let deck = new Reveal();
deck.initialize();

1.3 Full Development Setup

Clone the repository and run the dev server:

git clone https://github.com/hakimel/reveal.js.git
cd reveal.js
npm install
npm start

The Vite dev server launches at http://localhost:8000 by default. Change the port with the npm_config_port environment variable:

PORT=3000 npm start

Note

reveal.js requires Node.js 20.19.0 or later. The project uses Vite 8 for development and TypeScript 6 for type checking.


2. Installation

npm install reveal.js

Package exports available after installation:

Import Path Content
reveal.js Core library (ESM + CJS)
reveal.js/dist/reveal.css Core presentation CSS
reveal.js/dist/reset.css CSS reset
reveal.js/dist/theme/*.css Theme stylesheets
reveal.js/plugin/highlight Syntax highlighting plugin
reveal.js/plugin/markdown Markdown plugin
reveal.js/plugin/math Math rendering plugin
reveal.js/plugin/notes Speaker notes plugin
reveal.js/plugin/search In-slide search plugin
reveal.js/plugin/zoom Element zoom plugin

2.2 From Source

git clone https://github.com/hakimel/reveal.js.git
cd reveal.js
npm install
npm run build

The build outputs to dist/:

  • dist/reveal.esm.js -- ES module build
  • dist/reveal.js -- UMD build
  • dist/reveal.css -- core styles
  • dist/theme/ -- compiled theme CSS

2.3 React Integration

For React applications, use the official wrapper:

npm install @revealjs/react reveal.js

Requires React 18+ and reveal.js 5+ as peer dependencies.

import { Deck, Slide, Fragment } from '@revealjs/react';

function Presentation() {
  return (
    <Deck>
      <Slide>
        <h1>Hello World</h1>
        <Fragment>
          <p>This appears on click</p>
        </Fragment>
      </Slide>
    </Deck>
  );
}

Available components: Deck, Slide, Stack, Markdown, Fragment, Code. Available hook: useReveal() (returns the Reveal instance from RevealContext).

Tip

When using @revealjs/react with Vite 8, be aware that Markdown fragment directives may not work correctly (see GitHub issue #3883). Use JSX Fragment components as a workaround.


3. Configuration

3.1 Initializing with Options

Pass a configuration object to Reveal.initialize():

Reveal.initialize({
  width: 960,
  height: 700,
  controls: true,
  progress: true,
  hash: true,
  transition: 'slide',
  plugins: [ RevealHighlight, RevealNotes ]
});

3.2 Presentation Size and Scaling

Option Default Description
width 960 Base width in pixels
height 700 Base height in pixels
margin 0.04 Empty space around content (as a factor)
minScale 0.2 Smallest scale factor applied
maxScale 2.0 Largest scale factor applied

reveal.js scales presentations to fit any display while maintaining aspect ratio. The margin value creates breathing room around slides -- 0.04 means 4% of the viewport on each side.

3.3 Controls and Navigation

Option Default Description
controls true Show navigation arrows
controlsTutorial true Highlight arrows on first encounter
controlsLayout 'bottom-right' Arrow position
controlsBackArrows 'faded' Back arrow visibility ('faded', 'hidden', 'visible')
progress true Show progress bar at bottom
slideNumber false Display slide number (see below)
showSlideNumber 'all' Where slide numbers appear ('all', 'print', 'speaker')
keyboard true Enable keyboard navigation
keyboardCondition null Condition function for keyboard activation
touch true Enable touch navigation
loop false Loop the presentation
rtl false Right-to-left slide flow
navigationMode 'default' Navigation model ('default', 'linear', 'grid')
shuffle false Randomize slide order on init
overview true Enable overview mode
center true Vertically center slide content
jumpToSlide true Allow jumping to slides via slide number
help true Show ? help overlay
pause true Allow pausing with B or .

Slide number formats:

// Boolean
slideNumber: true       // e.g. "3"

// String format
slideNumber: 'h.v'     // "2.1" (horizontal.vertical)
slideNumber: 'h/v'     // "2/1"
slideNumber: 'c'       // "5" (flattened count)
slideNumber: 'c/t'     // "5/12" (current/total)

3.4 Transitions

Option Default Description
transition 'slide' Slide transition style
transitionSpeed 'default' Transition speed
backgroundTransition 'fade' Background transition style

Transition styles: none, fade, slide, convex, concave, zoom

Transition speeds: default, fast, slow

Override per-slide with data attributes:

<section data-transition="zoom">
  <h2>This slide zooms in</h2>
</section>

<section data-transition="fade-in slide-out">
  <h2>Fade in, slide out</h2>
</section>

3.5 Fragments

Option Default Description
fragments true Enable fragment animations
fragmentInURL true Track fragment state in URL
sortFragmentsOnSync true Auto-sort fragments by DOM order

3.6 Media and Embedding

Option Default Description
autoPlayMedia null Auto-play <video> and <audio> (null = only if autoplay attr present)
preloadIframes null Pre-load iframes (null = only if data-preload attr present)
preventIframeAutoFocus false Prevent iframes from stealing focus
embedded false Run in embedded mode (no keyboard capture)
previewLinks false Open links in preview overlay instead of navigating
showNotes false Show speaker notes in presentation
showHiddenSlides false Show slides marked as hidden
display 'block' CSS display property for slides

3.7 Auto-Slide

Option Default Description
autoSlide 0 Milliseconds between auto-advance (0 = disabled)
autoSlideStoppable true Allow user input to stop auto-slide
autoSlideMethod null Custom function for auto-slide navigation
defaultTiming null Default seconds per slide (used by speaker timer)
// Auto-advance every 5 seconds
Reveal.initialize({ autoSlide: 5000 });

Override per-slide:

<section data-autoslide="10000">
  <p>This slide stays for 10 seconds</p>
</section>

3.8 Cursor Behavior

Option Default Description
mouseWheel false Navigate slides with scroll wheel
hideInactiveCursor true Hide cursor when idle
hideCursorTime 5000 Milliseconds before cursor hides

3.9 Hash and History

Option Default Description
hash false Reflect current slide in URL hash
respondToHashChanges true Navigate when URL hash changes
history false Push each slide change to browser history

Important

When hash: true is enabled, each slide gets a unique URL. This is essential for sharing links to specific slides but may conflict with some single-page application routers.

3.10 Parallax Backgrounds

Reveal.initialize({
  parallaxBackgroundImage: 'https://example.com/bg.jpg',
  parallaxBackgroundSize: '2100px 900px',
  parallaxBackgroundRepeat: '',
  parallaxBackgroundPosition: '',
  parallaxBackgroundHorizontal: 200,
  parallaxBackgroundVertical: 50
});

3.11 Views and Scroll Mode

Option Default Description
view null View mode (null, 'print', 'scroll')
scrollLayout 'full' Scroll view layout
scrollSnap 'mandatory' CSS scroll-snap behavior
scrollProgress 'auto' Progress indicator in scroll view
scrollActivationWidth 435 Viewport width below which scroll view activates

3.12 PDF Export Settings

Option Default Description
pdfMaxPagesPerSlide Infinity Limit pages generated per slide
pdfSeparateFragments true Each fragment step gets its own page
pdfPageHeightOffset -1 Height adjustment for PDF pages

3.13 View Distance (Performance)

Option Default Description
viewDistance 3 Number of slides away from current to pre-render
mobileViewDistance 2 Pre-render distance on mobile

Reducing these values improves performance for presentations with heavy media content.

3.14 PostMessage API

Option Default Description
postMessage true Enable postMessage API
postMessageEvents false Dispatch reveal.js events via postMessage
focusBodyOnPageVisibilityChange true Focus body when page becomes visible

For the full configuration reference with internal options and architecture details, see the Admin Guide.


4. Core Usage

4.1 Slide Structure

Every reveal.js presentation requires this DOM structure:

<div class="reveal">
  <div class="slides">
    <section>Horizontal Slide 1</section>
    <section>Horizontal Slide 2</section>
  </div>
</div>

Vertical Slides

Nest <section> elements to create vertical slide stacks:

<section>
  <section>Vertical Slide 1 (top)</section>
  <section>Vertical Slide 2</section>
  <section>Vertical Slide 3</section>
</section>

Navigate down through vertical slides, then right to the next horizontal group.

4.2 Themes

reveal.js ships with 14 themes:

Theme Description
beige Warm beige with serif headings
black Black background, white text (default)
black-contrast High-contrast black variant
blood Dark with red accents
dracula Popular dark color scheme
league Gray textured background
moon Dark blue background
night Black with thick heading fonts
serif Cappuccino tones with serif fonts
simple Clean white background
sky Light blue gradient
solarized Solarized color palette
white White background, dark text
white-contrast High-contrast white variant

Switch themes by changing the theme stylesheet:

<!-- Pick one -->
<link rel="stylesheet" href="dist/theme/dracula.css" />
<link rel="stylesheet" href="dist/theme/moon.css" />
<link rel="stylesheet" href="dist/theme/serif.css" />

Or when installed via npm:

import 'reveal.js/dist/theme/dracula.css';

4.3 Slide Backgrounds

Color Backgrounds

<section data-background-color="#4d7e65">
  <h2>Solid color</h2>
</section>

<section data-background-gradient="linear-gradient(to bottom, #283b95, #17b2c3)">
  <h2>Gradient</h2>
</section>

Image Backgrounds

<section data-background-image="image.jpg"
         data-background-size="cover"
         data-background-position="center"
         data-background-repeat="no-repeat"
         data-background-opacity="0.5">
  <h2>Image background</h2>
</section>

Video Backgrounds

<section data-background-video="video.mp4"
         data-background-video-loop
         data-background-video-muted>
  <h2>Video background</h2>
</section>

Iframe Backgrounds

<section data-background-iframe="https://example.com"
         data-background-interactive>
  <h2>Interactive iframe background</h2>
</section>

Use data-preload on iframe backgrounds to load them before the slide is reached. The data-background-interactive attribute allows clicking and scrolling within the iframe.

All background data attributes:

Attribute Purpose
data-background Shorthand for color or image
data-background-color CSS color value
data-background-gradient CSS gradient value
data-background-image Image URL
data-background-size CSS background-size
data-background-position CSS background-position
data-background-repeat CSS background-repeat
data-background-opacity Opacity (0-1)
data-background-video Video URL
data-background-video-loop Loop the video
data-background-video-muted Mute the video
data-background-iframe Iframe URL
data-background-interactive Enable iframe interaction
data-background-transition Per-slide background transition
data-preload Pre-load this background

4.4 Fragments (Step-by-Step Reveals)

Wrap elements in the fragment class to reveal them incrementally:

<section>
  <p class="fragment">Appears first</p>
  <p class="fragment">Appears second</p>
  <p class="fragment">Appears third</p>
</section>

Fragment Animations

Class Effect
fade-out Fade out on step
fade-up Slide up while fading in
fade-down Slide down while fading in
fade-left Slide left while fading in
fade-right Slide right while fading in
fade-in-then-out Fade in, then fade out on next step
fade-in-then-semi-out Fade in, then dim on next step
grow Scale up
shrink Scale down
strike Strikethrough text
highlight-red Turn text red
highlight-blue Turn text blue
highlight-green Turn text green
highlight-current-red Red on current step, revert on next
highlight-current-blue Blue on current step, revert on next
highlight-current-green Green on current step, revert on next
<p class="fragment fade-up">Slides up</p>
<p class="fragment highlight-red">Turns red</p>
<p class="fragment fade-in-then-out">Appears then disappears</p>

Custom Fragment Order

Use data-fragment-index to control the order:

<p class="fragment" data-fragment-index="3">Third</p>
<p class="fragment" data-fragment-index="1">First</p>
<p class="fragment" data-fragment-index="2">Second</p>

Warning

Fragment animations fade-out and fade-in-then-out may produce blank slides under certain conditions (GitHub issue #3884). Test these animations with your specific content before presenting.

4.5 Speaker Notes

Add notes visible only in the speaker view:

<section>
  <h2>My Slide</h2>
  <aside class="notes">
    These notes are only visible in speaker view.
    You can use <strong>HTML</strong> formatting here.
  </aside>
</section>

Or use the data-notes attribute for plain text:

<section data-notes="Simple plain text note for this slide">
  <h2>My Slide</h2>
</section>

Open speaker view by pressing S or appending ?notes to the URL.

4.6 Keyboard Shortcuts

Key Action
Arrow keys Navigate between slides
Space Next slide/fragment
Shift + Space Previous slide/fragment
S Open speaker notes window
O or Esc Toggle overview mode
F Toggle fullscreen
B or . Pause/blackout
? Toggle help overlay
Ctrl + Shift + F Open search (requires Search plugin)
Alt + click Zoom into element (requires Zoom plugin; Ctrl + click on Linux)

4.7 Slide Visibility

Control whether slides appear in the deck:

<!-- Completely removed from the flow -->
<section data-visibility="hidden">
  <h2>This slide won't appear</h2>
</section>

<!-- Visible but not counted in slide numbers -->
<section data-visibility="uncounted">
  <h2>Bonus slide (unnumbered)</h2>
</section>

4.8 Auto-Animate

Create smooth transitions between consecutive slides by adding data-auto-animate:

<section data-auto-animate>
  <h1>Step 1</h1>
</section>
<section data-auto-animate>
  <h1 style="color: red;">Step 1</h1>
  <p>New content appears</p>
</section>

reveal.js automatically animates matching elements between slides. It matches elements by:

  1. Explicit data-id attribute (strongest match)
  2. Tag name + text content (for text, images, videos, code blocks)

Matching with data-id

<section data-auto-animate>
  <div data-id="box" style="width: 100px; height: 100px; background: blue;"></div>
</section>
<section data-auto-animate>
  <div data-id="box" style="width: 300px; height: 100px; background: red;"></div>
</section>

Controlling Animation Behavior

Global configuration:

Option Default Description
autoAnimate true Enable auto-animate globally
autoAnimateEasing 'ease' CSS easing function
autoAnimateDuration 1.0 Animation duration in seconds
autoAnimateUnmatched true Animate unmatched elements (fade in)
autoAnimateMatcher null Custom matching function

Animatable CSS properties (default):

opacity, color, background-color, padding, font-size, line-height, letter-spacing, border-width, border-color, border-radius, outline, outline-offset

Per-element overrides:

<div data-id="box"
     data-auto-animate-easing="ease-in-out"
     data-auto-animate-duration="0.8"
     data-auto-animate-delay="0.2">
</div>

Grouping and Breaking Chains

Use data-auto-animate-id to group non-adjacent auto-animate slides:

<section data-auto-animate data-auto-animate-id="group-a">Step A1</section>
<section data-auto-animate data-auto-animate-id="group-b">Step B1</section>
<section data-auto-animate data-auto-animate-id="group-a">Step A2</section>

Use data-auto-animate-restart to break an animation chain:

<section data-auto-animate>Step 1</section>
<section data-auto-animate>Step 2 (animates from Step 1)</section>
<section data-auto-animate data-auto-animate-restart>Step 3 (fresh start)</section>

Link to specific slides within your presentation:

<a href="#/2">Go to slide 3 (zero-indexed)</a>
<a href="#/2/1">Go to slide 3, vertical slide 2</a>

With hash: true, you can also use slide IDs:

<section id="conclusion">
  <h2>Conclusion</h2>
</section>

<!-- Link to it -->
<a href="#/conclusion">Jump to conclusion</a>

4.10 Media: Images, Video, Audio

Lazy-Loading Media

Use data-src instead of src to lazy-load:

<section>
  <img data-src="large-image.jpg" />
  <video data-src="video.mp4"></video>
  <iframe data-src="https://example.com"></iframe>
</section>

Embedded Video

<section>
  <video data-autoplay src="video.mp4"></video>
</section>

With autoPlayMedia: null (the default), videos only auto-play if they have the data-autoplay attribute.


5. Common Tasks

5.1 Writing Slides in Markdown

Load the Markdown plugin:

import Reveal from 'reveal.js';
import RevealMarkdown from 'reveal.js/plugin/markdown';

Reveal.initialize({
  plugins: [ RevealMarkdown ]
});

Inline Markdown

<section data-markdown>
  <textarea data-template>
    ## Slide Title

    A paragraph with **bold** and *italic*.

    - Item one
    - Item two
    ---
    ## Next Slide

    More content here.
  </textarea>
</section>

The default horizontal separator is ---. Configure custom separators:

<section data-markdown
         data-separator="^\n---\n$"
         data-separator-vertical="^\n--\n$"
         data-separator-notes="^notes:">
  <textarea data-template>
    ## Slide 1
    Content
    --
    ## Vertical Slide 1.1
    More content

    notes:
    Speaker notes for this slide
    ---
    ## Slide 2
  </textarea>
</section>

External Markdown Files

<section data-markdown="slides.md"
         data-separator="^\n---\n$"
         data-separator-vertical="^\n--\n$"
         data-separator-notes="^notes:"
         data-charset="utf-8">
</section>

Markdown Plugin Configuration

Reveal.initialize({
  markdown: {
    separator: '^\r?\n---\r?\n$',
    verticalSeparator: '^\r?\n--\r?\n$',
    notesSeparator: 'notes:',
    animateLists: false,
    smartypants: false,
    gfm: true,
    breaks: false
  },
  plugins: [ RevealMarkdown ]
});

Element Attributes in Markdown

Apply HTML attributes to Markdown elements:

## Title <!-- .element: class="fragment" data-fragment-index="1" -->

- Item 1 <!-- .element: class="fragment fade-up" -->
- Item 2 <!-- .element: class="fragment fade-up" -->

Apply attributes to the slide itself:

<!-- .slide: data-background="#ff0000" -->
## Red Background Slide

5.2 Syntax-Highlighted Code

Load the Highlight plugin:

import Reveal from 'reveal.js';
import RevealHighlight from 'reveal.js/plugin/highlight';

Reveal.initialize({
  plugins: [ RevealHighlight ]
});

Include a highlight.js theme:

<link rel="stylesheet" href="plugin/highlight/monokai.css" />

Basic Code Block

<section>
  <pre><code data-trim data-noescape class="language-python">
def greet(name):
    return f"Hello, {name}!"

print(greet("World"))
  </code></pre>
</section>
  • data-trim -- removes leading/trailing whitespace
  • data-noescape -- prevents HTML entity escaping

Line Numbers

<pre><code data-line-numbers class="language-javascript">
const x = 1;
const y = 2;
const z = x + y;
console.log(z);
</code></pre>

Highlight specific lines:

<!-- Highlight lines 1 and 3 -->
<pre><code data-line-numbers="1,3" class="language-javascript">
const x = 1;
const y = 2;
const z = x + y;
</code></pre>

Step-Through Highlighting

Animate through different highlighted lines as fragments:

<pre><code data-line-numbers="1|3-4|6" class="language-javascript">
const width = 960;
const height = 700;
const area = width * height;
const perimeter = 2 * (width + height);

console.log(`Area: ${area}, Perimeter: ${perimeter}`);
</code></pre>

Each pipe-separated group becomes a fragment step: first line 1 highlights, then lines 3-4, then line 6.

Highlight Plugin Configuration

Option Default Description
highlightOnLoad true Auto-highlight all code blocks
escapeHTML true Escape HTML in code blocks
beforeHighlight null Callback before highlighting
Reveal.initialize({
  highlight: {
    highlightOnLoad: true,
    escapeHTML: true
  },
  plugins: [ RevealHighlight ]
});

5.3 Rendering Math Equations

Load one of the Math sub-plugins:

import Reveal from 'reveal.js';
import RevealMath from 'reveal.js/plugin/math';

Reveal.initialize({
  plugins: [ RevealMath.KaTeX ]
  // or: RevealMath.MathJax2, RevealMath.MathJax3, RevealMath.MathJax4
});

KaTeX (Fastest)

Reveal.initialize({
  katex: {
    version: 'latest',
    delimiters: [
      { left: '$$', right: '$$', display: true },
      { left: '$', right: '$', display: false },
      { left: '\\(', right: '\\)', display: false },
      { left: '\\[', right: '\\]', display: true }
    ],
    ignoredTags: ['script', 'noscript', 'style', 'textarea', 'pre']
  },
  plugins: [ RevealMath.KaTeX ]
});

MathJax 2

Reveal.initialize({
  mathjax2: {
    mathjax: 'https://cdn.jsdelivr.net/npm/mathjax@2/MathJax.js',
    config: 'TeX-AMS_HTML-full',
    tex2jax: {
      inlineMath: [['$', '$'], ['\\(', '\\)']],
      skipTags: ['script', 'noscript', 'style', 'textarea', 'pre']
    }
  },
  plugins: [ RevealMath.MathJax2 ]
});

Using Math in Slides

<section>
  <h2>Euler's Identity</h2>
  <p>$$e^{i\pi} + 1 = 0$$</p>
  <p>Inline math: $a^2 + b^2 = c^2$</p>
</section>

5.4 Using Speaker Notes

Load the Notes plugin:

import Reveal from 'reveal.js';
import RevealNotes from 'reveal.js/plugin/notes';

Reveal.initialize({
  plugins: [ RevealNotes ]
});

The speaker view opens in a separate window showing:

  • Current slide
  • Next slide preview
  • Speaker notes
  • Elapsed time and timer

Open with S key or ?notes query parameter.

5.5 Searching Within Slides

Load the Search plugin:

import Reveal from 'reveal.js';
import RevealSearch from 'reveal.js/plugin/search';

Reveal.initialize({
  plugins: [ RevealSearch ]
});

Open search with Ctrl + Shift + F. The plugin provides programmatic access via open(), close(), and toggle() methods.

5.6 Zooming Into Elements

Load the Zoom plugin:

import Reveal from 'reveal.js';
import RevealZoom from 'reveal.js/plugin/zoom';

Reveal.initialize({
  plugins: [ RevealZoom ]
});

Alt + click (or Ctrl + click on Linux) on any element to zoom in. Click again to zoom out.

Configuration:

Option Default Description
zoomKey 'alt' Modifier key for zoom ('alt' or 'ctrl')
zoomLevel 2 Zoom magnification factor

5.7 Exporting to PDF

Add ?print-pdf to the presentation URL:

http://localhost:8000/?print-pdf

Then use the browser's print dialog (Ctrl + P / Cmd + P):

  1. Set destination to "Save as PDF"
  2. Set layout to "Landscape"
  3. Set margins to "None"
  4. Enable "Background graphics"

Tip

Set pdfSeparateFragments: false to collapse all fragment steps into single slides for more compact PDFs. If your PDFs are very large, reduce the number of high-resolution background images.

PDF Configuration

Reveal.initialize({
  pdfMaxPagesPerSlide: 1,        // Limit pages per slide
  pdfSeparateFragments: true,     // Each fragment = separate page
  pdfPageHeightOffset: -1         // Fine-tune page height
});

5.8 Scroll View

Enable scroll view for a document-like reading experience:

Reveal.initialize({
  view: 'scroll',
  scrollLayout: 'full',
  scrollSnap: 'mandatory',
  scrollProgress: 'auto'
});

On viewports narrower than scrollActivationWidth (default: 435px), scroll view activates automatically.

5.9 Embedding a Presentation

Embed reveal.js in a page alongside other content:

<div class="reveal" style="width: 800px; height: 600px;">
  <div class="slides">
    <section>Embedded slide</section>
  </div>
</div>

<script type="module">
  import Reveal from 'reveal.js';
  let deck = new Reveal(document.querySelector('.reveal'), {
    embedded: true,
    keyboardCondition: 'focused'
  });
  deck.initialize();
</script>

With embedded: true, the presentation only captures keyboard events when focused. Multiple independent presentations can coexist on one page.

5.10 Using the PostMessage API

Control a presentation embedded in an iframe:

// From the parent page
const iframe = document.querySelector('iframe');

// Navigate to slide 2
iframe.contentWindow.postMessage(
  JSON.stringify({ method: 'slide', args: [2] }),
  '*'
);

// Listen for events
window.addEventListener('message', event => {
  const data = JSON.parse(event.data);
  if (data.namespace === 'reveal') {
    console.log('Slide changed:', data.eventName);
  }
});

Enable event broadcasting:

Reveal.initialize({
  postMessage: true,
  postMessageEvents: true
});

5.11 Multiple Presentations on One Page

Create independent deck instances by passing a DOM element:

let deck1 = new Reveal(document.querySelector('#deck1'), {
  embedded: true
});
deck1.initialize();

let deck2 = new Reveal(document.querySelector('#deck2'), {
  embedded: true
});
deck2.initialize();

5.12 Loading All Plugins Together

A typical full-featured setup:

import Reveal from 'reveal.js';
import RevealHighlight from 'reveal.js/plugin/highlight';
import RevealMarkdown from 'reveal.js/plugin/markdown';
import RevealMath from 'reveal.js/plugin/math';
import RevealNotes from 'reveal.js/plugin/notes';
import RevealSearch from 'reveal.js/plugin/search';
import RevealZoom from 'reveal.js/plugin/zoom';

Reveal.initialize({
  hash: true,
  plugins: [
    RevealHighlight,
    RevealMarkdown,
    RevealMath.KaTeX,
    RevealNotes,
    RevealSearch,
    RevealZoom
  ]
});

5.13 React Component Recipes

Basic React Deck

import { Deck, Slide, Fragment, Code } from '@revealjs/react';

function MyPresentation() {
  return (
    <Deck transition="slide" hash={true}>
      <Slide>
        <h1>Welcome</h1>
        <Fragment>
          <p>This fades in</p>
        </Fragment>
      </Slide>
      <Slide backgroundColor="#4d7e65">
        <h2>Code Example</h2>
        <Code language="javascript" lineNumbers="1|3-4">
          {`const x = 1;
const y = 2;
const sum = x + y;
console.log(sum);`}
        </Code>
      </Slide>
    </Deck>
  );
}

Accessing the Reveal Instance

import { useReveal } from '@revealjs/react';

function SlideControls() {
  const reveal = useReveal();

  return (
    <button onClick={() => reveal.next()}>
      Next Slide
    </button>
  );
}

Vertical Slide Stacks

import { Deck, Slide, Stack } from '@revealjs/react';

function Presentation() {
  return (
    <Deck>
      <Slide>Horizontal 1</Slide>
      <Stack>
        <Slide>Vertical 1.1</Slide>
        <Slide>Vertical 1.2</Slide>
      </Stack>
      <Slide>Horizontal 2</Slide>
    </Deck>
  );
}

5.14 Custom Keyboard Bindings

Add custom key handlers:

Reveal.initialize({
  keyboard: {
    // Pause on 'P' key (key code 80)
    80: () => { Reveal.togglePause(); },
    // Go to first slide on 'Home'
    36: () => { Reveal.slide(0); }
  }
});

5.15 Event Listeners

React to presentation events:

Reveal.on('slidechanged', event => {
  console.log(`Moved to slide ${event.indexh}, ${event.indexv}`);
});

Reveal.on('fragmentshown', event => {
  console.log('Fragment shown:', event.fragment);
});

Reveal.on('overviewshown', () => {
  console.log('Overview mode activated');
});

Reveal.on('paused', () => {
  console.log('Presentation paused');
});

6. Troubleshooting

Error: Unable to find presentation root

Cause: reveal.js cannot locate the .reveal container element in the DOM.

Solution: Ensure your HTML contains the required structure. The div.reveal > div.slides hierarchy must exist before Reveal.initialize() is called:

<div class="reveal">
  <div class="slides">
    <section>At least one slide</section>
  </div>
</div>

If initializing dynamically, make sure the DOM is ready (use DOMContentLoaded or place the script after the markup).

Prevention: Always verify the container structure exists before initializing. When using frameworks like React, use the @revealjs/react wrapper which handles this automatically.

Error: Please specify a valid media type to preview

Cause: The previewLinks feature was activated but could not determine the content type of the linked resource.

Solution: Ensure the linked URL returns proper content-type headers, or disable link previews for problematic links by adding data-preview-link="false" to the anchor element.

Prevention: Only enable previewLinks: true when your linked resources are reliably accessible and serve correct headers.

Problem: npm install fails

Cause: Node.js version mismatch or network issues. reveal.js v6.0.1 requires Node.js 20.19.0+ (GitHub issue #3821).

Solution:

node --version
# Must be >= 20.19.0

nvm install 20
nvm use 20
npm install

Prevention: Use a .nvmrc file or check the engines field in package.json before installing.

Problem: PDF export generates very large files

Cause: High-resolution background images and per-fragment page generation inflate file size (GitHub issue #3860).

Solution:

  1. Optimize background images before using them
  2. Reduce fragment separation:
    Reveal.initialize({ pdfSeparateFragments: false });
    
  3. Limit pages per slide:
    Reveal.initialize({ pdfMaxPagesPerSlide: 1 });
    

Prevention: Use compressed images and consider whether every fragment step needs its own PDF page.

Problem: PDF export fails with Decktape

Cause: Compatibility issues between Decktape and reveal.js v6 (GitHub issue #3888).

Solution: Use the built-in ?print-pdf approach with browser print instead of Decktape. If Decktape is required, check the issue thread for updated compatibility patches.

Prevention: Test PDF export workflow before committing to Decktape for production use.

Problem: Dracula theme background color changed in v6

Cause: The Dracula theme SCSS was updated in v6.0.1 (GitHub issue #3895).

Solution: Override the background color in your custom CSS:

.reveal {
  background-color: #282a36; /* Original Dracula background */
}

Prevention: Pin theme variables explicitly if your design depends on specific colors.

Problem: Content with multiple spaces is corrupted

Cause: Whitespace normalization strips intentional multiple spaces in text content (GitHub issue #3839).

Solution: Use &nbsp; entities for intentional multiple spaces, or wrap content in <pre> tags to preserve whitespace.

Prevention: Avoid relying on multiple consecutive spaces in HTML content; use CSS for spacing instead.

Problem: v6 not available on CDN

Cause: The v6 release has not been published to CDN providers yet (GitHub issue #3894).

Solution: Install via npm or serve the dist/ files from your own server after building from source.

Prevention: Use npm for production deployments rather than relying on CDN availability for new major versions.

Caution

If you encounter TypeScript errors related to keyboard configuration types, this is a known typing issue (GitHub issue #3749). Cast the config values as needed or use // @ts-ignore as a temporary workaround.

Problem: Background styling leaks outside reveal.js

Cause: The .background CSS class is not scoped exclusively to reveal.js (GitHub issue #3869).

Solution: Ensure your page's CSS does not use a generic .background class, or scope reveal.js within a shadow DOM or iframe.

Prevention: Use namespaced CSS classes in your surrounding page to avoid collisions.

Problem: Vertical background offset calculated incorrectly

Cause: Background positioning miscalculation for vertical slides (GitHub issue #3878).

Solution: Set explicit background positioning via data-background-position on affected vertical slides.

Prevention: Test vertical slide backgrounds across different viewport sizes.


7. FAQ

Q: What browsers does reveal.js support? reveal.js works in all modern evergreen browsers (Chrome, Firefox, Safari, Edge). It uses standard Web APIs and ES modules.

Q: Can I use reveal.js without npm or Node.js? Yes. Download the built files from the GitHub releases page and include dist/reveal.esm.js and dist/reveal.css directly in your HTML. Node.js is only needed for development (running the dev server, building from source, running tests).

Q: How do I use multiple plugins together? Pass them all in the plugins array during initialization. Each plugin registers itself by its ID (e.g., 'highlight', 'markdown', 'notes'). See recipe 5.12 for a complete example.

Q: Can I run multiple presentations on one page? Yes. Create separate new Reveal(element) instances with embedded: true. Each deck operates independently. See recipe 5.11.

Q: How do I navigate to a specific slide via URL? Enable hash: true in your config. Slides are addressable as #/0 (first slide), #/1/2 (second horizontal, third vertical). Named slides with id attributes work as #/slide-id.

Q: How do I customize a theme? Create a custom SCSS file that imports the theme template and overrides variables. The theme source files are in css/theme/source/. Build with npm run build to compile. Alternatively, override styles in a regular CSS file loaded after the theme.

Q: What is the difference between view: 'scroll' and normal mode? Scroll view transforms the presentation into a vertically scrollable document, similar to a web page. Normal mode shows one slide at a time with transitions. Scroll view activates automatically on narrow viewports (below scrollActivationWidth: 435 pixels).

Q: Can I use reveal.js with React? Yes. Install @revealjs/react (v0.2.1) which provides Deck, Slide, Stack, Markdown, Fragment, and Code components, plus a useReveal() hook. Requires React 18+ and reveal.js 5+.

Q: How do I add my own JavaScript logic to slides? Listen to reveal.js events (see recipe 5.15) or write a custom plugin. The Reveal.on() method lets you react to slidechanged, fragmentshown, fragmenthidden, overviewshown, paused, resumed, and other lifecycle events.

Q: How do I disable specific keyboard shortcuts? Pass a keyboard config object that maps key codes to null:

Reveal.initialize({
  keyboard: {
    27: null  // Disable Escape key (overview toggle)
  }
});

Note

For configuration internals, plugin architecture, build system details, and deployment strategies, see the Admin Guide at hakimel/reveal.js.