Reveal.js is an open-source HTML presentation framework created by Hakim El Hattab in 2011. It transforms standard HTML into beautiful, interactive slide decks that run directly in the browser.
With over 67,000 GitHub stars, it powers millions of presentations worldwide — including talks at major tech conferences, university lectures, and corporate training.
Three ways to set up a Reveal.js presentation — pick whichever fits your workflow:
Link directly to hosted files. Zero setup, single HTML file, works offline once cached.
Full development environment with live reload, Sass compilation, and plugin management.
Scaffold a new presentation with npx create-reveal. Templates and configuration included.
Minimal CDN boilerplate — this is all you need:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js@4.6.1/dist/reveal.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js@4.6.1/dist/theme/black.css">
</head>
<body>
<div class="reveal">
<div class="slides">
<section>Slide 1</section>
<section>Slide 2</section>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/reveal.js@4.6.1/dist/reveal.js"></script>
<script>Reveal.initialize();</script>
</body>
</html>
Every slide is a <section> element inside .slides. Reveal.js supports horizontal (left/right) and vertical (up/down) navigation by nesting sections.
<div class="reveal">
<div class="slides">
<!-- Horizontal slide 1 -->
<section>Slide 1</section>
<!-- Vertical stack -->
<section>
<section>Vertical 1</section>
<section>Vertical 2</section>
</section>
<!-- Horizontal slide 3 -->
<section id="named"
data-background="#4ecb8d"
data-transition="zoom">
Custom slide
</section>
</div>
</div>
data-background — colour, image, video, iframedata-transition — per-slide transitiondata-auto-animate — morph between slidesdata-visibility — hidden or uncountedid — deep-linkable URL hashHorizontal = chapters or main topics
Vertical = sub-topics or detail drilldowns
Press Esc for the overview grid, O for overview mode. Arrow keys or swipe to navigate.
Write slides in Markdown instead of HTML. Reveal.js uses the marked parser and supports both inline and external file Markdown.
Embed Markdown directly inside a <section>:
<section data-markdown>
<textarea data-template>
## Slide Title
- Bullet one
- Bullet two
```js
console.log('Hello');
```
</textarea>
</section>
Load slides from a separate .md file:
<section data-markdown="slides.md"
data-separator="^---$"
data-separator-vertical="^--$"
data-separator-notes="^Notes:">
</section>
Add HTML attributes via special comments:
Item 1 <!-- .element: class="fragment" -->
Item 2 <!-- .element: class="fragment" -->
Control how slides enter and exit with transition effects. Set globally in config or per-slide with data-transition.
| Transition | Description |
|---|---|
none | Instant switch, no animation |
fade | Cross-fade between slides |
slide | Slide in from the right |
convex | Slide with 3D convex curve |
concave | Slide with 3D concave curve |
zoom | Zoom in to the next slide |
<!-- Per-slide transition -->
<section data-transition="zoom">
Zooms in!
</section>
<!-- Mixed in/out -->
<section data-transition="fade-in slide-out">
Fades in, slides out
</section>
Automatically animate matching elements between consecutive slides. Just add data-auto-animate to both sections:
<section data-auto-animate>
<h1>Hello</h1>
</section>
<section data-auto-animate>
<h1 style="color:red;">Hello</h1>
<p>World</p>
</section>
data-auto-animate-easing — CSS easing functiondata-auto-animate-duration — seconds (default 1.0)data-auto-animate-unmatched — fade or hidedata-id — explicitly match elements across slidesFragments reveal elements step-by-step within a single slide. Add the fragment class and optionally a style class to control the animation.
| Class | Effect |
|---|---|
fade-in | Fades in (default) |
fade-out | Starts visible, fades out |
fade-up | Slides up while fading in |
fade-down | Slides down while fading in |
highlight-red | Text turns red |
highlight-green | Text turns green |
highlight-blue | Text turns blue |
grow | Scales up |
shrink | Scales down |
strike | Strikethrough text |
semi-fade-out | Fades to 50% opacity |
<p class="fragment">Appears first</p>
<p class="fragment fade-up">Slides up</p>
<p class="fragment highlight-red">Turns red</p>
<!-- Custom order with data-fragment-index -->
<p class="fragment" data-fragment-index="2">Second</p>
<p class="fragment" data-fragment-index="1">First</p>
<!-- Nested: fade in, then highlight -->
<span class="fragment fade-in">
<span class="fragment highlight-green">
Two-step reveal
</span>
</span>
Reveal.js integrates highlight.js for syntax highlighting across 190+ languages. Line numbers and step-through highlighting make code walkthroughs powerful.
<pre><code class="language-javascript">
function greet(name) {
return `Hello, ${name}!`;
}
</code></pre>
<!-- Show all line numbers -->
<pre><code data-line-numbers>
const x = 1;
const y = 2;
const z = x + y;
</code></pre>
<!-- Highlight specific lines -->
<pre><code data-line-numbers="1|3|5-7">
// Step through highlighted lines
// using the pipe separator
</code></pre>
Use data-line-numbers="1-2|3-4|5-6" to walk through code in stages. Each pipe creates a new fragment step.
// 1. Import the framework
import Reveal from 'reveal.js';
// 2. Configure options
const deck = new Reveal({ transition: 'fade' });
// 3. Initialize
deck.initialize().then(() => {
console.log('Presentation ready!');
});
Reveal.initialize({
plugins: [RevealHighlight],
highlight: { theme: 'monokai' }
});
Reveal.js ships with 12 built-in themes. Switch themes by changing one CSS import, or build a fully custom theme using CSS custom properties.
| Theme | Style |
|---|---|
black | Dark background, white text (default) |
white | Light background, dark text |
league | Grey textured background |
beige | Warm beige tones |
sky | Light blue gradient |
night | Dark blue background |
serif | Serif fonts, cappuccino feel |
simple | Minimal white design |
solarized | Solarized colour scheme |
moon | Dark with muted blue |
dracula | Dracula colour palette |
blood | Dark with red accents |
<!-- Just change the CSS file -->
<link rel="stylesheet"
href="dist/theme/dracula.css">
:root {
--r-background-color: #0a0a0f;
--r-main-font: 'DM Sans', sans-serif;
--r-main-color: #e8e8f0;
--r-heading-font: 'Playfair Display', serif;
--r-heading-color: #f5a623;
--r-link-color: #60a5fa;
--r-link-color-hover: #a78bfa;
--r-selection-background-color: #f5a623;
}
Themes are Sass files in css/theme/source/. Compile with npm run build or write plain CSS overrides.
Reveal.js uses a fixed viewport model. Slides are authored at a reference size and scaled to fit the display. CSS Grid and Flexbox work normally inside slides.
Reveal.initialize({
width: 1100, // Reference width in pixels
height: 900, // Reference height in pixels
margin: 0.04, // Margin around slides (fraction)
minScale: 0.2, // Minimum scale factor
maxScale: 2.0 // Maximum scale factor
});
/* Two-column layout */
.cols-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.5em;
}
/* Asymmetric 60/40 split */
.cols-6040 {
grid-template-columns: 6fr 4fr;
}
width and heightr-stretch class auto-sizes images and media to fill available spacer-stretch — stretch element to fill spacer-fit-text — auto-size text to fit slider-stack — stack children on top of each otherr-frame — add a thin border framer-hstack / r-vstack — flex row/columnEach slide can have its own full-bleed background — colours, images, videos, or even live iframes. Media inside slides can auto-play and lazy-load.
<!-- Solid colour -->
<section data-background-color="#4ecb8d">
<!-- Image -->
<section data-background-image="bg.jpg"
data-background-size="cover"
data-background-opacity="0.3">
<!-- Video -->
<section data-background-video="bg.mp4"
data-background-video-loop
data-background-video-muted>
<!-- Gradient -->
<section data-background-gradient=
"linear-gradient(to right, #0a0a0f, #1a1a3f)">
<!-- Live iframe -->
<section data-background-iframe=
"https://revealjs.com"
data-background-interactive>
<!-- Auto-play video on slide enter -->
<video data-autoplay src="demo.mp4"></video>
<!-- Lazy-loaded iframe -->
<iframe data-src="https://example.com"
width="800" height="500">
</iframe>
<!-- Stretch image to fill -->
<img class="r-stretch" src="chart.png">
Control how backgrounds animate between slides:
Reveal.initialize({
backgroundTransition: 'fade' // or slide, convex, concave, zoom
});
Press S to open the speaker view in a new window. It shows your notes, a timer, the current slide, and a preview of the upcoming slide.
<section>
<h2>My Slide</h2>
<p>Visible content</p>
<aside class="notes">
These notes are only visible
in the speaker view.
- Remember to mention X
- Demo the feature live
</aside>
</section>
## My Slide
Visible content here.
Notes:
These notes are only visible
in the speaker view.
import RevealNotes from 'reveal.js/plugin/notes/notes';
Reveal.initialize({
plugins: [RevealNotes]
});
// Or from CDN:
// plugin/notes/notes.js
Speaker view works cross-window. Open it on your laptop screen while projecting the main presentation on the big screen.
Reveal.js exposes dozens of configuration properties. Pass them to Reveal.initialize() to customise every aspect of behaviour.
| Option | Default | Description |
|---|---|---|
controls | true | Show navigation arrows |
progress | true | Show progress bar |
hash | false | Update URL hash on slide change |
history | false | Push slide changes to browser history |
keyboard | true | Enable keyboard navigation |
touch | true | Enable touch/swipe navigation |
loop | false | Loop the presentation |
autoSlide | 0 | Auto-advance interval (ms) |
center | true | Vertically centre slide content |
embedded | false | Run inside an iframe |
Reveal.initialize({
hash: true,
history: true,
transition: 'fade',
transitionSpeed: 'fast',
// Display
controls: true,
progress: true,
center: false,
slideNumber: 'c/t',
// Viewport
width: 1100,
height: 900,
margin: 0.04,
// Auto-advance (0 = off)
autoSlide: 0,
autoSlideStoppable: true,
// Plugins
plugins: [
RevealHighlight,
RevealNotes,
RevealMarkdown
]
});
Reveal.js has a modular plugin system. Built-in plugins ship with the framework; third-party plugins extend it further. Writing your own plugin is straightforward.
const MyPlugin = {
id: 'my-plugin',
init: (reveal) => {
// Called when Reveal.js initialises
console.log('Slides:', reveal.getTotalSlides());
// Listen to events
reveal.on('slidechanged', (event) => {
console.log('Now on slide', event.indexh);
});
// Access the API
reveal.getSlides().forEach(slide => {
// Modify slides programmatically
});
},
destroy: () => {
// Cleanup when plugin is removed
}
};
// Register the plugin
Reveal.initialize({
plugins: [MyPlugin]
});
Reveal.js supports PDF export through a special print stylesheet. Append ?print-pdf to your URL, then use the browser's Print dialog.
presentation.html?print-pdf/* Only applies in print-pdf mode */
@media print {
.reveal .slides section {
page-break-after: always;
min-height: 100%;
}
.no-print { display: none; }
}
For reliable, automated PDF export, use Decktape — a headless Chrome-based tool designed for slide decks.
# Install globally
npm install -g decktape
# Export to PDF
decktape reveal \
http://localhost:8000 \
output.pdf
# Custom page size
decktape reveal \
--size 1100x900 \
presentation.html \
slides.pdf
?print-pdf before your talkpdfSeparateFragments: false to collapse fragmentsThe multiplex plugin lets a presenter control slide navigation across multiple client browsers in real time via Socket.io.
// Master (presenter) configuration
Reveal.initialize({
multiplex: {
secret: 'your-secret-token',
id: 'presentation-id',
url: 'https://reveal-multiplex.glitch.me'
},
plugins: [RevealMultiplex]
});
// Client (audience) configuration
Reveal.initialize({
multiplex: {
// No secret — clients can't control
id: 'presentation-id',
url: 'https://reveal-multiplex.glitch.me'
},
plugins: [RevealMultiplex]
});
Reveal.js presentations can embed external content and also be embedded inside other pages. Lazy loading ensures performance stays snappy.
<!-- Lazy-loaded iframe -->
<iframe data-src="https://codepen.io/..."
width="100%" height="500">
</iframe>
<!-- Background iframe (full slide) -->
<section data-background-iframe=
"https://threejs.org/examples/"
data-background-interactive>
<!-- Content overlays the iframe -->
</section>
<!-- Auto-play media on slide enter -->
<video data-autoplay data-src="demo.mp4"
width="800"></video>
<!-- Embed as an iframe -->
<iframe src="slides.html"
width="800" height="600"
frameborder="0">
</iframe>
// Enable embedded mode
Reveal.initialize({
embedded: true, // Only capture input when focused
keyboardCondition: 'focused'
});
Control an embedded deck from the parent page:
// Parent page controls the deck
const iframe = document.querySelector('iframe');
iframe.contentWindow.postMessage(
JSON.stringify({ method: 'slide', args: [2, 0] }),
'*'
);
Reveal.js presentations are static HTML — deploy them anywhere you can serve files. No server-side runtime required.
index.html to a repousername.github.io/reponpx surge . one-liner deployLoad Reveal.js from a CDN — your entire presentation is a single index.html file. No build step, no dependencies. Email it, put it on a USB stick, or host it anywhere.
# .github/workflows/deploy.yml
name: Deploy Slides
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/configure-pages@v4
- uses: actions/upload-pages-artifact@v3
with:
path: '.'
- uses: actions/deploy-pages@v4
.md filesThank you! — Built with Reveal.js · Single self-contained HTML file