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
2.1 npm (Recommended)
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 builddist/reveal.js-- UMD builddist/reveal.css-- core stylesdist/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/reactwith Vite 8, be aware that Markdown fragment directives may not work correctly (see GitHub issue #3883). Use JSXFragmentcomponents 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: trueis 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-outandfade-in-then-outmay 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:
- Explicit
data-idattribute (strongest match) - 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>
4.9 Links and Slide References
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 whitespacedata-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):
- Set destination to "Save as PDF"
- Set layout to "Landscape"
- Set margins to "None"
- Enable "Background graphics"
Tip
Set
pdfSeparateFragments: falseto 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:
- Optimize background images before using them
- Reduce fragment separation:
Reveal.initialize({ pdfSeparateFragments: false }); - 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 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-ignoreas 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.